@ray-js/robot-map 0.0.5-beta.9 → 0.0.7-beta.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/lib/RjsComponent/index.js +15 -2
- package/lib/RjsComponent/index.rjs +19 -1
- package/lib/RjsRobotMap.js +3 -1
- package/lib/RobotMap.d.ts +1 -1
- package/lib/RobotMap.js +54 -9
- package/lib/props.d.ts +82 -1
- package/package.json +2 -2
|
@@ -106,10 +106,20 @@ const componentOptions = {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
|
+
furnitures: {
|
|
110
|
+
type: Array,
|
|
111
|
+
observer: function (newValue, oldValue) {
|
|
112
|
+
if (!isEqual(newValue, oldValue) && this.data.initialized && this.render) {
|
|
113
|
+
this.render.onReceiveFurnitures(newValue);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
109
117
|
config: {
|
|
110
118
|
type: Object,
|
|
111
|
-
observer: function () {
|
|
112
|
-
|
|
119
|
+
observer: function (newValue, oldValue) {
|
|
120
|
+
if (!isEqual(newValue, oldValue) && this.data.initialized && this.render) {
|
|
121
|
+
this.render.onUpdateConfig(newValue);
|
|
122
|
+
}
|
|
113
123
|
}
|
|
114
124
|
},
|
|
115
125
|
runtime: {
|
|
@@ -171,6 +181,9 @@ const componentOptions = {
|
|
|
171
181
|
if (this.data.customElements) {
|
|
172
182
|
this.render.onReceiveCustomElements(this.data.customElements);
|
|
173
183
|
}
|
|
184
|
+
if (this.data.furnitures) {
|
|
185
|
+
this.render.onReceiveFurnitures(this.data.furnitures);
|
|
186
|
+
}
|
|
174
187
|
if (this.data.runtime) {
|
|
175
188
|
var _this$data$runtime$di;
|
|
176
189
|
this.render.onUpdateRuntime(_objectSpread(_objectSpread({}, this.data.runtime), {}, {
|
|
@@ -30,7 +30,13 @@ export default Render({
|
|
|
30
30
|
await this.mapInstance.initialize({
|
|
31
31
|
resizeTo: containerElement ?? window,
|
|
32
32
|
events,
|
|
33
|
-
config
|
|
33
|
+
config: {
|
|
34
|
+
...config,
|
|
35
|
+
global: {
|
|
36
|
+
...config?.global,
|
|
37
|
+
rendererPreference: 'webgl',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
34
40
|
})
|
|
35
41
|
|
|
36
42
|
if (containerElement) {
|
|
@@ -123,6 +129,18 @@ export default Render({
|
|
|
123
129
|
this.mapInstance.drawCustomElements(elements)
|
|
124
130
|
},
|
|
125
131
|
|
|
132
|
+
// 接收家具
|
|
133
|
+
onReceiveFurnitures(furnitures) {
|
|
134
|
+
if (!this.mapInstance || !furnitures) return
|
|
135
|
+
this.mapInstance.drawFurnitures(furnitures)
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// 更新静态配置
|
|
139
|
+
onUpdateConfig(config) {
|
|
140
|
+
if (!this.mapInstance || !config) return
|
|
141
|
+
this.mapInstance.updateConfig(config)
|
|
142
|
+
},
|
|
143
|
+
|
|
126
144
|
// 更新运行时配置
|
|
127
145
|
onUpdateRuntime(runtime) {
|
|
128
146
|
if (!this.mapInstance || !runtime) return
|
package/lib/RjsRobotMap.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
|
|
3
|
-
const _excluded = ["map", "path", "roomProperties", "forbiddenSweepZones", "forbiddenMopZones", "cleanZones", "virtualWalls", "spots", "wayPoints", "detectedObjects", "customElements", "config", "runtime", "onMapReady"];
|
|
3
|
+
const _excluded = ["map", "path", "roomProperties", "forbiddenSweepZones", "forbiddenMopZones", "cleanZones", "virtualWalls", "spots", "wayPoints", "detectedObjects", "customElements", "furnitures", "config", "runtime", "onMapReady"];
|
|
4
4
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
5
5
|
import "core-js/modules/esnext.iterator.for-each.js";
|
|
6
6
|
import "core-js/modules/esnext.iterator.map.js";
|
|
@@ -27,6 +27,7 @@ const RjsRobotMap = _ref => {
|
|
|
27
27
|
wayPoints = [],
|
|
28
28
|
detectedObjects = [],
|
|
29
29
|
customElements = [],
|
|
30
|
+
furnitures = [],
|
|
30
31
|
config,
|
|
31
32
|
runtime,
|
|
32
33
|
onMapReady
|
|
@@ -149,6 +150,7 @@ const RjsRobotMap = _ref => {
|
|
|
149
150
|
wayPoints: wayPoints,
|
|
150
151
|
detectedObjects: detectedObjects,
|
|
151
152
|
customElements: customElements,
|
|
153
|
+
furnitures: furnitures,
|
|
152
154
|
config: config,
|
|
153
155
|
runtime: runtime,
|
|
154
156
|
containerId: containerId
|
package/lib/RobotMap.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { AppOptions } from '@ray-js/robot-map-sdk';
|
|
3
3
|
import { RobotMapProps } from './props';
|
|
4
4
|
declare const RobotMap: {
|
|
5
|
-
({ map, path, roomProperties, forbiddenSweepZones, forbiddenMopZones, cleanZones, virtualWalls, spots, wayPoints, detectedObjects, customElements, onMapReady, config, runtime, ...callbacks }: RobotMapProps & AppOptions['events']): React.JSX.Element;
|
|
5
|
+
({ map, path, roomProperties, forbiddenSweepZones, forbiddenMopZones, cleanZones, virtualWalls, spots, wayPoints, detectedObjects, customElements, customCarpets, furnitures, heatmap, onMapReady, config, runtime, ...callbacks }: RobotMapProps & AppOptions['events']): React.JSX.Element;
|
|
6
6
|
defaultProps: RobotMapProps;
|
|
7
7
|
displayName: string;
|
|
8
8
|
};
|
package/lib/RobotMap.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
|
|
2
2
|
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
3
|
-
const _excluded = ["map", "path", "roomProperties", "forbiddenSweepZones", "forbiddenMopZones", "cleanZones", "virtualWalls", "spots", "wayPoints", "detectedObjects", "customElements", "onMapReady", "config", "runtime"];
|
|
3
|
+
const _excluded = ["map", "path", "roomProperties", "forbiddenSweepZones", "forbiddenMopZones", "cleanZones", "virtualWalls", "spots", "wayPoints", "detectedObjects", "customElements", "customCarpets", "furnitures", "heatmap", "onMapReady", "config", "runtime"];
|
|
4
4
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
5
5
|
import "core-js/modules/esnext.iterator.for-each.js";
|
|
6
6
|
import "core-js/modules/esnext.iterator.map.js";
|
|
@@ -10,6 +10,7 @@ import "core-js/modules/web.dom-collections.iterator.js";
|
|
|
10
10
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
11
|
import createInvoke from '@ray-js/webview-invoke/native';
|
|
12
12
|
import { createWebviewContext, WebView } from '@ray-js/ray';
|
|
13
|
+
import { isEqual } from 'lodash-es';
|
|
13
14
|
import { MAP_API_METHODS, MAP_CALLBACK_METHODS } from '@ray-js/robot-map-sdk';
|
|
14
15
|
import { nanoid } from 'nanoid/non-secure';
|
|
15
16
|
import { robotMapDefaultProps } from './props';
|
|
@@ -33,12 +34,18 @@ const RobotMap = _ref => {
|
|
|
33
34
|
wayPoints,
|
|
34
35
|
detectedObjects,
|
|
35
36
|
customElements,
|
|
37
|
+
customCarpets,
|
|
38
|
+
furnitures,
|
|
39
|
+
heatmap,
|
|
36
40
|
onMapReady,
|
|
37
41
|
config = {},
|
|
38
42
|
runtime = {}
|
|
39
43
|
} = _ref,
|
|
40
44
|
callbacks = _objectWithoutProperties(_ref, _excluded);
|
|
41
45
|
const [mapApi, setMapApi] = useState(null);
|
|
46
|
+
const prevConfigRef = useRef(null);
|
|
47
|
+
const prevRuntimeRef = useRef(null);
|
|
48
|
+
const hasSkippedInitialConfigUpdateRef = useRef(false);
|
|
42
49
|
const webviewId = useRef(nanoid()).current;
|
|
43
50
|
const webviewContext = useRef(null);
|
|
44
51
|
const invoke = useRef(null);
|
|
@@ -55,7 +62,11 @@ const RobotMap = _ref => {
|
|
|
55
62
|
onReceiveWayPoints: () => {},
|
|
56
63
|
onReceiveDetectedObjects: () => {},
|
|
57
64
|
onReceiveCustomElements: () => {},
|
|
58
|
-
|
|
65
|
+
onReceiveFurnitures: () => {},
|
|
66
|
+
onUpdateConfig: () => {},
|
|
67
|
+
onUpdateRuntime: () => {},
|
|
68
|
+
onReceiveCustomCarpets: () => {},
|
|
69
|
+
onReceiveHeatmap: () => {}
|
|
59
70
|
});
|
|
60
71
|
const handleMessage = useCallback(event => {
|
|
61
72
|
if (invoke.current) {
|
|
@@ -102,7 +113,11 @@ const RobotMap = _ref => {
|
|
|
102
113
|
triggersRef.current.onReceiveWayPoints = invoke.current.bind('onReceiveWayPoints');
|
|
103
114
|
triggersRef.current.onReceiveDetectedObjects = invoke.current.bind('onReceiveDetectedObjects');
|
|
104
115
|
triggersRef.current.onReceiveCustomElements = invoke.current.bind('onReceiveCustomElements');
|
|
116
|
+
triggersRef.current.onReceiveFurnitures = invoke.current.bind('onReceiveFurnitures');
|
|
117
|
+
triggersRef.current.onUpdateConfig = invoke.current.bind('onUpdateConfig');
|
|
105
118
|
triggersRef.current.onUpdateRuntime = invoke.current.bind('onUpdateRuntime');
|
|
119
|
+
triggersRef.current.onReceiveCustomCarpets = invoke.current.bind('onReceiveCustomCarpets');
|
|
120
|
+
triggersRef.current.onReceiveHeatmap = invoke.current.bind('onReceiveHeatmap');
|
|
106
121
|
|
|
107
122
|
// 为每个需要的事件定义分发函数
|
|
108
123
|
Object.keys(CALLBACK_TO_EVENT_MAP).forEach(eventName => {
|
|
@@ -127,6 +142,11 @@ const RobotMap = _ref => {
|
|
|
127
142
|
triggersRef.current.onReceiveMap(map);
|
|
128
143
|
}
|
|
129
144
|
}, [map, mapApi]);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (mapApi && heatmap) {
|
|
147
|
+
triggersRef.current.onReceiveHeatmap(heatmap);
|
|
148
|
+
}
|
|
149
|
+
}, [heatmap, mapApi]);
|
|
130
150
|
useEffect(() => {
|
|
131
151
|
if (mapApi && path) {
|
|
132
152
|
triggersRef.current.onReceivePath(path);
|
|
@@ -177,22 +197,47 @@ const RobotMap = _ref => {
|
|
|
177
197
|
triggersRef.current.onReceiveCustomElements(customElements);
|
|
178
198
|
}
|
|
179
199
|
}, [customElements, mapApi]);
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (mapApi && furnitures) {
|
|
202
|
+
triggersRef.current.onReceiveFurnitures(furnitures);
|
|
203
|
+
}
|
|
204
|
+
}, [furnitures, mapApi]);
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (mapApi && customCarpets) {
|
|
207
|
+
triggersRef.current.onReceiveCustomCarpets(customCarpets);
|
|
208
|
+
}
|
|
209
|
+
}, [customCarpets, mapApi]);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
if (mapApi && config) {
|
|
212
|
+
// 初始化配置由 initApp 注入,这里跳过首次触发,避免重复 updateConfig
|
|
213
|
+
if (!hasSkippedInitialConfigUpdateRef.current) {
|
|
214
|
+
hasSkippedInitialConfigUpdateRef.current = true;
|
|
215
|
+
prevConfigRef.current = config;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 避免相同配置内容重复触发 updateConfig
|
|
220
|
+
if (isEqual(prevConfigRef.current, config)) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
triggersRef.current.onUpdateConfig(config);
|
|
224
|
+
prevConfigRef.current = config;
|
|
225
|
+
}
|
|
226
|
+
}, [config, mapApi]);
|
|
180
227
|
useEffect(() => {
|
|
181
228
|
if (mapApi && runtime) {
|
|
182
229
|
var _runtime$dividingRoom;
|
|
230
|
+
// runtime 需要初始化时下发,因此不跳过首次触发;仅做内容去重
|
|
231
|
+
if (isEqual(prevRuntimeRef.current, runtime)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
183
234
|
triggersRef.current.onUpdateRuntime(_objectSpread(_objectSpread({}, runtime), {}, {
|
|
184
235
|
// 最初版本定义null为不显示分割线,但安卓在message序列化的过程中,null会被序列化为undefined导致丢失,在组件兼容处理成-1
|
|
185
236
|
dividingRoomId: (_runtime$dividingRoom = runtime.dividingRoomId) !== null && _runtime$dividingRoom !== void 0 ? _runtime$dividingRoom : -1
|
|
186
237
|
}));
|
|
238
|
+
prevRuntimeRef.current = runtime;
|
|
187
239
|
}
|
|
188
240
|
}, [runtime, mapApi]);
|
|
189
|
-
useEffect(() => {
|
|
190
|
-
if (mapApi) {
|
|
191
|
-
setTimeout(() => {
|
|
192
|
-
mapApi.getCleanZones();
|
|
193
|
-
}, 2000);
|
|
194
|
-
}
|
|
195
|
-
}, [mapApi]);
|
|
196
241
|
return /*#__PURE__*/React.createElement(WebView, {
|
|
197
242
|
src: "webview://node_modules/@ray-js/robot-map-sdk/dist-app/index.html"
|
|
198
243
|
// src="http://127.0.0.1:3000"
|
package/lib/props.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DeepPartialAppConfig, DeepPartialRuntimeConfig, RoomProperty, ZoneParam, VirtualWallParam, DetectedObjectParam, CustomElementParam, MapState, PathState, Point, RoomData, SpotParam, WayPointParam } from '@ray-js/robot-map-sdk';
|
|
1
|
+
import { DeepPartialAppConfig, DeepPartialRuntimeConfig, RoomProperty, ZoneParam, VirtualWallParam, DetectedObjectParam, CustomElementParam, MapState, PathState, Point, RoomData, SpotParam, WayPointParam, CustomCarpetParam, IconPoint, FurnitureParam } from '@ray-js/robot-map-sdk';
|
|
2
2
|
import { MapApi } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* 机器人地图组件属性接口
|
|
@@ -84,6 +84,20 @@ export interface RobotMapProps {
|
|
|
84
84
|
* @description.en User-defined map elements, which can be images, GIFs, and HTML.
|
|
85
85
|
*/
|
|
86
86
|
customElements?: CustomElementParam[];
|
|
87
|
+
/**
|
|
88
|
+
* @description.zh 自定义地毯
|
|
89
|
+
* @description.en Custom carpets
|
|
90
|
+
* @description.zh 用户自定义的地毯信息,支持图片、GIF和HTML三种格式。
|
|
91
|
+
* @description.en User-defined carpet information, which can be images, GIFs, and HTML.
|
|
92
|
+
*/
|
|
93
|
+
customCarpets?: CustomCarpetParam[];
|
|
94
|
+
/**
|
|
95
|
+
* @description.zh 家具
|
|
96
|
+
* @description.en Furnitures
|
|
97
|
+
* @description.zh 地图上的家具信息,支持编辑、拖拽、旋转和缩放。
|
|
98
|
+
* @description.en Furniture data on the map, supporting edit, drag, rotate and scale.
|
|
99
|
+
*/
|
|
100
|
+
furnitures?: FurnitureParam[];
|
|
87
101
|
/**
|
|
88
102
|
* @description.zh 配置项
|
|
89
103
|
* @description.en Configuration options
|
|
@@ -98,6 +112,13 @@ export interface RobotMapProps {
|
|
|
98
112
|
* @description.en Control the various behavior states of the map at runtime
|
|
99
113
|
*/
|
|
100
114
|
runtime?: DeepPartialRuntimeConfig;
|
|
115
|
+
/**
|
|
116
|
+
* @description.zh 地图热力图点数据
|
|
117
|
+
* @description.en Map heatmap points data
|
|
118
|
+
* @description.zh 地图热力图点数据,用于定义地图热力图的样式和行为等
|
|
119
|
+
* @description.en Map heatmap points data, used to define the style and behavior of the map heatmap
|
|
120
|
+
*/
|
|
121
|
+
heatmap?: string;
|
|
101
122
|
/**
|
|
102
123
|
* @description.zh 地图初始化完成,API准备就绪的回调
|
|
103
124
|
* @description.en Callback when the map is initialized and the API is ready
|
|
@@ -115,6 +136,7 @@ export interface RobotMapProps {
|
|
|
115
136
|
* @param.en mapState - Map state information including map ID, origin, dimensions, etc.
|
|
116
137
|
*/
|
|
117
138
|
onMapFirstDrawed?: (mapState: MapState) => void;
|
|
139
|
+
onCarpetsUpdated?: (carpets: CustomCarpetParam[]) => void;
|
|
118
140
|
/**
|
|
119
141
|
* @description.zh 地图绘制完成回调
|
|
120
142
|
* @description.en Callback when the map is drawn
|
|
@@ -322,6 +344,33 @@ export interface RobotMapProps {
|
|
|
322
344
|
* @param.en element - Clicked custom element data
|
|
323
345
|
*/
|
|
324
346
|
onClickCustomElement?: (element: CustomElementParam) => void;
|
|
347
|
+
/**
|
|
348
|
+
* @description.zh 点击家具回调
|
|
349
|
+
* @description.en Callback when clicking furniture
|
|
350
|
+
* @description.zh 当用户点击家具时触发。
|
|
351
|
+
* @description.en Triggered when user clicks furniture.
|
|
352
|
+
* @param.zh furniture - 被点击的家具数据
|
|
353
|
+
* @param.en furniture - Clicked furniture data
|
|
354
|
+
*/
|
|
355
|
+
onClickFurniture?: (furniture: FurnitureParam) => void;
|
|
356
|
+
/**
|
|
357
|
+
* @description.zh 更新家具回调
|
|
358
|
+
* @description.en Callback when updating furniture
|
|
359
|
+
* @description.zh 当用户操作家具(如拖拽、旋转、缩放)后触发。
|
|
360
|
+
* @description.en Triggered when user updates furniture (such as drag, rotate or scale).
|
|
361
|
+
* @param.zh furniture - 更新后的家具数据
|
|
362
|
+
* @param.en furniture - Updated furniture data
|
|
363
|
+
*/
|
|
364
|
+
onUpdateFurniture?: (furniture: FurnitureParam) => void;
|
|
365
|
+
/**
|
|
366
|
+
* @description.zh 删除家具回调
|
|
367
|
+
* @description.en Callback when removing furniture
|
|
368
|
+
* @description.zh 当用户删除家具时触发。
|
|
369
|
+
* @description.en Triggered when user removes furniture.
|
|
370
|
+
* @param.zh id - 被删除的家具ID
|
|
371
|
+
* @param.en id - ID of removed furniture
|
|
372
|
+
*/
|
|
373
|
+
onRemoveFurniture?: (id: string) => void;
|
|
325
374
|
/**
|
|
326
375
|
* @description.zh 更新分割线回调
|
|
327
376
|
* @description.en Callback when updating divider
|
|
@@ -340,5 +389,37 @@ export interface RobotMapProps {
|
|
|
340
389
|
* @param.en point - Clicked point data
|
|
341
390
|
*/
|
|
342
391
|
onClickMap?: (point: Point) => void;
|
|
392
|
+
/**
|
|
393
|
+
* @description.zh 点击机器人回调
|
|
394
|
+
* @description.en Callback when clicking robot
|
|
395
|
+
* @description.zh 当用户点击机器人时触发。
|
|
396
|
+
* @description.en Triggered when user clicks robot.
|
|
397
|
+
* @param.zh robot - 被点击的机器人数据
|
|
398
|
+
* @param.en robot - Clicked robot data
|
|
399
|
+
*/
|
|
400
|
+
onClickRobot?: (robot: IconPoint) => void;
|
|
401
|
+
/**
|
|
402
|
+
* @description.zh 点击充电桩回调
|
|
403
|
+
* @description.en Callback when clicking charging station
|
|
404
|
+
* @description.zh 当用户点击充电桩时触发。
|
|
405
|
+
* @description.en Triggered when user clicks charging station.
|
|
406
|
+
* @param.zh chargingStation - 被点击的充电桩数据
|
|
407
|
+
* @param.en chargingStation - Clicked charging station data
|
|
408
|
+
*/
|
|
409
|
+
onClickChargingStation?: (chargingStation: IconPoint) => void;
|
|
410
|
+
/**
|
|
411
|
+
* @description.zh 手势开始回调
|
|
412
|
+
* @description.en Callback when gesture starts
|
|
413
|
+
* @description.zh 当用户开始手势操作时触发。
|
|
414
|
+
* @description.en Triggered when user starts gesture operation.
|
|
415
|
+
*/
|
|
416
|
+
onGestureStart?: () => void;
|
|
417
|
+
/**
|
|
418
|
+
* @description.zh 手势结束回调
|
|
419
|
+
* @description.en Callback when gesture ends
|
|
420
|
+
* @description.zh 当用户结束手势操作时触发。
|
|
421
|
+
* @description.en Triggered when user ends gesture operation.
|
|
422
|
+
*/
|
|
423
|
+
onGestureEnd?: () => void;
|
|
343
424
|
}
|
|
344
425
|
export declare const robotMapDefaultProps: RobotMapProps;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ray-js/robot-map",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7-beta.1",
|
|
4
4
|
"description": "机器人地图组件",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"files": [
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@ray-js/ray": "^1.7.39"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@ray-js/robot-map-sdk": "0.0.
|
|
36
|
+
"@ray-js/robot-map-sdk": "0.0.14-beta.1",
|
|
37
37
|
"clsx": "^1.2.1",
|
|
38
38
|
"nanoid": "^5.1.6"
|
|
39
39
|
},
|