@ray-js/ipc-player-integration 0.0.35-beta.3 → 0.0.35-beta.30
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/ctx/ctx.js +0 -1
- package/lib/features/initPlayerWidgets/index.js +0 -1
- package/lib/i18n/index.d.ts +4 -0
- package/lib/i18n/strings.d.ts +2 -0
- package/lib/i18n/strings.js +4 -2
- package/lib/res/try/try_close.png +0 -0
- package/lib/ui/bottomLeftContent.d.ts +8 -3
- package/lib/ui/bottomLeftContent.js +50 -48
- package/lib/ui/bottomRightContent.d.ts +6 -2
- package/lib/ui/bottomRightContent.js +15 -6
- package/lib/ui/constant.d.ts +2 -2
- package/lib/ui/constant.js +2 -3
- package/lib/ui/index.d.ts +1 -1
- package/lib/ui/index.js +1 -1
- package/lib/ui/ui.js +106 -70
- package/lib/ui/ui.less +2 -2
- package/lib/utils/index.d.ts +0 -1
- package/lib/utils/index.js +0 -10
- package/lib/utils/navigation.d.ts +1 -0
- package/lib/utils/navigation.js +5 -2
- package/lib/utils/ttt.d.ts +7 -5
- package/lib/utils/ttt.js +132 -74
- package/lib/widgets/trialBadge/index.d.ts +1 -3
- package/lib/widgets/trialBadge/index.js +20 -22
- package/lib/widgets/trialBadge/index.less +17 -6
- package/lib/widgets/trialBadge/useTrialBadge.js +24 -15
- package/lib/widgets/tryExperience/tryExperience.d.ts +5 -0
- package/lib/widgets/tryExperience/tryExperience.js +109 -13
- package/package.json +3 -3
package/lib/ctx/ctx.js
CHANGED
|
@@ -52,7 +52,6 @@ export async function initPlayerWidgets(ctx, options) {
|
|
|
52
52
|
newDefaultBottomLeftContent[magnificationIndex].hidden = false;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
console.log('res===1', newDefaultBottomLeftContent);
|
|
56
55
|
ctx.addContent('bottomLeft', newDefaultBottomLeftContent);
|
|
57
56
|
const newDefaultBottomRightContent = _cloneDeep(options.bottomRightContent || defaultBottomRightContent);
|
|
58
57
|
const fullScreenIndex = newDefaultBottomRightContent.findIndex(item => item.id === 'FullScreen');
|
package/lib/i18n/index.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ declare const Strings: kit.I18N<{
|
|
|
33
33
|
ipc_player_trial_preview_text: string;
|
|
34
34
|
ipc_player_trial_subscribe: string;
|
|
35
35
|
ipc_player_fetch_error: string;
|
|
36
|
+
ipc_player_low_phone_not_support: string;
|
|
36
37
|
};
|
|
37
38
|
zh: {
|
|
38
39
|
ipc_player_resolution_HD: string;
|
|
@@ -67,6 +68,7 @@ declare const Strings: kit.I18N<{
|
|
|
67
68
|
ipc_player_trial_preview_text: string;
|
|
68
69
|
ipc_player_trial_subscribe: string;
|
|
69
70
|
ipc_player_fetch_error: string;
|
|
71
|
+
ipc_player_low_phone_not_support: string;
|
|
70
72
|
};
|
|
71
73
|
}, {
|
|
72
74
|
ipc_player_resolution_HD: string;
|
|
@@ -101,6 +103,7 @@ declare const Strings: kit.I18N<{
|
|
|
101
103
|
ipc_player_trial_preview_text: string;
|
|
102
104
|
ipc_player_trial_subscribe: string;
|
|
103
105
|
ipc_player_fetch_error: string;
|
|
106
|
+
ipc_player_low_phone_not_support: string;
|
|
104
107
|
} | {
|
|
105
108
|
ipc_player_resolution_HD: string;
|
|
106
109
|
ipc_player_resolution_SD: string;
|
|
@@ -134,5 +137,6 @@ declare const Strings: kit.I18N<{
|
|
|
134
137
|
ipc_player_trial_preview_text: string;
|
|
135
138
|
ipc_player_trial_subscribe: string;
|
|
136
139
|
ipc_player_fetch_error: string;
|
|
140
|
+
ipc_player_low_phone_not_support: string;
|
|
137
141
|
}>;
|
|
138
142
|
export default Strings;
|
package/lib/i18n/strings.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ declare const _default: {
|
|
|
32
32
|
ipc_player_trial_preview_text: string;
|
|
33
33
|
ipc_player_trial_subscribe: string;
|
|
34
34
|
ipc_player_fetch_error: string;
|
|
35
|
+
ipc_player_low_phone_not_support: string;
|
|
35
36
|
};
|
|
36
37
|
zh: {
|
|
37
38
|
ipc_player_resolution_HD: string;
|
|
@@ -66,6 +67,7 @@ declare const _default: {
|
|
|
66
67
|
ipc_player_trial_preview_text: string;
|
|
67
68
|
ipc_player_trial_subscribe: string;
|
|
68
69
|
ipc_player_fetch_error: string;
|
|
70
|
+
ipc_player_low_phone_not_support: string;
|
|
69
71
|
};
|
|
70
72
|
};
|
|
71
73
|
export default _default;
|
package/lib/i18n/strings.js
CHANGED
|
@@ -31,7 +31,8 @@ export default {
|
|
|
31
31
|
ipc_player_trial_in_use: 'Trial',
|
|
32
32
|
ipc_player_trial_preview_text: 'End',
|
|
33
33
|
ipc_player_trial_subscribe: 'Open',
|
|
34
|
-
ipc_player_fetch_error: 'Failed to fetch data'
|
|
34
|
+
ipc_player_fetch_error: 'Failed to fetch data',
|
|
35
|
+
ipc_player_low_phone_not_support: 'This feature is not supported on this device'
|
|
35
36
|
},
|
|
36
37
|
zh: {
|
|
37
38
|
ipc_player_resolution_HD: '高清',
|
|
@@ -65,6 +66,7 @@ export default {
|
|
|
65
66
|
ipc_player_trial_in_use: '试用中',
|
|
66
67
|
ipc_player_trial_preview_text: '后结束',
|
|
67
68
|
ipc_player_trial_subscribe: '开通',
|
|
68
|
-
ipc_player_fetch_error: '获取异常'
|
|
69
|
+
ipc_player_fetch_error: '获取异常',
|
|
70
|
+
ipc_player_low_phone_not_support: '当前设备不支持此功能'
|
|
69
71
|
}
|
|
70
72
|
};
|
|
Binary file
|
|
@@ -3,8 +3,13 @@ import { UseCtx } from '../interface';
|
|
|
3
3
|
type Props = {
|
|
4
4
|
ctx: ReturnType<UseCtx>;
|
|
5
5
|
children: React.ReactNode;
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* 右下角组件占据的宽度,左下角通过该值约束自身可视宽度避免与右下角重叠。
|
|
8
|
+
* - number: 视为 px
|
|
9
|
+
* - string: 视为合法的 CSS 长度(例如 "35%"),用于横屏按百分比预留
|
|
10
|
+
*/
|
|
11
|
+
reservedRight?: number | string;
|
|
12
|
+
canOpenSettings?: boolean;
|
|
8
13
|
};
|
|
9
|
-
declare const BottomLeftContent: ({ ctx, children, reservedRight }: Props) => React.JSX.Element;
|
|
14
|
+
declare const BottomLeftContent: ({ ctx, children, reservedRight, canOpenSettings }: Props) => React.JSX.Element;
|
|
10
15
|
export default BottomLeftContent;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
1
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
3
2
|
import "core-js/modules/esnext.iterator.find.js";
|
|
4
3
|
import React, { useMemo } from 'react';
|
|
@@ -7,35 +6,23 @@ import clsx from 'clsx';
|
|
|
7
6
|
import { useStore } from '../ctx/store';
|
|
8
7
|
import { useComponentHideState } from './hooks';
|
|
9
8
|
import { TrialBadge, useTrialBadge } from '../widgets/trialBadge';
|
|
10
|
-
/** 右侧透明渐变宽度,用于提示横向可滑动 */
|
|
11
|
-
const FADE_MASK = 'linear-gradient(to right, #000 calc(100% - 16px), transparent 100%)';
|
|
12
|
-
|
|
13
9
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* 右侧透明渐变 mask,用于提示横向可滑动。
|
|
11
|
+
* - 竖屏容器窄,48px 渐变区已足够明显
|
|
12
|
+
* - 横屏容器约占 65% 宽度(≥ 500px),且单个按钮就有 ~72px,
|
|
13
|
+
* 渐变区需大于一个按钮宽度才能让最右侧按钮"半隐半现",
|
|
14
|
+
* 暗示后面还有内容
|
|
17
15
|
*/
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
bottom: '100%',
|
|
22
|
-
marginBottom: 4,
|
|
23
|
-
zIndex: 2,
|
|
24
|
-
width: 'max-content'
|
|
25
|
-
};
|
|
26
|
-
const TRIAL_BADGE_WRAP_STYLE_FULL = {
|
|
27
|
-
position: 'absolute',
|
|
28
|
-
left: '25px',
|
|
29
|
-
bottom: '100%',
|
|
30
|
-
marginBottom: 8,
|
|
31
|
-
zIndex: 2,
|
|
32
|
-
width: 'max-content'
|
|
16
|
+
const getFadeMask = screenType => {
|
|
17
|
+
const fadeWidth = screenType === 'vertical' ? 60 : 120;
|
|
18
|
+
return `linear-gradient(to right, #000 calc(100% - ${fadeWidth}px), transparent 100%)`;
|
|
33
19
|
};
|
|
34
20
|
const BottomLeftContent = _ref => {
|
|
35
21
|
let {
|
|
36
22
|
ctx,
|
|
37
23
|
children,
|
|
38
|
-
reservedRight = 0
|
|
24
|
+
reservedRight = 0,
|
|
25
|
+
canOpenSettings = true
|
|
39
26
|
} = _ref;
|
|
40
27
|
const {
|
|
41
28
|
screenType,
|
|
@@ -57,10 +44,20 @@ const BottomLeftContent = _ref => {
|
|
|
57
44
|
const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
|
|
58
45
|
return (_trialRemainingSec = tryExp === null || tryExp === void 0 || (_tryExp$initProps2 = tryExp.initProps) === null || _tryExp$initProps2 === void 0 ? void 0 : _tryExp$initProps2.trialRemainingSec) !== null && _trialRemainingSec !== void 0 ? _trialRemainingSec : 0;
|
|
59
46
|
}, [bottomLeftContent]);
|
|
60
|
-
const
|
|
61
|
-
var
|
|
47
|
+
const buttonState = useMemo(() => {
|
|
48
|
+
var _tryExp$initProps3;
|
|
49
|
+
const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
|
|
50
|
+
return tryExp === null || tryExp === void 0 || (_tryExp$initProps3 = tryExp.initProps) === null || _tryExp$initProps3 === void 0 ? void 0 : _tryExp$initProps3.buttonState;
|
|
51
|
+
}, [bottomLeftContent]);
|
|
52
|
+
const isLowPhone = useMemo(() => {
|
|
53
|
+
var _isLowPhone, _tryExp$initProps4;
|
|
54
|
+
const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
|
|
55
|
+
return (_isLowPhone = tryExp === null || tryExp === void 0 || (_tryExp$initProps4 = tryExp.initProps) === null || _tryExp$initProps4 === void 0 ? void 0 : _tryExp$initProps4.isLowPhone) !== null && _isLowPhone !== void 0 ? _isLowPhone : false;
|
|
56
|
+
}, []);
|
|
57
|
+
const gRawData = useMemo(() => {
|
|
58
|
+
var _gRawData, _tryExp$initProps5;
|
|
62
59
|
const tryExp = bottomLeftContent === null || bottomLeftContent === void 0 ? void 0 : bottomLeftContent.find(item => item.id === 'TryExperience');
|
|
63
|
-
return (
|
|
60
|
+
return (_gRawData = tryExp === null || tryExp === void 0 || (_tryExp$initProps5 = tryExp.initProps) === null || _tryExp$initProps5 === void 0 ? void 0 : _tryExp$initProps5.gRawData) !== null && _gRawData !== void 0 ? _gRawData : false;
|
|
64
61
|
}, [bottomLeftContent]);
|
|
65
62
|
const [shouldHide] = useComponentHideState();
|
|
66
63
|
const {
|
|
@@ -69,45 +66,50 @@ const BottomLeftContent = _ref => {
|
|
|
69
66
|
handleCountdownEnd
|
|
70
67
|
} = useTrialBadge(ctx.event, ctx.devId, trialRemainingSec);
|
|
71
68
|
const paddingLeftPx = screenType === 'vertical' ? 0 : 25;
|
|
72
|
-
const
|
|
69
|
+
const showBadge = showSmartImageQuality && showTrialBadge && canOpenSettings && gRawData && !isLowPhone && buttonState === 1 && trialRemainingSec > 0;
|
|
70
|
+
const coverHeight = screenType === 'vertical' ? shouldHide ? 49 : 48 : shouldHide ? 84 : 83;
|
|
71
|
+
const containerHeight = screenType === 'vertical' ? 48 : 58;
|
|
72
|
+
|
|
73
|
+
// reservedRight 兼容 number(px)与 string(任意 CSS 长度,例如 "35%")
|
|
74
|
+
const rightCss = typeof reservedRight === 'number' ? `${reservedRight}px` : reservedRight;
|
|
75
|
+
const fadeMask = getFadeMask(screenType);
|
|
73
76
|
return /*#__PURE__*/React.createElement(CoverView, {
|
|
74
|
-
className: clsx('ipc-player-bottom-left-content-wrap')
|
|
75
|
-
// 注意:不要在这里覆盖 position,CSS 类已经设置 position: absolute; left: 0; bottom: 0
|
|
76
|
-
// 否则 children 会失去贴底定位,从而与 bottomRightContent 不对齐
|
|
77
|
-
,
|
|
77
|
+
className: clsx('ipc-player-bottom-left-content-wrap'),
|
|
78
78
|
style: {
|
|
79
|
-
right:
|
|
80
|
-
height:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
style: _objectSpread(_objectSpread({}, trialBadgeWrapStyle), {}, {
|
|
85
|
-
transform: shouldHide ? 'translateY(40px)' : 'translateY(0)',
|
|
79
|
+
right: rightCss,
|
|
80
|
+
height: showBadge ? `${coverHeight + 30}px` : `${coverHeight}px`,
|
|
81
|
+
display: 'flex',
|
|
82
|
+
flexDirection: 'column',
|
|
83
|
+
justifyContent: 'center',
|
|
86
84
|
opacity: shouldHide ? 0 : 1,
|
|
87
|
-
|
|
88
|
-
}
|
|
85
|
+
pointerEvents: shouldHide ? 'none' : 'auto'
|
|
86
|
+
}
|
|
87
|
+
}, showBadge && /*#__PURE__*/React.createElement(View, {
|
|
88
|
+
style: {
|
|
89
|
+
height: '32px',
|
|
90
|
+
marginLeft: screenType === 'vertical' ? '12px' : '25px'
|
|
91
|
+
}
|
|
89
92
|
}, /*#__PURE__*/React.createElement(TrialBadge, {
|
|
90
93
|
brandColor: brandColor,
|
|
91
94
|
trialRemainingSec: trialRemainingSec,
|
|
92
|
-
refreshToken: refreshToken,
|
|
93
95
|
onSubscribe: handleTrialSubscribe,
|
|
94
96
|
onCountdownEnd: handleCountdownEnd
|
|
95
97
|
})), /*#__PURE__*/React.createElement(View, {
|
|
96
98
|
style: {
|
|
97
|
-
paddingBottom: screenType === 'vertical' ?
|
|
99
|
+
paddingBottom: screenType === 'vertical' ? 0 : '25px',
|
|
98
100
|
paddingLeft: `${paddingLeftPx}px`,
|
|
101
|
+
paddingRight: '16px',
|
|
99
102
|
width: '100%',
|
|
103
|
+
height: `${containerHeight}px`,
|
|
104
|
+
marginTop: showBadge ? '-2px' : 0,
|
|
100
105
|
overflowX: 'auto',
|
|
101
106
|
overflowY: 'hidden',
|
|
102
107
|
whiteSpace: 'nowrap',
|
|
103
108
|
WebkitOverflowScrolling: 'touch',
|
|
104
|
-
WebkitMaskImage:
|
|
105
|
-
maskImage:
|
|
109
|
+
WebkitMaskImage: fadeMask,
|
|
110
|
+
maskImage: fadeMask
|
|
106
111
|
},
|
|
107
|
-
className: clsx('ipc-player-bottom-left-content-container'
|
|
108
|
-
// 'ipc-player-bottom-left-content-hide': shouldHide,
|
|
109
|
-
// 'ipc-player-bottom-left-content-show': !shouldHide,
|
|
110
|
-
})
|
|
112
|
+
className: clsx('ipc-player-bottom-left-content-container')
|
|
111
113
|
}, children));
|
|
112
114
|
};
|
|
113
115
|
export default BottomLeftContent;
|
|
@@ -3,8 +3,12 @@ import { UseCtx } from '../interface';
|
|
|
3
3
|
type Props = {
|
|
4
4
|
ctx: ReturnType<UseCtx>;
|
|
5
5
|
children: React.ReactNode;
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* 由 ui.tsx 计算得到的右下角组件宽度(与左下角的 reservedRight 保持一致)
|
|
8
|
+
* - number: 视为 px
|
|
9
|
+
* - string: 视为合法的 CSS width 值(例如 "35%" / "20vw")
|
|
10
|
+
*/
|
|
11
|
+
width?: number | string;
|
|
8
12
|
};
|
|
9
13
|
declare const BottomRightContent: ({ ctx, children, width }: Props) => React.JSX.Element;
|
|
10
14
|
export default BottomRightContent;
|
|
@@ -17,20 +17,29 @@ const BottomRightContent = _ref => {
|
|
|
17
17
|
playState: ctx.playState
|
|
18
18
|
});
|
|
19
19
|
const [shouldHide] = useComponentHideState();
|
|
20
|
+
const containerHeight = screenType === 'vertical' ? 48 : 58;
|
|
21
|
+
|
|
22
|
+
// 将 width 统一规范成 CSS 合法值。number → "${n}px",string → 原样透传。
|
|
23
|
+
// 0 / 空串视为「没有传宽度」,沿用默认布局(不设 width)。
|
|
24
|
+
const widthStyle = typeof width === 'number' && width > 0 ? {
|
|
25
|
+
width: `${width}px`,
|
|
26
|
+
boxSizing: 'border-box'
|
|
27
|
+
} : typeof width === 'string' && width ? {
|
|
28
|
+
width,
|
|
29
|
+
boxSizing: 'border-box'
|
|
30
|
+
} : null;
|
|
20
31
|
return /*#__PURE__*/React.createElement(CoverView, {
|
|
21
32
|
className: clsx('ipc-player-bottom-right-content-wrap'),
|
|
22
33
|
style: _objectSpread({
|
|
23
34
|
paddingLeft: screenType === 'vertical' ? '10px' : '0',
|
|
24
|
-
height: screenType === 'vertical' ? shouldHide ? '
|
|
25
|
-
},
|
|
26
|
-
width: `${width}px`,
|
|
27
|
-
boxSizing: 'border-box'
|
|
28
|
-
} : null)
|
|
35
|
+
height: screenType === 'vertical' ? shouldHide ? '49px' : '48px' : shouldHide ? '84px' : '83px'
|
|
36
|
+
}, widthStyle)
|
|
29
37
|
}, /*#__PURE__*/React.createElement(View, {
|
|
30
38
|
style: {
|
|
31
|
-
paddingBottom: screenType === 'vertical' ?
|
|
39
|
+
paddingBottom: screenType === 'vertical' ? 0 : '25px',
|
|
32
40
|
paddingRight: screenType === 'vertical' ? 0 : '25px',
|
|
33
41
|
width: '100%',
|
|
42
|
+
height: `${containerHeight}px`,
|
|
34
43
|
justifyContent: 'flex-end'
|
|
35
44
|
},
|
|
36
45
|
className: clsx('ipc-player-bottom-right-content-container', {
|
package/lib/ui/constant.d.ts
CHANGED
|
@@ -21,13 +21,13 @@ export declare const landscapeTipId = "landscapeTipId";
|
|
|
21
21
|
export declare const multiPtzId = "multiPtzId";
|
|
22
22
|
/** 控件点击统一事件:任意播放器内控件被点击时触发,可用于埋点或统一处理 */
|
|
23
23
|
export declare const widgetClick = "widgetClick";
|
|
24
|
-
/** 「去体验」按钮业务事件,与清晰度 resolutionBtnControlClick 用法一致 */
|
|
25
|
-
export declare const tryExperienceBtnClick = "tryExperienceBtnClick";
|
|
26
24
|
/**
|
|
27
25
|
* 试看倒计时结束事件:试看徽章倒计时归零时触发,
|
|
28
26
|
* 业务侧/相关控件可监听此事件刷新自己的状态(例如 TryExperience 重新拉取状态)
|
|
29
27
|
*/
|
|
30
28
|
export declare const trialCountdownEnd = "trialCountdownEnd";
|
|
29
|
+
export declare const hideTrialBadgeEvent = "hideTrialBadge";
|
|
30
|
+
export declare const refreshSmartImageQualityEvent = "refreshSmartImageQuality";
|
|
31
31
|
/** 控件点击事件用的 widgetId,统一在此维护 */
|
|
32
32
|
export declare const widgetLabs: {
|
|
33
33
|
readonly SCREENSHOT: "Screenshot";
|
package/lib/ui/constant.js
CHANGED
|
@@ -22,14 +22,13 @@ export const multiPtzId = 'multiPtzId';
|
|
|
22
22
|
/** 控件点击统一事件:任意播放器内控件被点击时触发,可用于埋点或统一处理 */
|
|
23
23
|
export const widgetClick = 'widgetClick';
|
|
24
24
|
|
|
25
|
-
/** 「去体验」按钮业务事件,与清晰度 resolutionBtnControlClick 用法一致 */
|
|
26
|
-
export const tryExperienceBtnClick = 'tryExperienceBtnClick';
|
|
27
|
-
|
|
28
25
|
/**
|
|
29
26
|
* 试看倒计时结束事件:试看徽章倒计时归零时触发,
|
|
30
27
|
* 业务侧/相关控件可监听此事件刷新自己的状态(例如 TryExperience 重新拉取状态)
|
|
31
28
|
*/
|
|
32
29
|
export const trialCountdownEnd = 'trialCountdownEnd';
|
|
30
|
+
export const hideTrialBadgeEvent = 'hideTrialBadge';
|
|
31
|
+
export const refreshSmartImageQualityEvent = 'refreshSmartImageQuality';
|
|
33
32
|
|
|
34
33
|
/** 控件点击事件用的 widgetId,统一在此维护 */
|
|
35
34
|
export const widgetLabs = {
|
package/lib/ui/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export * from './hooks';
|
|
|
3
3
|
export * from './context';
|
|
4
4
|
export type * from './event';
|
|
5
5
|
export { widgetClick } from './widgetClick';
|
|
6
|
-
export { widgetLabs,
|
|
6
|
+
export { widgetLabs, trialCountdownEnd } from './constant';
|
package/lib/ui/index.js
CHANGED