@mpxjs/webpack-plugin 2.9.69-beta.6 → 2.9.69-beta.8
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/config.js +3 -1
- package/lib/platform/template/wx/index.js +3 -1
- package/lib/react/processScript.js +5 -3
- package/lib/runtime/components/react/dist/mpx-swiper.jsx +25 -25
- package/lib/runtime/components/react/dist/mpx-web-view.jsx +64 -33
- package/lib/runtime/components/react/dist/useAnimationHooks.js +4 -4
- package/lib/runtime/components/react/mpx-swiper.tsx +29 -30
- package/lib/runtime/components/react/mpx-web-view.tsx +77 -45
- package/lib/runtime/components/react/useAnimationHooks.ts +4 -4
- package/package.json +1 -1
package/lib/config.js
CHANGED
|
@@ -138,7 +138,9 @@ module.exports = {
|
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
140
|
getEvent (eventName, prefix = 'on') {
|
|
141
|
-
return
|
|
141
|
+
return prefix + dash2hump(eventName.replace(/^./, (matched) => {
|
|
142
|
+
return matched.toUpperCase()
|
|
143
|
+
}))
|
|
142
144
|
},
|
|
143
145
|
defaultModelProp: 'value',
|
|
144
146
|
defaultModelEvent: 'input',
|
|
@@ -302,7 +302,9 @@ module.exports = function getSpec ({ warn, error }) {
|
|
|
302
302
|
const rPrefix = runRules(spec.event.prefix, prefix, { mode: 'ali' })
|
|
303
303
|
const rEventName = runRules(eventRules, eventName, { mode: 'ali' })
|
|
304
304
|
return {
|
|
305
|
-
name:
|
|
305
|
+
name: rPrefix + dash2hump(rEventName.replace(/^./, (matched) => {
|
|
306
|
+
return matched.toUpperCase()
|
|
307
|
+
})) + modifierStr,
|
|
306
308
|
value
|
|
307
309
|
}
|
|
308
310
|
},
|
|
@@ -14,25 +14,27 @@ module.exports = function (script, {
|
|
|
14
14
|
localPagesMap
|
|
15
15
|
}, callback) {
|
|
16
16
|
let scriptSrcMode = srcMode
|
|
17
|
+
const mode = loaderContext.getMpx().mode
|
|
17
18
|
if (script) {
|
|
18
19
|
scriptSrcMode = script.mode || scriptSrcMode
|
|
19
20
|
} else {
|
|
20
21
|
script = { tag: 'script' }
|
|
21
22
|
}
|
|
22
|
-
|
|
23
23
|
let output = '/* script */\n'
|
|
24
24
|
if (ctorType === 'app') {
|
|
25
25
|
output += `
|
|
26
26
|
import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPath)}
|
|
27
27
|
import { NavigationContainer, StackActions } from '@react-navigation/native'
|
|
28
|
-
import { createStackNavigator } from '@react-navigation/stack'
|
|
28
|
+
${mode === 'ios' ? "import { createNativeStackNavigator } from '@react-navigation/native-stack'" : "import { createStackNavigator } from '@react-navigation/stack'" }
|
|
29
|
+
import { useHeaderHeight } from '@react-navigation/elements';
|
|
29
30
|
import Provider from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-provider'
|
|
30
31
|
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
31
32
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
32
33
|
|
|
33
34
|
global.__navigationHelper = {
|
|
34
35
|
NavigationContainer: NavigationContainer,
|
|
35
|
-
createStackNavigator: createStackNavigator,
|
|
36
|
+
createStackNavigator: ${mode === 'ios' ? 'createNativeStackNavigator' : 'createStackNavigator'},
|
|
37
|
+
useHeaderHeight: useHeaderHeight,
|
|
36
38
|
StackActions: StackActions,
|
|
37
39
|
GestureHandlerRootView: GestureHandlerRootView,
|
|
38
40
|
Provider: Provider,
|
|
@@ -63,14 +63,14 @@ const activeDotStyle = {
|
|
|
63
63
|
};
|
|
64
64
|
const longPressRatio = 100;
|
|
65
65
|
const easeMap = {
|
|
66
|
-
default: Easing.
|
|
66
|
+
default: Easing.inOut(Easing.poly(3)),
|
|
67
67
|
linear: Easing.linear,
|
|
68
68
|
easeInCubic: Easing.in(Easing.cubic),
|
|
69
69
|
easeOutCubic: Easing.out(Easing.cubic),
|
|
70
70
|
easeInOutCubic: Easing.inOut(Easing.cubic)
|
|
71
71
|
};
|
|
72
72
|
const SwiperWrapper = forwardRef((props, ref) => {
|
|
73
|
-
const { 'indicator-dots': showsPagination, 'indicator-color': dotColor = 'rgba(0, 0, 0, .3)', 'indicator-active-color': activeDotColor = '#000000', 'enable-var': enableVar = false, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'external-var-context': externalVarContext, style = {}, autoplay, circular } = props;
|
|
73
|
+
const { 'indicator-dots': showsPagination, 'indicator-color': dotColor = 'rgba(0, 0, 0, .3)', 'indicator-active-color': activeDotColor = '#000000', 'enable-var': enableVar = false, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'external-var-context': externalVarContext, style = {}, autoplay = false, circular = false } = props;
|
|
74
74
|
const easeingFunc = props['easing-function'] || 'default';
|
|
75
75
|
const easeDuration = props.duration || 500;
|
|
76
76
|
const horizontal = props.vertical !== undefined ? !props.vertical : true;
|
|
@@ -86,15 +86,15 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
86
86
|
});
|
|
87
87
|
const { textStyle } = splitStyle(normalStyle);
|
|
88
88
|
const { textProps } = splitProps(props);
|
|
89
|
-
const preMargin = props['previous-margin'] ?
|
|
90
|
-
const nextMargin = props['next-margin'] ?
|
|
91
|
-
const
|
|
89
|
+
const preMargin = props['previous-margin'] ? global.__formatValue(props['previous-margin']) : 0;
|
|
90
|
+
const nextMargin = props['next-margin'] ? global.__formatValue(props['next-margin']) : 0;
|
|
91
|
+
const preMarginShared = useSharedValue(preMargin);
|
|
92
92
|
const nextMarginShared = useSharedValue(nextMargin);
|
|
93
|
-
const autoplayShared = useSharedValue(autoplay
|
|
93
|
+
const autoplayShared = useSharedValue(autoplay);
|
|
94
94
|
// 默认前后补位的元素个数
|
|
95
95
|
const patchElmNum = circular ? (preMargin ? 2 : 1) : 0;
|
|
96
96
|
const patchElmNumShared = useSharedValue(patchElmNum);
|
|
97
|
-
const circularShared = useSharedValue(circular
|
|
97
|
+
const circularShared = useSharedValue(circular);
|
|
98
98
|
const children = Array.isArray(props.children) ? props.children.filter(child => child) : (props.children ? [props.children] : []);
|
|
99
99
|
// 对有变化的变量,在worklet中只能使用sharedValue变量,useRef不能更新
|
|
100
100
|
const childrenLength = useSharedValue(children.length);
|
|
@@ -245,7 +245,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
245
245
|
return;
|
|
246
246
|
}
|
|
247
247
|
nextIndex += 1;
|
|
248
|
-
// targetOffset = -nextIndex * step.value -
|
|
248
|
+
// targetOffset = -nextIndex * step.value - preMarginShared.value
|
|
249
249
|
targetOffset = -nextIndex * step.value;
|
|
250
250
|
offset.value = withTiming(targetOffset, {
|
|
251
251
|
duration: easeDuration,
|
|
@@ -259,12 +259,12 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
259
259
|
// 默认向右, 向下
|
|
260
260
|
if (nextIndex === childrenLength.value - 1) {
|
|
261
261
|
nextIndex = 0;
|
|
262
|
-
targetOffset = -(childrenLength.value + patchElmNumShared.value) * step.value +
|
|
262
|
+
targetOffset = -(childrenLength.value + patchElmNumShared.value) * step.value + preMarginShared.value;
|
|
263
263
|
// 执行动画到下一帧
|
|
264
264
|
offset.value = withTiming(targetOffset, {
|
|
265
265
|
duration: easeDuration
|
|
266
266
|
}, () => {
|
|
267
|
-
const initOffset = -step.value * patchElmNumShared.value +
|
|
267
|
+
const initOffset = -step.value * patchElmNumShared.value + preMarginShared.value;
|
|
268
268
|
// 将开始位置设置为真正的位置
|
|
269
269
|
offset.value = initOffset;
|
|
270
270
|
currentIndex.value = nextIndex;
|
|
@@ -273,7 +273,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
273
273
|
}
|
|
274
274
|
else {
|
|
275
275
|
nextIndex = currentIndex.value + 1;
|
|
276
|
-
targetOffset = -(nextIndex + patchElmNumShared.value) * step.value +
|
|
276
|
+
targetOffset = -(nextIndex + patchElmNumShared.value) * step.value + preMarginShared.value;
|
|
277
277
|
// 执行动画到下一帧
|
|
278
278
|
offset.value = withTiming(targetOffset, {
|
|
279
279
|
duration: easeDuration,
|
|
@@ -358,13 +358,13 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
358
358
|
});
|
|
359
359
|
useEffect(() => {
|
|
360
360
|
let patchStep = 0;
|
|
361
|
-
if (preMargin !==
|
|
362
|
-
patchStep += preMargin -
|
|
361
|
+
if (preMargin !== preMarginShared.value) {
|
|
362
|
+
patchStep += preMargin - preMarginShared.value;
|
|
363
363
|
}
|
|
364
364
|
if (nextMargin !== nextMarginShared.value) {
|
|
365
365
|
patchStep += nextMargin - nextMarginShared.value;
|
|
366
366
|
}
|
|
367
|
-
|
|
367
|
+
preMarginShared.value = preMargin;
|
|
368
368
|
nextMarginShared.value = nextMargin;
|
|
369
369
|
const newStep = step.value - patchStep;
|
|
370
370
|
if (step.value !== newStep) {
|
|
@@ -387,7 +387,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
387
387
|
updateCurrent(props.current || 0, step.value);
|
|
388
388
|
}, [props.current]);
|
|
389
389
|
useEffect(() => {
|
|
390
|
-
autoplayShared.value = autoplay
|
|
390
|
+
autoplayShared.value = autoplay;
|
|
391
391
|
updateAutoplay();
|
|
392
392
|
return () => {
|
|
393
393
|
if (autoplay) {
|
|
@@ -397,7 +397,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
397
397
|
}, [autoplay]);
|
|
398
398
|
useEffect(() => {
|
|
399
399
|
if (circular !== circularShared.value) {
|
|
400
|
-
circularShared.value = circular
|
|
400
|
+
circularShared.value = circular;
|
|
401
401
|
patchElmNumShared.value = circular ? (preMargin ? 2 : 1) : 0;
|
|
402
402
|
offset.value = getOffset(currentIndex.value, step.value);
|
|
403
403
|
}
|
|
@@ -413,7 +413,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
413
413
|
let isCriticalItem = false;
|
|
414
414
|
// 真实滚动到的偏移量坐标
|
|
415
415
|
let moveToTargetPos = 0;
|
|
416
|
-
const currentOffset = translation < 0 ? offset.value -
|
|
416
|
+
const currentOffset = translation < 0 ? offset.value - preMarginShared.value : offset.value + preMarginShared.value;
|
|
417
417
|
const computedIndex = Math.abs(currentOffset) / step.value;
|
|
418
418
|
const moveToIndex = translation < 0 ? Math.ceil(computedIndex) : Math.floor(computedIndex);
|
|
419
419
|
// 实际应该定位的索引值
|
|
@@ -424,19 +424,19 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
424
424
|
else {
|
|
425
425
|
if (moveToIndex >= childrenLength.value + patchElmNumShared.value) {
|
|
426
426
|
selectedIndex = moveToIndex - (childrenLength.value + patchElmNumShared.value);
|
|
427
|
-
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value -
|
|
428
|
-
moveToTargetPos = moveToIndex * step.value -
|
|
427
|
+
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value - preMarginShared.value;
|
|
428
|
+
moveToTargetPos = moveToIndex * step.value - preMarginShared.value;
|
|
429
429
|
isCriticalItem = true;
|
|
430
430
|
}
|
|
431
431
|
else if (moveToIndex <= patchElmNumShared.value - 1) {
|
|
432
432
|
selectedIndex = moveToIndex === 0 ? childrenLength.value - patchElmNumShared.value : childrenLength.value - 1;
|
|
433
|
-
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value -
|
|
434
|
-
moveToTargetPos = moveToIndex * step.value -
|
|
433
|
+
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value - preMarginShared.value;
|
|
434
|
+
moveToTargetPos = moveToIndex * step.value - preMarginShared.value;
|
|
435
435
|
isCriticalItem = true;
|
|
436
436
|
}
|
|
437
437
|
else {
|
|
438
438
|
selectedIndex = moveToIndex - patchElmNumShared.value;
|
|
439
|
-
moveToTargetPos = moveToIndex * step.value -
|
|
439
|
+
moveToTargetPos = moveToIndex * step.value - preMarginShared.value;
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
442
|
return {
|
|
@@ -495,11 +495,11 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
495
495
|
// 向右滑动的back:trans < 0, 向左滑动的back: trans < 0
|
|
496
496
|
let currentOffset = Math.abs(offset.value);
|
|
497
497
|
if (circularShared.value) {
|
|
498
|
-
currentOffset += translation < 0 ?
|
|
498
|
+
currentOffset += translation < 0 ? preMarginShared.value : -preMarginShared.value;
|
|
499
499
|
}
|
|
500
500
|
const curIndex = currentOffset / step.value;
|
|
501
501
|
const moveToIndex = (translation < 0 ? Math.floor(curIndex) : Math.ceil(curIndex)) - patchElmNumShared.value;
|
|
502
|
-
const targetOffset = -(moveToIndex + patchElmNumShared.value) * step.value + (circularShared.value ?
|
|
502
|
+
const targetOffset = -(moveToIndex + patchElmNumShared.value) * step.value + (circularShared.value ? preMarginShared.value : 0);
|
|
503
503
|
offset.value = withTiming(targetOffset, {
|
|
504
504
|
duration: easeDuration,
|
|
505
505
|
easing: easeMap[easeingFunc]
|
|
@@ -515,7 +515,7 @@ const SwiperWrapper = forwardRef((props, ref) => {
|
|
|
515
515
|
const currentOffset = Math.abs(offset.value);
|
|
516
516
|
let preOffset = (currentIndex.value + patchElmNumShared.value) * step.value;
|
|
517
517
|
if (circularShared.value) {
|
|
518
|
-
preOffset -=
|
|
518
|
+
preOffset -= preMarginShared.value;
|
|
519
519
|
}
|
|
520
520
|
// 正常事件中拿到的transition值(正向滑动<0,倒着滑>0)
|
|
521
521
|
const diffOffset = preOffset - currentOffset;
|
|
@@ -5,7 +5,7 @@ import { getCustomEvent } from './getInnerListeners';
|
|
|
5
5
|
import { promisify, redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy';
|
|
6
6
|
import { WebView } from 'react-native-webview';
|
|
7
7
|
import useNodesRef from './useNodesRef';
|
|
8
|
-
import { getCurrentPage
|
|
8
|
+
import { getCurrentPage } from './utils';
|
|
9
9
|
import { useNavigation } from '@react-navigation/native';
|
|
10
10
|
import { RouteContext } from './context';
|
|
11
11
|
import { BackHandler, StyleSheet, View, Text } from 'react-native';
|
|
@@ -54,6 +54,8 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
54
54
|
const [pageLoadErr, setPageLoadErr] = useState(false);
|
|
55
55
|
const currentPage = useMemo(() => getCurrentPage(pageId), [pageId]);
|
|
56
56
|
const webViewRef = useRef(null);
|
|
57
|
+
const [isLoaded, setIsLoaded] = useState(true);
|
|
58
|
+
const fristLoaded = useRef(false);
|
|
57
59
|
const defaultWebViewStyle = {
|
|
58
60
|
position: 'absolute',
|
|
59
61
|
left: 0,
|
|
@@ -99,28 +101,10 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
99
101
|
if (!src) {
|
|
100
102
|
return null;
|
|
101
103
|
}
|
|
102
|
-
const _load = function (res) {
|
|
103
|
-
const result = {
|
|
104
|
-
type: 'load',
|
|
105
|
-
timeStamp: res.timeStamp,
|
|
106
|
-
detail: {
|
|
107
|
-
src: res.nativeEvent?.url
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
bindload?.(result);
|
|
111
|
-
};
|
|
112
|
-
const _error = function (res) {
|
|
113
|
-
setPageLoadErr(true);
|
|
114
|
-
const result = {
|
|
115
|
-
type: 'error',
|
|
116
|
-
timeStamp: res.timeStamp,
|
|
117
|
-
detail: {
|
|
118
|
-
src: ''
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
binderror && binderror(result);
|
|
122
|
-
};
|
|
123
104
|
const _reload = function () {
|
|
105
|
+
if (__mpx_mode__ === 'android') {
|
|
106
|
+
fristLoaded.current = false; // 安卓需要重新设置
|
|
107
|
+
}
|
|
124
108
|
setPageLoadErr(false);
|
|
125
109
|
};
|
|
126
110
|
const injectedJavaScript = `
|
|
@@ -151,7 +135,7 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
151
135
|
`;
|
|
152
136
|
const sendMessage = function (params) {
|
|
153
137
|
return `
|
|
154
|
-
window.mpxWebviewMessageCallback(${params})
|
|
138
|
+
window.mpxWebviewMessageCallback && window.mpxWebviewMessageCallback(${params})
|
|
155
139
|
true;
|
|
156
140
|
`;
|
|
157
141
|
};
|
|
@@ -251,22 +235,69 @@ const _WebView = forwardRef((props, ref) => {
|
|
|
251
235
|
}
|
|
252
236
|
});
|
|
253
237
|
};
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
238
|
+
let isLoadError = false;
|
|
239
|
+
let statusCode = '';
|
|
240
|
+
const onLoadEndHandle = function (res) {
|
|
241
|
+
fristLoaded.current = true;
|
|
242
|
+
setIsLoaded(true);
|
|
243
|
+
const src = res.nativeEvent?.url;
|
|
244
|
+
if (isLoadError) {
|
|
245
|
+
isLoadError = false;
|
|
246
|
+
isNavigateBack.current = false;
|
|
247
|
+
const result = {
|
|
248
|
+
type: 'error',
|
|
249
|
+
timeStamp: res.timeStamp,
|
|
250
|
+
detail: {
|
|
251
|
+
src,
|
|
252
|
+
statusCode
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
binderror && binderror(result);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const result = {
|
|
259
|
+
type: 'load',
|
|
260
|
+
timeStamp: res.timeStamp,
|
|
261
|
+
detail: {
|
|
262
|
+
src
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
bindload?.(result);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const onLoadEnd = function (res) {
|
|
269
|
+
if (__mpx_mode__ === 'android') {
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
onLoadEndHandle(res);
|
|
272
|
+
}, 0);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
onLoadEndHandle(res);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
const onHttpError = function (res) {
|
|
279
|
+
isLoadError = true;
|
|
280
|
+
statusCode = res.nativeEvent?.statusCode;
|
|
281
|
+
};
|
|
282
|
+
const onError = function () {
|
|
283
|
+
statusCode = '';
|
|
284
|
+
isLoadError = true;
|
|
285
|
+
if (!fristLoaded.current) {
|
|
286
|
+
setPageLoadErr(true);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const onLoadStart = function () {
|
|
290
|
+
if (!fristLoaded.current) {
|
|
291
|
+
setIsLoaded(false);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
263
294
|
return (<Portal key={pageLoadErr ? 'error' : 'webview'}>
|
|
264
295
|
{pageLoadErr
|
|
265
296
|
? (<View style={[styles.loadErrorContext, defaultWebViewStyle]}>
|
|
266
297
|
<View style={styles.loadErrorText}><Text style={{ fontSize: 14, color: '#999999' }}>{currentErrorText.text}</Text></View>
|
|
267
298
|
<View style={styles.loadErrorButton} onTouchEnd={_reload}><Text style={{ fontSize: 12, color: '#666666' }}>{currentErrorText.button}</Text></View>
|
|
268
299
|
</View>)
|
|
269
|
-
: (<WebView style={defaultWebViewStyle} source={{ uri: src }} ref={webViewRef} javaScriptEnabled={true} onNavigationStateChange={_changeUrl} onMessage={_message} injectedJavaScript={injectedJavaScript} onLoadProgress={_onLoadProgress}
|
|
300
|
+
: (<WebView style={defaultWebViewStyle} source={{ uri: src }} pointerEvents={isLoaded ? 'auto' : 'none'} ref={webViewRef} javaScriptEnabled={true} onNavigationStateChange={_changeUrl} onMessage={_message} injectedJavaScript={injectedJavaScript} onLoadProgress={_onLoadProgress} onLoadEnd={onLoadEnd} onHttpError={onHttpError} onError={onError} onLoadStart={onLoadStart} allowsBackForwardNavigationGestures={true}></WebView>)}
|
|
270
301
|
</Portal>);
|
|
271
302
|
});
|
|
272
303
|
_WebView.displayName = 'MpxWebview';
|
|
@@ -4,10 +4,10 @@ import { error } from '@mpxjs/utils';
|
|
|
4
4
|
// 微信 timingFunction 和 RN Easing 对应关系
|
|
5
5
|
const EasingKey = {
|
|
6
6
|
linear: Easing.linear,
|
|
7
|
-
ease: Easing.ease,
|
|
8
|
-
'ease-in': Easing.in(Easing.
|
|
9
|
-
'ease-in-out': Easing.inOut(Easing.
|
|
10
|
-
'ease-out': Easing.out(Easing.
|
|
7
|
+
ease: Easing.inOut(Easing.ease),
|
|
8
|
+
'ease-in': Easing.in(Easing.poly(3)),
|
|
9
|
+
'ease-in-out': Easing.inOut(Easing.poly(3)),
|
|
10
|
+
'ease-out': Easing.out(Easing.poly(3))
|
|
11
11
|
// 'step-start': '',
|
|
12
12
|
// 'step-end': ''
|
|
13
13
|
};
|
|
@@ -2,7 +2,7 @@ import { View, NativeSyntheticEvent, LayoutChangeEvent } from 'react-native'
|
|
|
2
2
|
import { GestureDetector, Gesture } from 'react-native-gesture-handler'
|
|
3
3
|
import Animated, { useAnimatedStyle, useSharedValue, withTiming, Easing, runOnJS, useAnimatedReaction, cancelAnimation } from 'react-native-reanimated'
|
|
4
4
|
|
|
5
|
-
import React, { JSX, forwardRef, useRef, useEffect, ReactNode, ReactElement,
|
|
5
|
+
import React, { JSX, forwardRef, useRef, useEffect, ReactNode, ReactElement, useMemo } from 'react'
|
|
6
6
|
import useInnerProps, { getCustomEvent } from './getInnerListeners'
|
|
7
7
|
import useNodesRef, { HandlerRef } from './useNodesRef' // 引入辅助函数
|
|
8
8
|
import { useTransformStyle, splitStyle, splitProps, useLayout, wrapChildren } from './utils'
|
|
@@ -117,7 +117,7 @@ const activeDotStyle = {
|
|
|
117
117
|
const longPressRatio = 100
|
|
118
118
|
|
|
119
119
|
const easeMap = {
|
|
120
|
-
default: Easing.
|
|
120
|
+
default: Easing.inOut(Easing.poly(3)),
|
|
121
121
|
linear: Easing.linear,
|
|
122
122
|
easeInCubic: Easing.in(Easing.cubic),
|
|
123
123
|
easeOutCubic: Easing.out(Easing.cubic),
|
|
@@ -135,8 +135,8 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
135
135
|
'parent-height': parentHeight,
|
|
136
136
|
'external-var-context': externalVarContext,
|
|
137
137
|
style = {},
|
|
138
|
-
autoplay,
|
|
139
|
-
circular
|
|
138
|
+
autoplay = false,
|
|
139
|
+
circular = false
|
|
140
140
|
} = props
|
|
141
141
|
const easeingFunc = props['easing-function'] || 'default'
|
|
142
142
|
const easeDuration = props.duration || 500
|
|
@@ -160,15 +160,15 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
160
160
|
})
|
|
161
161
|
const { textStyle } = splitStyle(normalStyle)
|
|
162
162
|
const { textProps } = splitProps(props)
|
|
163
|
-
const preMargin = props['previous-margin'] ?
|
|
164
|
-
const nextMargin = props['next-margin'] ?
|
|
165
|
-
const
|
|
163
|
+
const preMargin = props['previous-margin'] ? global.__formatValue(props['previous-margin']) as number : 0
|
|
164
|
+
const nextMargin = props['next-margin'] ? global.__formatValue(props['next-margin']) as number : 0
|
|
165
|
+
const preMarginShared = useSharedValue(preMargin)
|
|
166
166
|
const nextMarginShared = useSharedValue(nextMargin)
|
|
167
|
-
const autoplayShared = useSharedValue(autoplay
|
|
167
|
+
const autoplayShared = useSharedValue(autoplay)
|
|
168
168
|
// 默认前后补位的元素个数
|
|
169
169
|
const patchElmNum = circular ? (preMargin ? 2 : 1) : 0
|
|
170
170
|
const patchElmNumShared = useSharedValue(patchElmNum)
|
|
171
|
-
const circularShared = useSharedValue(circular
|
|
171
|
+
const circularShared = useSharedValue(circular)
|
|
172
172
|
const children = Array.isArray(props.children) ? props.children.filter(child => child) : (props.children ? [props.children] : [])
|
|
173
173
|
// 对有变化的变量,在worklet中只能使用sharedValue变量,useRef不能更新
|
|
174
174
|
const childrenLength = useSharedValue(children.length)
|
|
@@ -312,7 +312,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
312
312
|
return (<SwiperContext.Provider value={contextValue}>{arrChildren}</SwiperContext.Provider>)
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
const { loop, pauseLoop, resumeLoop} = useMemo(() => {
|
|
315
|
+
const { loop, pauseLoop, resumeLoop } = useMemo(() => {
|
|
316
316
|
function createAutoPlay () {
|
|
317
317
|
if (!step.value) return
|
|
318
318
|
let targetOffset = 0
|
|
@@ -324,7 +324,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
324
324
|
return
|
|
325
325
|
}
|
|
326
326
|
nextIndex += 1
|
|
327
|
-
// targetOffset = -nextIndex * step.value -
|
|
327
|
+
// targetOffset = -nextIndex * step.value - preMarginShared.value
|
|
328
328
|
targetOffset = -nextIndex * step.value
|
|
329
329
|
offset.value = withTiming(targetOffset, {
|
|
330
330
|
duration: easeDuration,
|
|
@@ -337,12 +337,12 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
337
337
|
// 默认向右, 向下
|
|
338
338
|
if (nextIndex === childrenLength.value - 1) {
|
|
339
339
|
nextIndex = 0
|
|
340
|
-
targetOffset = -(childrenLength.value + patchElmNumShared.value) * step.value +
|
|
340
|
+
targetOffset = -(childrenLength.value + patchElmNumShared.value) * step.value + preMarginShared.value
|
|
341
341
|
// 执行动画到下一帧
|
|
342
342
|
offset.value = withTiming(targetOffset, {
|
|
343
343
|
duration: easeDuration
|
|
344
344
|
}, () => {
|
|
345
|
-
const initOffset = -step.value * patchElmNumShared.value +
|
|
345
|
+
const initOffset = -step.value * patchElmNumShared.value + preMarginShared.value
|
|
346
346
|
// 将开始位置设置为真正的位置
|
|
347
347
|
offset.value = initOffset
|
|
348
348
|
currentIndex.value = nextIndex
|
|
@@ -350,7 +350,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
350
350
|
})
|
|
351
351
|
} else {
|
|
352
352
|
nextIndex = currentIndex.value + 1
|
|
353
|
-
targetOffset = -(nextIndex + patchElmNumShared.value) * step.value +
|
|
353
|
+
targetOffset = -(nextIndex + patchElmNumShared.value) * step.value + preMarginShared.value
|
|
354
354
|
// 执行动画到下一帧
|
|
355
355
|
offset.value = withTiming(targetOffset, {
|
|
356
356
|
duration: easeDuration,
|
|
@@ -437,13 +437,13 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
437
437
|
|
|
438
438
|
useEffect(() => {
|
|
439
439
|
let patchStep = 0
|
|
440
|
-
if (preMargin !==
|
|
441
|
-
patchStep += preMargin -
|
|
440
|
+
if (preMargin !== preMarginShared.value) {
|
|
441
|
+
patchStep += preMargin - preMarginShared.value
|
|
442
442
|
}
|
|
443
443
|
if (nextMargin !== nextMarginShared.value) {
|
|
444
444
|
patchStep += nextMargin - nextMarginShared.value
|
|
445
445
|
}
|
|
446
|
-
|
|
446
|
+
preMarginShared.value = preMargin
|
|
447
447
|
nextMarginShared.value = nextMargin
|
|
448
448
|
const newStep = step.value - patchStep
|
|
449
449
|
if (step.value !== newStep) {
|
|
@@ -469,7 +469,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
469
469
|
}, [props.current])
|
|
470
470
|
|
|
471
471
|
useEffect(() => {
|
|
472
|
-
autoplayShared.value = autoplay
|
|
472
|
+
autoplayShared.value = autoplay
|
|
473
473
|
updateAutoplay()
|
|
474
474
|
return () => {
|
|
475
475
|
if (autoplay) {
|
|
@@ -480,7 +480,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
480
480
|
|
|
481
481
|
useEffect(() => {
|
|
482
482
|
if (circular !== circularShared.value) {
|
|
483
|
-
circularShared.value = circular
|
|
483
|
+
circularShared.value = circular
|
|
484
484
|
patchElmNumShared.value = circular ? (preMargin ? 2 : 1) : 0
|
|
485
485
|
offset.value = getOffset(currentIndex.value, step.value)
|
|
486
486
|
}
|
|
@@ -497,7 +497,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
497
497
|
let isCriticalItem = false
|
|
498
498
|
// 真实滚动到的偏移量坐标
|
|
499
499
|
let moveToTargetPos = 0
|
|
500
|
-
const currentOffset = translation < 0 ? offset.value -
|
|
500
|
+
const currentOffset = translation < 0 ? offset.value - preMarginShared.value : offset.value + preMarginShared.value
|
|
501
501
|
const computedIndex = Math.abs(currentOffset) / step.value
|
|
502
502
|
const moveToIndex = translation < 0 ? Math.ceil(computedIndex) : Math.floor(computedIndex)
|
|
503
503
|
// 实际应该定位的索引值
|
|
@@ -507,17 +507,17 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
507
507
|
} else {
|
|
508
508
|
if (moveToIndex >= childrenLength.value + patchElmNumShared.value) {
|
|
509
509
|
selectedIndex = moveToIndex - (childrenLength.value + patchElmNumShared.value)
|
|
510
|
-
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value -
|
|
511
|
-
moveToTargetPos = moveToIndex * step.value -
|
|
510
|
+
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value - preMarginShared.value
|
|
511
|
+
moveToTargetPos = moveToIndex * step.value - preMarginShared.value
|
|
512
512
|
isCriticalItem = true
|
|
513
513
|
} else if (moveToIndex <= patchElmNumShared.value - 1) {
|
|
514
514
|
selectedIndex = moveToIndex === 0 ? childrenLength.value - patchElmNumShared.value : childrenLength.value - 1
|
|
515
|
-
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value -
|
|
516
|
-
moveToTargetPos = moveToIndex * step.value -
|
|
515
|
+
resetOffsetPos = (selectedIndex + patchElmNumShared.value) * step.value - preMarginShared.value
|
|
516
|
+
moveToTargetPos = moveToIndex * step.value - preMarginShared.value
|
|
517
517
|
isCriticalItem = true
|
|
518
518
|
} else {
|
|
519
519
|
selectedIndex = moveToIndex - patchElmNumShared.value
|
|
520
|
-
moveToTargetPos = moveToIndex * step.value -
|
|
520
|
+
moveToTargetPos = moveToIndex * step.value - preMarginShared.value
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
523
|
return {
|
|
@@ -573,11 +573,11 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
573
573
|
// 向右滑动的back:trans < 0, 向左滑动的back: trans < 0
|
|
574
574
|
let currentOffset = Math.abs(offset.value)
|
|
575
575
|
if (circularShared.value) {
|
|
576
|
-
currentOffset += translation < 0 ?
|
|
576
|
+
currentOffset += translation < 0 ? preMarginShared.value : -preMarginShared.value
|
|
577
577
|
}
|
|
578
578
|
const curIndex = currentOffset / step.value
|
|
579
579
|
const moveToIndex = (translation < 0 ? Math.floor(curIndex) : Math.ceil(curIndex)) - patchElmNumShared.value
|
|
580
|
-
const targetOffset = -(moveToIndex + patchElmNumShared.value) * step.value + (circularShared.value ?
|
|
580
|
+
const targetOffset = -(moveToIndex + patchElmNumShared.value) * step.value + (circularShared.value ? preMarginShared.value : 0)
|
|
581
581
|
offset.value = withTiming(targetOffset, {
|
|
582
582
|
duration: easeDuration,
|
|
583
583
|
easing: easeMap[easeingFunc]
|
|
@@ -593,7 +593,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
593
593
|
const currentOffset = Math.abs(offset.value)
|
|
594
594
|
let preOffset = (currentIndex.value + patchElmNumShared.value) * step.value
|
|
595
595
|
if (circularShared.value) {
|
|
596
|
-
preOffset -=
|
|
596
|
+
preOffset -= preMarginShared.value
|
|
597
597
|
}
|
|
598
598
|
// 正常事件中拿到的transition值(正向滑动<0,倒着滑>0)
|
|
599
599
|
const diffOffset = preOffset - currentOffset
|
|
@@ -611,7 +611,6 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
611
611
|
// 移动的距离
|
|
612
612
|
const { translation } = eventData
|
|
613
613
|
const elementsLength = step.value * childrenLength.value
|
|
614
|
-
|
|
615
614
|
let isBoundary = false
|
|
616
615
|
let resetOffset = 0
|
|
617
616
|
// Y轴向下滚动, transDistance > 0, 向上滚动 < 0 X轴向左滚动, transDistance > 0
|
|
@@ -678,7 +677,7 @@ const SwiperWrapper = forwardRef<HandlerRef<View, SwiperProps>, SwiperProps>((pr
|
|
|
678
677
|
})
|
|
679
678
|
.onTouchesUp((e) => {
|
|
680
679
|
'worklet'
|
|
681
|
-
if(touchfinish.value) return
|
|
680
|
+
if (touchfinish.value) return
|
|
682
681
|
const touchEventData = e.changedTouches[0]
|
|
683
682
|
const moveDistance = touchEventData[strAbso] - moveTranstion.value
|
|
684
683
|
touchfinish.value = true
|
|
@@ -5,8 +5,8 @@ import { getCustomEvent } from './getInnerListeners'
|
|
|
5
5
|
import { promisify, redirectTo, navigateTo, navigateBack, reLaunch, switchTab } from '@mpxjs/api-proxy'
|
|
6
6
|
import { WebView } from 'react-native-webview'
|
|
7
7
|
import useNodesRef, { HandlerRef } from './useNodesRef'
|
|
8
|
-
import { getCurrentPage
|
|
9
|
-
import {
|
|
8
|
+
import { getCurrentPage } from './utils'
|
|
9
|
+
import { WebViewMessageEvent, WebViewNavigation, WebViewProgressEvent, WebViewHttpErrorEvent, WebViewEvent } from 'react-native-webview/lib/WebViewTypes'
|
|
10
10
|
import { useNavigation } from '@react-navigation/native'
|
|
11
11
|
import { RouteContext } from './context'
|
|
12
12
|
import { BackHandler, StyleSheet, View, Text, Platform } from 'react-native'
|
|
@@ -99,6 +99,9 @@ const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((pr
|
|
|
99
99
|
const [pageLoadErr, setPageLoadErr] = useState<boolean>(false)
|
|
100
100
|
const currentPage = useMemo(() => getCurrentPage(pageId), [pageId])
|
|
101
101
|
const webViewRef = useRef<WebView>(null)
|
|
102
|
+
const [isLoaded, setIsLoaded] = useState<boolean>(true)
|
|
103
|
+
const fristLoaded = useRef<boolean>(false)
|
|
104
|
+
|
|
102
105
|
const defaultWebViewStyle = {
|
|
103
106
|
position: 'absolute' as 'absolute' | 'relative' | 'static',
|
|
104
107
|
left: 0 as number,
|
|
@@ -151,29 +154,10 @@ const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((pr
|
|
|
151
154
|
return null
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
const _load = function (res: WebViewNavigationEvent) {
|
|
155
|
-
const result = {
|
|
156
|
-
type: 'load',
|
|
157
|
-
timeStamp: res.timeStamp,
|
|
158
|
-
detail: {
|
|
159
|
-
src: res.nativeEvent?.url
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
bindload?.(result)
|
|
163
|
-
}
|
|
164
|
-
const _error = function (res: WebViewErrorEvent) {
|
|
165
|
-
setPageLoadErr(true)
|
|
166
|
-
const result = {
|
|
167
|
-
type: 'error',
|
|
168
|
-
timeStamp: res.timeStamp,
|
|
169
|
-
detail: {
|
|
170
|
-
src: ''
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
binderror && binderror(result)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
157
|
const _reload = function () {
|
|
158
|
+
if (__mpx_mode__ === 'android') {
|
|
159
|
+
fristLoaded.current = false // 安卓需要重新设置
|
|
160
|
+
}
|
|
177
161
|
setPageLoadErr(false)
|
|
178
162
|
}
|
|
179
163
|
|
|
@@ -207,7 +191,7 @@ const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((pr
|
|
|
207
191
|
|
|
208
192
|
const sendMessage = function (params: string) {
|
|
209
193
|
return `
|
|
210
|
-
window.mpxWebviewMessageCallback(${params})
|
|
194
|
+
window.mpxWebviewMessageCallback && window.mpxWebviewMessageCallback(${params})
|
|
211
195
|
true;
|
|
212
196
|
`
|
|
213
197
|
}
|
|
@@ -307,17 +291,61 @@ const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((pr
|
|
|
307
291
|
}
|
|
308
292
|
})
|
|
309
293
|
}
|
|
310
|
-
const events = {}
|
|
311
294
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
295
|
+
let isLoadError = false
|
|
296
|
+
let statusCode: string | number = ''
|
|
297
|
+
const onLoadEndHandle = function (res: WebViewEvent) {
|
|
298
|
+
fristLoaded.current = true
|
|
299
|
+
setIsLoaded(true)
|
|
300
|
+
const src = res.nativeEvent?.url
|
|
301
|
+
if (isLoadError) {
|
|
302
|
+
isLoadError = false
|
|
303
|
+
isNavigateBack.current = false
|
|
304
|
+
const result = {
|
|
305
|
+
type: 'error',
|
|
306
|
+
timeStamp: res.timeStamp,
|
|
307
|
+
detail: {
|
|
308
|
+
src,
|
|
309
|
+
statusCode
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
binderror && binderror(result)
|
|
313
|
+
} else {
|
|
314
|
+
const result = {
|
|
315
|
+
type: 'load',
|
|
316
|
+
timeStamp: res.timeStamp,
|
|
317
|
+
detail: {
|
|
318
|
+
src
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
bindload?.(result)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const onLoadEnd = function (res: WebViewEvent) {
|
|
325
|
+
if (__mpx_mode__ === 'android') {
|
|
326
|
+
setTimeout(() => {
|
|
327
|
+
onLoadEndHandle(res)
|
|
328
|
+
}, 0)
|
|
329
|
+
} else {
|
|
330
|
+
onLoadEndHandle(res)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const onHttpError = function (res: WebViewHttpErrorEvent) {
|
|
334
|
+
isLoadError = true
|
|
335
|
+
statusCode = res.nativeEvent?.statusCode
|
|
336
|
+
}
|
|
337
|
+
const onError = function () {
|
|
338
|
+
statusCode = ''
|
|
339
|
+
isLoadError = true
|
|
340
|
+
if (!fristLoaded.current) {
|
|
341
|
+
setPageLoadErr(true)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const onLoadStart = function () {
|
|
345
|
+
if (!fristLoaded.current) {
|
|
346
|
+
setIsLoaded(false)
|
|
347
|
+
}
|
|
316
348
|
}
|
|
317
|
-
|
|
318
|
-
extendObject(events, {
|
|
319
|
-
onError: _error
|
|
320
|
-
})
|
|
321
349
|
|
|
322
350
|
return (
|
|
323
351
|
<Portal key={pageLoadErr ? 'error' : 'webview'}>
|
|
@@ -329,17 +357,21 @@ const _WebView = forwardRef<HandlerRef<WebView, WebViewProps>, WebViewProps>((pr
|
|
|
329
357
|
</View>
|
|
330
358
|
)
|
|
331
359
|
: (<WebView
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
360
|
+
style={ defaultWebViewStyle }
|
|
361
|
+
source={{ uri: src }}
|
|
362
|
+
pointerEvents={ isLoaded ? 'auto' : 'none' }
|
|
363
|
+
ref={webViewRef}
|
|
364
|
+
javaScriptEnabled={true}
|
|
365
|
+
onNavigationStateChange={_changeUrl}
|
|
366
|
+
onMessage={_message}
|
|
367
|
+
injectedJavaScript={injectedJavaScript}
|
|
368
|
+
onLoadProgress={_onLoadProgress}
|
|
369
|
+
onLoadEnd={onLoadEnd}
|
|
370
|
+
onHttpError={onHttpError}
|
|
371
|
+
onError={onError}
|
|
372
|
+
onLoadStart={onLoadStart}
|
|
373
|
+
allowsBackForwardNavigationGestures={true}
|
|
374
|
+
></WebView>)}
|
|
343
375
|
</Portal>
|
|
344
376
|
)
|
|
345
377
|
})
|
|
@@ -40,10 +40,10 @@ export type AnimationProp = {
|
|
|
40
40
|
// 微信 timingFunction 和 RN Easing 对应关系
|
|
41
41
|
const EasingKey = {
|
|
42
42
|
linear: Easing.linear,
|
|
43
|
-
ease: Easing.ease,
|
|
44
|
-
'ease-in': Easing.in(Easing.
|
|
45
|
-
'ease-in-out': Easing.inOut(Easing.
|
|
46
|
-
'ease-out': Easing.out(Easing.
|
|
43
|
+
ease: Easing.inOut(Easing.ease),
|
|
44
|
+
'ease-in': Easing.in(Easing.poly(3)),
|
|
45
|
+
'ease-in-out': Easing.inOut(Easing.poly(3)),
|
|
46
|
+
'ease-out': Easing.out(Easing.poly(3))
|
|
47
47
|
// 'step-start': '',
|
|
48
48
|
// 'step-end': ''
|
|
49
49
|
}
|