@24i/bigscreen-sdk 1.0.8-alpha.2196 → 1.0.8-alpha.2201

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 (37) hide show
  1. package/package.json +9 -8
  2. package/packages/analytics/README.md +2 -2
  3. package/packages/analytics/src/A.ts +2 -2
  4. package/packages/analytics/src/Analytics.ts +1 -1
  5. package/packages/analytics/src/clients/GoogleAnalytics/GoogleAnalytics.ts +1 -1
  6. package/packages/analytics/src/clients/GoogleAnalytics/constants.ts +17 -17
  7. package/packages/analytics/src/constants.ts +2 -2
  8. package/packages/analytics/src/interface.ts +1 -1
  9. package/packages/device/src/resolver/resolver.ts +5 -5
  10. package/packages/driver-base/src/KeyMap/KeyMap.ts +4 -0
  11. package/packages/driver-base/src/__mocks__/keyMap.ts +1 -0
  12. package/packages/driver-base/src/types/KeycodeKeyMap.ts +2 -0
  13. package/packages/driver-browser/src/keymap.ts +8 -0
  14. package/packages/epg/src/v2/DataManager.ts +1 -0
  15. package/packages/epg/src/v2/DatePicker.tsx +1 -1
  16. package/packages/epg/src/v2/Epg.tsx +2 -4
  17. package/packages/focus/src/Layout/Matrix.tsx +18 -18
  18. package/packages/grid/src/Base/Base.tsx +10 -10
  19. package/packages/grid/src/FirstOnlyGrid/FirstOnlyGrid.tsx +4 -2
  20. package/packages/icon/src/Icon.tsx +27 -5
  21. package/packages/keyboard/src/WhitelabelKeyboard/Keyboard.tsx +20 -18
  22. package/packages/l10n/src/date/de.ts +6 -6
  23. package/packages/l10n/src/date/en-common.ts +1 -1
  24. package/packages/l10n/src/date/fr.ts +5 -4
  25. package/packages/l10n/src/date/he.ts +6 -6
  26. package/packages/l10n/src/date/types.ts +1 -1
  27. package/packages/list/src/Base/Base.tsx +8 -8
  28. package/packages/player-ui/src/PlayerUI.tsx +26 -25
  29. package/packages/player-ui/src/Seekbar.tsx +18 -11
  30. package/packages/player-ui/src/Seeking.ts +48 -12
  31. package/packages/player-ui/src/mocks.ts +8 -4
  32. package/packages/player-ui/src/timeUtils.ts +3 -3
  33. package/packages/time/src/adapters/24iMediaTimeApi.ts +47 -0
  34. package/packages/time/src/services/24iMediaTimeApi/24iMediaTimeApi.ts +43 -0
  35. package/packages/time/src/services/24iMediaTimeApi/index.ts +6 -0
  36. package/packages/types-bigscreen-jsx/index.d.ts +9 -6
  37. package/packages/utils/src/textUtils.ts +6 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@24i/bigscreen-sdk",
3
- "version": "1.0.8-alpha.2196",
3
+ "version": "1.0.8-alpha.2201",
4
4
  "description": "SmartApps BIGscreen SDK monorepo",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -38,7 +38,7 @@
38
38
  "homepage": "https://github.com/24i/smartapps-bigscreen-sdk#readme",
39
39
  "dependencies": {
40
40
  "@24i/player-base": "6.10.0",
41
- "@24i/smartapps-datalayer": "3.0.5-alpha.965",
41
+ "@24i/smartapps-datalayer": "3.0.5",
42
42
  "@sentry/browser": "^7.12.1",
43
43
  "@types/gtag.js": "^0.0.12",
44
44
  "@types/prop-types": "^15.7.5",
@@ -54,11 +54,11 @@
54
54
  "@babel/core": "^7.19.0",
55
55
  "@babel/preset-env": "^7.19.0",
56
56
  "@types/fs-extra": "^9.0.13",
57
- "@types/inquirer": "^9.0.1",
58
- "@types/jest": "^29.0.2",
57
+ "@types/inquirer": "^9.0.3",
58
+ "@types/jest": "^29.2.3",
59
59
  "@types/minimist": "^1.2.2",
60
60
  "@types/node": "^18.7.18",
61
- "@types/react": "^18.0.20",
61
+ "@types/react": "^18.0.25",
62
62
  "babel-jest": "^29.0.3",
63
63
  "chalk": "^5.0.1",
64
64
  "coveralls": "^3.1.1",
@@ -66,15 +66,15 @@
66
66
  "fs-extra": "^10.1.0",
67
67
  "full-icu": "^1.5.0",
68
68
  "identity-obj-proxy": "^3.0.0",
69
- "inquirer": "^9.1.1",
70
- "jest": "^29.0.3",
69
+ "inquirer": "^9.1.4",
70
+ "jest": "^29.3.1",
71
71
  "jest-canvas-mock": "^2.4.0",
72
72
  "jest-environment-jsdom": "^29.0.3",
73
73
  "minimist": "^1.2.6",
74
74
  "patch-package": "^6.4.7",
75
75
  "sass": "^1.54.9",
76
76
  "sass-true": "^6.1.0",
77
- "ts-jest": "^29.0.1",
77
+ "ts-jest": "^29.0.3",
78
78
  "ts-node": "^10.9.1",
79
79
  "tsconfig-paths-jest": "0.0.1",
80
80
  "typescript": "^4.8.3"
@@ -250,6 +250,7 @@
250
250
  "./storage": "./packages/storage/src/index.ts",
251
251
  "./throbber": "./packages/throbber/src/index.ts",
252
252
  "./time/adapters/worldTimeApi": "./packages/time/src/adapters/worldTimeApi.ts",
253
+ "./time/adapters/24iMediaTimeApi": "./packages/time/src/adapters/24iMediaTimeApi.ts",
253
254
  "./time": "./packages/time/src/index.ts",
254
255
  "./toast/styles": "./packages/toast/src/Toast/styles.ts",
255
256
  "./toast": "./packages/toast/src/index.ts",
@@ -44,7 +44,7 @@ class DetailScene {
44
44
  });
45
45
  this.a('buffering_stop');
46
46
  // render
47
- this.a('screen_view');
47
+ this.a('scene_view');
48
48
  }
49
49
  }
50
50
  ```
@@ -60,6 +60,6 @@ Clients have to opt-in to events they wish to listen to. This can be done by lis
60
60
  using a regular expression.
61
61
 
62
62
  ```typescript
63
- analytics.addClient(new GoogleAnalytics(), ['screen_view', 'buffering_start', 'buffering_stop']);
63
+ analytics.addClient(new GoogleAnalytics(), ['scene_view', 'buffering_start', 'buffering_stop']);
64
64
  analytics.addClient(new ConsoleAnalytics(), /.*/);
65
65
  ```
@@ -16,8 +16,8 @@ export class A {
16
16
  * // Creates a function and sets the basic payload.
17
17
  * const a = A.of({ sceneType: 'HOME', sceneId: 'home' });
18
18
  *
19
- * // Sends 'screen_view' event with the basic payload.
20
- * a('screen_view');
19
+ * // Sends 'scene_view' event with the basic payload.
20
+ * a('scene_view');
21
21
  *
22
22
  * // Sends 'buffering_stop' with shallow merge of the basic payload and { asset } object.
23
23
  * a('buffering_stop', { asset });
@@ -51,7 +51,7 @@ class Analytics {
51
51
  registeredEvents[eventName] = listener;
52
52
  });
53
53
  this.clients[client.name] = {
54
- client: client,
54
+ client,
55
55
  registeredEvents,
56
56
  };
57
57
  }
@@ -80,7 +80,7 @@ export class GoogleAnalytics implements AnalyticsClient {
80
80
  }
81
81
 
82
82
  async startSession() {
83
- const sessionNumber = parseInt(await Storage.getItem(GA_SESSION_COUNTER) ?? '0') + 1;
83
+ const sessionNumber = parseInt(await Storage.getItem(GA_SESSION_COUNTER) ?? '0', 10) + 1;
84
84
  this.constantUrlParams.sct = sessionNumber;
85
85
  this.constantUrlParams.sid = generateSessionId();
86
86
  await Storage.setItem(GA_SESSION_COUNTER, sessionNumber.toString());
@@ -12,7 +12,7 @@ export const GOOGLE_ANALYTICS_TRIGGERS = [
12
12
  'playback_stop',
13
13
  'player_close',
14
14
  'player_open',
15
- 'screen_view',
15
+ 'scene_view',
16
16
  'scroll_25',
17
17
  'scroll_50',
18
18
  'scroll_75',
@@ -23,31 +23,31 @@ export const TRIGGER_NAME_TO_GA_EVENTS: Record<
23
23
  typeof GOOGLE_ANALYTICS_TRIGGERS[number], GAEventsNames[]
24
24
  > = {
25
25
  // App
26
- 'app_close': ['app_close', 'video_progress'],
26
+ app_close: ['app_close', 'video_progress'],
27
27
 
28
28
  // Playback
29
- 'playback_10': ['video_progress'],
30
- 'playback_25': ['video_progress'],
31
- 'playback_50': ['video_progress'],
32
- 'playback_75': ['video_progress'],
33
- 'playback_90': ['video_progress'],
34
- 'playback_pause': ['video_progress'],
35
- 'playback_start': ['video_start'],
36
- 'playback_stop': ['video_progress'],
29
+ playback_10: ['video_progress'],
30
+ playback_25: ['video_progress'],
31
+ playback_50: ['video_progress'],
32
+ playback_75: ['video_progress'],
33
+ playback_90: ['video_progress'],
34
+ playback_pause: ['video_progress'],
35
+ playback_start: ['video_start'],
36
+ playback_stop: ['video_progress'],
37
37
 
38
38
  // Player
39
- 'player_close': ['a24i_content', 'video_progress'],
40
- 'player_open': ['a24i_content'],
39
+ player_close: ['a24i_content', 'video_progress'],
40
+ player_open: ['a24i_content'],
41
41
 
42
42
  // Scenes
43
- 'screen_view': [
43
+ scene_view: [
44
44
  'page_view',
45
45
  'a24i_operational',
46
46
  ],
47
47
 
48
48
  // Scrolling in scene
49
- 'scroll_25': ['scroll'],
50
- 'scroll_50': ['scroll'],
51
- 'scroll_75': ['scroll'],
52
- 'scroll_90': ['scroll'],
49
+ scroll_25: ['scroll'],
50
+ scroll_50: ['scroll'],
51
+ scroll_75: ['scroll'],
52
+ scroll_90: ['scroll'],
53
53
  };
@@ -26,7 +26,7 @@ export const EVENT_NAMES = [
26
26
  'player_open',
27
27
 
28
28
  // Scenes
29
- 'screen_view',
29
+ 'scene_view',
30
30
 
31
31
  // Scrolling in scene
32
32
  'scroll_25',
@@ -52,5 +52,5 @@ export const ONLY_ONCE_EVENTS: AnalyticsEventsNames[] = [
52
52
 
53
53
  export const UNREPEATABLE_EVENTS: AnalyticsEventsNames[] = [
54
54
  // Scenes
55
- 'screen_view',
55
+ 'scene_view',
56
56
  ];
@@ -47,7 +47,7 @@ export type AnalyticsEventsMap = {
47
47
  'player_open': (payload: void) => void,
48
48
 
49
49
  // Scenes
50
- 'screen_view': (payload: SceneInfo) => void,
50
+ 'scene_view': (payload: SceneInfo) => void,
51
51
 
52
52
  // Scrolling in scene
53
53
  'scroll_25': (payload: SceneInfo) => void,
@@ -20,6 +20,11 @@ const getDevice = () => {
20
20
  info('HbbTV');
21
21
  return new DeviceHbbTV();
22
22
  }
23
+ // any because JavaScriptBridge property is defined on Android TV Bridge platform
24
+ if (typeof (window as any).JavaScriptBridge !== 'undefined') {
25
+ info('AndroidTV');
26
+ return new DeviceAndroidTV();
27
+ }
23
28
  if (userAgent.match(/Tizen/)) {
24
29
  info('Tizen');
25
30
  return new DeviceTizen();
@@ -40,11 +45,6 @@ const getDevice = () => {
40
45
  info('KreaTV');
41
46
  return new DeviceKreaTV();
42
47
  }
43
- // any because JavaScriptBridge property is defined on Android TV Bridge platform
44
- if (typeof (window as any).JavaScriptBridge !== 'undefined') {
45
- info('AndroidTV');
46
- return new DeviceAndroidTV();
47
- }
48
48
  // any because ENTONE property is defined on Entone platform
49
49
  if (typeof (window as any).ENTONE !== 'undefined') {
50
50
  info('Entone');
@@ -384,3 +384,7 @@ export const GAMEPAD_A = new Key({
384
384
  export const GAMEPAD_B = new Key({
385
385
  id: 'GAMEPAD_B',
386
386
  });
387
+
388
+ export const SUBTITLES = new Key({
389
+ id: 'SUBTITLES',
390
+ });
@@ -38,4 +38,5 @@ export const KEYMAP = {
38
38
  KP_9: 105,
39
39
  CHANNEL_UP: 33,
40
40
  CHANNEL_DOWN: 34,
41
+ SUBTITLES: 83,
41
42
  };
@@ -82,4 +82,6 @@ export type KeycodeKeyMap = {
82
82
 
83
83
  GAMEPAD_A: number,
84
84
  GAMEPAD_B: number,
85
+
86
+ SUBTITLES: number,
85
87
  };
@@ -9,6 +9,14 @@ export const getKeyMap = () => {
9
9
  VOL_UP: { code: 'KeyU', keyCode: 85 },
10
10
  VOL_DOWN: { code: 'KeyJ', keyCode: 74 },
11
11
  MUTE: { code: 'KeyM', keyCode: 77 },
12
+ PLAY: { code: 'KeyO', keyCode: 79 },
13
+ PAUSE: { code: 'KeyP', keyCode: 80 },
14
+ PLAYPAUSE: { code: 'Space', keyCode: 32 },
15
+ FF: { code: 'Period', keyCode: 190 },
16
+ REW: { code: 'Comma', keyCode: 188 },
17
+ STOP: { code: 'Escape', keyCode: 27 },
18
+ SUBTITLES: { code: 'KeyS', keyCode: 83 },
19
+ REC: { code: 'KeyI', keyCode: 73 },
12
20
  });
13
21
  return generateKeyCodeKeyMap();
14
22
  };
@@ -122,6 +122,7 @@ export class DataManager implements IDataManager {
122
122
  const newStart = lastRangeFrom - cellSize;
123
123
  newRange[START] = newStart;
124
124
  }
125
+ // eslint-disable-next-line no-continue
125
126
  continue;
126
127
  }
127
128
  if (start < rangeFrom) {
@@ -47,7 +47,7 @@ const prepareDateData = ({
47
47
  const defaultFormat = (date: Date) => {
48
48
  const locale = i18n.getLanguage();
49
49
  return l10n.date(date, locale).toLocaleDateString(
50
- { weekday: 'short', day: 'numeric', month: 'short' }
50
+ { weekday: 'short', day: 'numeric', month: 'short' },
51
51
  );
52
52
  };
53
53
 
@@ -297,6 +297,8 @@ export class Epg<
297
297
  return ref;
298
298
  }
299
299
 
300
+ isFocusInRows = () => this.focused === 'epg';
301
+
300
302
  focus(options?: FocusOptions) {
301
303
  if (this.focused === 'datepicker') {
302
304
  this.datePickerComponent.current!.focus(options);
@@ -376,10 +378,6 @@ export class Epg<
376
378
  return this.inputEvents.isVerticallyFastScrolling();
377
379
  }
378
380
 
379
- isFocusInRows = () => {
380
- return this.focused === 'epg';
381
- };
382
-
383
381
  render() {
384
382
  const {
385
383
  datePickerProps, timelineWidthPx, channelHeaderWidthPx, scrollStepMs, rowHeightPx,
@@ -144,24 +144,6 @@ export class Matrix extends Component<Props> implements IFocusable {
144
144
  return false;
145
145
  }
146
146
 
147
- isDirectionLeft(event: KeyboardEvent) {
148
- const { dir } = this.props;
149
- const rtl = isRtl(dir);
150
- return (
151
- (!rtl && LEFT.is(event))
152
- || (rtl && RIGHT.is(event))
153
- );
154
- }
155
-
156
- isDirectionRight(event: KeyboardEvent) {
157
- const { dir } = this.props;
158
- const rtl = isRtl(dir);
159
- return (
160
- (!rtl && RIGHT.is(event))
161
- || (rtl && LEFT.is(event))
162
- );
163
- }
164
-
165
147
  moveForwards = (isCircular: boolean) => (x: number, length: number) => {
166
148
  if (x === length - 1) {
167
149
  return isCircular ? 0 : length - 1;
@@ -204,6 +186,24 @@ export class Matrix extends Component<Props> implements IFocusable {
204
186
  return this.searchVertical(column, fromY, this.moveForwards(!!isCircular));
205
187
  };
206
188
 
189
+ isDirectionLeft(event: KeyboardEvent) {
190
+ const { dir } = this.props;
191
+ const rtl = isRtl(dir);
192
+ return (
193
+ (!rtl && LEFT.is(event))
194
+ || (rtl && RIGHT.is(event))
195
+ );
196
+ }
197
+
198
+ isDirectionRight(event: KeyboardEvent) {
199
+ const { dir } = this.props;
200
+ const rtl = isRtl(dir);
201
+ return (
202
+ (!rtl && RIGHT.is(event))
203
+ || (rtl && LEFT.is(event))
204
+ );
205
+ }
206
+
207
207
  isDifferentReferenceFromCurrent(index: Index2D): boolean {
208
208
  const { current } = this.getRefAt(this.index);
209
209
  const { current: searched } = this.getRefAt(index);
@@ -178,16 +178,6 @@ export class GridBase<T extends Item<U>, U>
178
178
  }
179
179
  };
180
180
 
181
- shouldPreventBlur(event: KeyboardEvent) {
182
- const { horizontal, preventBlurOnLongPress } = this.props;
183
- return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
184
- (preventBlurOnLongPress.forward && isForward(event, horizontal))
185
- || (preventBlurOnLongPress.backward && isBackward(event, horizontal))
186
- || (preventBlurOnLongPress.forwardGroup && isFwGroup(event, horizontal))
187
- || (preventBlurOnLongPress.backwardGroup && isBwGroup(event, horizontal))
188
- ));
189
- }
190
-
191
181
  getRefAtIndex(i: number): Reference<T> {
192
182
  const ref = this.itemRefs[i];
193
183
  if (!ref) {
@@ -253,6 +243,16 @@ export class GridBase<T extends Item<U>, U>
253
243
  this.fastScrollingCounter.reset();
254
244
  };
255
245
 
246
+ shouldPreventBlur(event: KeyboardEvent) {
247
+ const { horizontal, preventBlurOnLongPress } = this.props;
248
+ return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
249
+ (preventBlurOnLongPress.forward && isForward(event, horizontal))
250
+ || (preventBlurOnLongPress.backward && isBackward(event, horizontal))
251
+ || (preventBlurOnLongPress.forwardGroup && isFwGroup(event, horizontal))
252
+ || (preventBlurOnLongPress.backwardGroup && isBwGroup(event, horizontal))
253
+ ));
254
+ }
255
+
256
256
  animatedScrollStarted() {
257
257
  this.isAnimatedScrollRunning = true;
258
258
  }
@@ -61,6 +61,8 @@ export class FirstOnlyGrid<T extends Item<U>, U>
61
61
  const linesDiff = Math.floor(grid.index / itemsInGroup) - oldLine;
62
62
  if (linesDiff > 0) {
63
63
  this.scrollForward(linesDiff);
64
+ } else if (linesDiff < 0) {
65
+ this.scrollBackward(Math.abs(linesDiff));
64
66
  }
65
67
  };
66
68
 
@@ -77,8 +79,8 @@ export class FirstOnlyGrid<T extends Item<U>, U>
77
79
  this.controller.scroll(targetScrollIndex);
78
80
  }
79
81
 
80
- scrollBackward() {
81
- const targetScrollIndex = this.controller.scrollIndex - 1;
82
+ scrollBackward(diff = 1) {
83
+ const targetScrollIndex = this.controller.scrollIndex - diff;
82
84
  this.controller.scroll(targetScrollIndex);
83
85
  }
84
86
 
@@ -1,12 +1,19 @@
1
1
  import { createRef, Reference, Component } from '@24i/bigscreen-sdk/jsx';
2
- import { addClass } from '@24i/bigscreen-sdk/utils/addClass';
3
- import { removeClass } from '@24i/bigscreen-sdk/utils/removeClass';
2
+ import {
3
+ getDisplayToggler,
4
+ IDisplayToggler,
5
+ removeClass,
6
+ addClass,
7
+ DISPLAY_NONE,
8
+ } from '@24i/bigscreen-sdk/utils';
4
9
 
5
10
  const ICON_PREFIX = 'icon-';
6
11
 
7
12
  type Props<SpritesJSON> = {
8
13
  icon?: keyof SpritesJSON,
9
- domRef?: Reference<HTMLDivElement>;
14
+ domRef?: Reference<HTMLDivElement>,
15
+ className?: string,
16
+ initialIsHidden?: boolean,
10
17
  };
11
18
 
12
19
  export class Icon<T> extends Component<Props<T>> {
@@ -14,10 +21,13 @@ export class Icon<T> extends Component<Props<T>> {
14
21
 
15
22
  currentIcon: keyof T | undefined;
16
23
 
24
+ iconDisplay: IDisplayToggler;
25
+
17
26
  constructor(props: Props<T>) {
18
27
  super(props);
19
28
  this.ref = props.domRef || createRef();
20
29
  this.currentIcon = props.icon;
30
+ this.iconDisplay = getDisplayToggler(this.ref, props.initialIsHidden);
21
31
  }
22
32
 
23
33
  getClass(icon: keyof T): string {
@@ -31,6 +41,16 @@ export class Icon<T> extends Component<Props<T>> {
31
41
  this.currentIcon = icon;
32
42
  }
33
43
 
44
+ show() {
45
+ if (!this.currentIcon) return;
46
+ this.iconDisplay.show();
47
+ }
48
+
49
+ hide() {
50
+ if (!this.currentIcon) return;
51
+ this.iconDisplay.hide();
52
+ }
53
+
34
54
  clearIcon() {
35
55
  if (!this.currentIcon) return;
36
56
  removeClass(this.ref, this.getClass(this.currentIcon));
@@ -38,11 +58,13 @@ export class Icon<T> extends Component<Props<T>> {
38
58
  }
39
59
 
40
60
  render() {
41
- const { icon } = this.props;
61
+ const { icon, className, initialIsHidden } = this.props;
42
62
  return (
43
63
  <div
44
64
  ref={this.ref}
45
- className={`icon ${icon ? this.getClass(icon) : ''}`}
65
+ className={`${className || ''} icon ${icon
66
+ ? this.getClass(icon) : ''} ${initialIsHidden
67
+ ? DISPLAY_NONE : ''}`}
46
68
  />
47
69
  );
48
70
  }
@@ -56,7 +56,7 @@ export class Keyboard extends KeyboardBase<Layouts, Props> {
56
56
  createLanguageFocusMatrix() {
57
57
  const { languageLayout } = this.props;
58
58
  this.languageFocusMatrix = generateFocusMatrixFromStringMatrix(
59
- this.keyRefs, languageLayout!.makeFocusStrings(languageLayout!.layout, true)
59
+ this.keyRefs, languageLayout!.makeFocusStrings(languageLayout!.layout, true),
60
60
  );
61
61
  }
62
62
 
@@ -257,23 +257,25 @@ export class Keyboard extends KeyboardBase<Layouts, Props> {
257
257
  <div className="key-row">
258
258
  <div className="key key-wide key-empty">&nbsp;</div>
259
259
  </div>
260
- {!!languageLayout ?
261
- <div className="key-row rounded-border-container">
262
- <Interactable
263
- ref={this.keyRefs.language}
264
- className="key key-wide key-with-icon key-language"
265
- onPress={() => this.onLanguage()}
266
- >
267
- <div className="key-background">
268
- <div className="key-mask-icon" />
269
- </div>
270
- </Interactable>
271
- </div>
272
- :
273
- <div className="key-row">
274
- <div className="key key-wide key-empty">&nbsp;</div>
275
- </div>
276
- }
260
+ {languageLayout
261
+ ? (
262
+ <div className="key-row rounded-border-container">
263
+ <Interactable
264
+ ref={this.keyRefs.language}
265
+ className="key key-wide key-with-icon key-language"
266
+ onPress={() => this.onLanguage()}
267
+ >
268
+ <div className="key-background">
269
+ <div className="key-mask-icon" />
270
+ </div>
271
+ </Interactable>
272
+ </div>
273
+ )
274
+ : (
275
+ <div className="key-row">
276
+ <div className="key key-wide key-empty">&nbsp;</div>
277
+ </div>
278
+ )}
277
279
  </div>
278
280
  <div className="keys-container key-layout">
279
281
  {layout}
@@ -1,7 +1,7 @@
1
1
  import { processYear, dateFormatter, toTwoDigitLocaleTimeString } from './common';
2
2
  import { DateTimeFormatOptions, Day, Month, Weekday, Year } from './types';
3
3
 
4
- //https://web.library.yale.edu/cataloging/months
4
+ // https://web.library.yale.edu/cataloging/months
5
5
  const DE_MONTHS = {
6
6
  narrow: [
7
7
  'J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D',
@@ -40,7 +40,7 @@ const processMonth = (
40
40
  ) => {
41
41
  if (typeof month === 'undefined') return;
42
42
  if (month === '2-digit' || month === 'numeric') {
43
- const strMonth = (date.getMonth() + 1).toString() + '.';
43
+ const strMonth = `${(date.getMonth() + 1).toString()}.`;
44
44
  dateParts.push(strMonth);
45
45
  } else {
46
46
  const finalMonth = DE_MONTHS[month][date.getMonth()];
@@ -80,10 +80,10 @@ const toLocaleDateString = (date: Date) => (
80
80
  const dateConnection = '';
81
81
  const dateParts: string[] = [];
82
82
  processDay(dateParts, day, date);
83
- if ((month === 'narrow' ||
84
- month === 'short' ||
85
- month === 'long') &&
86
- day != null
83
+ if ((month === 'narrow'
84
+ || month === 'short'
85
+ || month === 'long')
86
+ && day != null
87
87
  ) {
88
88
  dateParts.push(' ');
89
89
  }
@@ -32,7 +32,7 @@ export const processWeekday = (
32
32
  day: Day | undefined,
33
33
  weekday: Weekday | undefined,
34
34
  date: Date,
35
- weekdaysLocaleData: LocaleData = EN_WEEKDAYS
35
+ weekdaysLocaleData: LocaleData = EN_WEEKDAYS,
36
36
  ) => {
37
37
  if (typeof weekday === 'undefined') return;
38
38
  let weekdayString = weekdaysLocaleData[weekday][date.getDay()];
@@ -1,7 +1,7 @@
1
1
  import { dateFormatter, processYear, toTwoDigitLocaleTimeString } from './common';
2
2
  import { DateTimeFormatOptions, Day, Month, Weekday, Year } from './types';
3
3
 
4
- //https://web.library.yale.edu/cataloging/months
4
+ // https://web.library.yale.edu/cataloging/months
5
5
  const FR_MONTHS = {
6
6
  narrow: [
7
7
  'J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D',
@@ -38,7 +38,7 @@ const processWeekday = (
38
38
  if (typeof weekday === 'undefined') return;
39
39
  let weekdayString = FR_WEEKDAYS[weekday][date.getDay()];
40
40
  if (
41
- ( typeof year !== 'undefined'
41
+ (typeof year !== 'undefined'
42
42
  || typeof month !== 'undefined'
43
43
  || typeof day !== 'undefined') && weekday !== 'long'
44
44
  ) {
@@ -48,11 +48,12 @@ const processWeekday = (
48
48
  };
49
49
 
50
50
  const processDay = (
51
- dateParts: string[], day: Day | undefined, month: Month | undefined, date: Date) => {
51
+ dateParts: string[], day: Day | undefined, month: Month | undefined, date: Date,
52
+ ) => {
52
53
  if (typeof day === 'undefined') return;
53
54
  const strDate = date.getDate().toString();
54
55
  dateParts.push(
55
- strDate.length === 1 && month !== 'short' && month !== 'long' ? `0${strDate}` : strDate
56
+ strDate.length === 1 && month !== 'short' && month !== 'long' ? `0${strDate}` : strDate,
56
57
  );
57
58
  };
58
59
 
@@ -81,14 +81,14 @@ const toLocaleDateString = (date: Date) => (
81
81
  },
82
82
  ) => {
83
83
  const dateStringParts: string[] = [];
84
- processWeekday(dateStringParts, weekday, date);
85
- const dateConnection = month === 'numeric' || month === '2-digit' ? '.' : '';
84
+ processWeekday(dateStringParts, weekday, date);
85
+ const dateConnection = month === 'numeric' || month === '2-digit' ? '.' : '';
86
86
  const dateParts: string[] = [];
87
87
  processDay(dateParts, day, date);
88
- if ((month === 'narrow' ||
89
- month === 'short' ||
90
- month === 'long') &&
91
- day != null
88
+ if ((month === 'narrow'
89
+ || month === 'short'
90
+ || month === 'long')
91
+ && day != null
92
92
  ) {
93
93
  dateParts.push(' ');
94
94
  }
@@ -46,7 +46,7 @@ export type Props = {
46
46
  options?: DateTimeFormatOptions,
47
47
  };
48
48
 
49
- export type LocaleData = {
49
+ export type LocaleData = {
50
50
  narrow: string[];
51
51
  short: string[];
52
52
  long: string[];
@@ -152,14 +152,6 @@ export class ListBase<T extends Item<U>, U>
152
152
  }
153
153
  };
154
154
 
155
- shouldPreventBlur(event: KeyboardEvent) {
156
- const { horizontal, preventBlurOnLongPress } = this.props;
157
- return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
158
- (preventBlurOnLongPress.forward && isForward(event, horizontal))
159
- || (preventBlurOnLongPress.backward && isBackward(event, horizontal))
160
- ));
161
- }
162
-
163
155
  getRefAtIndex(i: number): Reference<T> {
164
156
  const ref = this.itemRefs[i];
165
157
  if (!ref) {
@@ -201,6 +193,14 @@ export class ListBase<T extends Item<U>, U>
201
193
  this.fastScrollingCounter.reset();
202
194
  }
203
195
 
196
+ shouldPreventBlur(event: KeyboardEvent) {
197
+ const { horizontal, preventBlurOnLongPress } = this.props;
198
+ return (this.preventBlurCounter >= PREVENT_BLUR_ON_LONG_PRESS_KEYDOWNS && (
199
+ (preventBlurOnLongPress.forward && isForward(event, horizontal))
200
+ || (preventBlurOnLongPress.backward && isBackward(event, horizontal))
201
+ ));
202
+ }
203
+
204
204
  animatedScrollStarted() {
205
205
  this.isAnimatedScrollRunning = true;
206
206
  }
@@ -1,6 +1,6 @@
1
1
  import { Component, createRef, DeclareProps } from '@24i/bigscreen-sdk/jsx';
2
2
  import { PlayerBase } from '@24i/player-base';
3
- import { switchByKey } from '@24i/bigscreen-sdk/device/keymap';
3
+ import { FF, REW, switchByKey } from '@24i/bigscreen-sdk/device/keymap';
4
4
  import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
5
5
  import {
6
6
  IDisplayToggler, getDisplayToggler, DISPLAY_NONE,
@@ -23,6 +23,7 @@ type Props = {
23
23
  onClose?: () => void,
24
24
  onHide?: () => void,
25
25
  isPauseAllowed?: () => boolean,
26
+ showSubtitlesPicker?: () => void,
26
27
  };
27
28
 
28
29
  export class PlayerUI extends Component<Props>
@@ -84,8 +85,17 @@ export class PlayerUI extends Component<Props>
84
85
  STOP: this.onKeyStop,
85
86
  FF: this.onKeyForward,
86
87
  REW: this.onKeyRewind,
88
+ SUBTITLES: this.onKeySubtitles,
87
89
  });
88
- if (processed) stopEvent(event);
90
+ if (processed && !FF.is(event) && !REW.is(event)) stopEvent(event);
91
+ };
92
+
93
+ onKeySubtitles = () => {
94
+ const { showSubtitlesPicker } = this.props;
95
+ if (showSubtitlesPicker) {
96
+ showSubtitlesPicker();
97
+ }
98
+ return true;
89
99
  };
90
100
 
91
101
  onKeyPlay = () => {
@@ -103,7 +113,8 @@ export class PlayerUI extends Component<Props>
103
113
  };
104
114
 
105
115
  onKeyPause = () => {
106
- if (!this.props.isPauseAllowed()) return false;
116
+ const { isPauseAllowed } = this.props;
117
+ if (!isPauseAllowed()) return false;
107
118
  if (this.seeking.isAutomaticallySeeking()) {
108
119
  this.seeking.stopAutomaticSeek();
109
120
  if (this.isPlaying()) this.trigger(PlayerAction.PAUSE);
@@ -118,7 +129,8 @@ export class PlayerUI extends Component<Props>
118
129
  };
119
130
 
120
131
  onKeyPlayPause = () => {
121
- if (!this.props.isPauseAllowed()) return false;
132
+ const { isPauseAllowed } = this.props;
133
+ if (!isPauseAllowed()) return false;
122
134
  if (this.seeking.isSeeking()) {
123
135
  this.seeking.confirmSeek();
124
136
  if (!this.isPlaying()) this.trigger(PlayerAction.PLAY);
@@ -137,33 +149,22 @@ export class PlayerUI extends Component<Props>
137
149
  };
138
150
 
139
151
  onKeyForward = () => {
140
- if (this.seeking.isAutomaticallySeekingBackward) {
141
- this.seeking.stopAutomaticSeek();
142
- return true;
143
- }
144
- if (!this.seeking.isAutomaticallySeekingForward) {
145
- this.seeking.startAutomaticSeekForward();
146
- return true;
147
- }
148
- return false;
152
+ if (this.seeking.isAutomaticallySeekingBackward) this.seeking.stopAutomaticSeek();
153
+ this.disableHiding();
154
+ this.seeking.automaticSeekForward();
155
+ return true;
149
156
  };
150
157
 
151
158
  onKeyRewind = () => {
152
- if (this.seeking.isAutomaticallySeekingForward) {
153
- this.seeking.stopAutomaticSeek();
154
- return true;
155
- }
156
- if (!this.seeking.isAutomaticallySeekingBackward) {
157
- this.seeking.startAutomaticSeekBackward();
158
- return true;
159
- }
160
- return false;
161
- };
159
+ if (this.seeking.isAutomaticallySeekingForward) this.seeking.stopAutomaticSeek();
160
+ this.disableHiding();
161
+ this.seeking.automaticSeekBackward();
162
+ return true;
162
163
 
163
- getPlayer = () => {
164
- return this.player!;
165
164
  };
166
165
 
166
+ getPlayer = () => this.player!;
167
+
167
168
  setPlayer(player: PlayerBase<any>) {
168
169
  this.player = player;
169
170
  this.seeking.setPlayer(player);
@@ -11,7 +11,7 @@ import { PlayerUICommonProps } from './types';
11
11
  const CONSECUTIVE_TIMEOUT = 500;
12
12
  const FAST_TRESHOLD = 5;
13
13
  const SLOW_TIME = 10000;
14
- const FAST_TIME = 20000;
14
+ const FAST_TIME = 30000;
15
15
  const SAFE_END_MARGIN = 10000;
16
16
  const TO_PERCENT = 100;
17
17
 
@@ -26,9 +26,9 @@ export class Seekbar extends Component<Props> implements IFocusable {
26
26
 
27
27
  seekTime = createRef<PlayerTime>();
28
28
 
29
- leftConsecutives = 0;
29
+ leftConsecutiveness = 0;
30
30
 
31
- rightConsecutives = 0;
31
+ rightConsecutiveness = 0;
32
32
 
33
33
  leftConsecutiveTimeout = createTimeout();
34
34
 
@@ -51,6 +51,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
51
51
  document.addEventListener('mouseup', this.onMouseUp);
52
52
  document.addEventListener('mousemove', this.onMouseMove);
53
53
  ui.seeking.addEventListener('seektimeupdate', this.onSeekTimeUpdate);
54
+ ui.seeking.addEventListener('stopautomaticseek', this.onStopAutomaticSeek);
54
55
  ui.getPlayer().addEventListener('timeupdate', this.onTimeUpdate);
55
56
  ui.getPlayer().addEventListener('durationchange', this.onDurationUpdate);
56
57
  }
@@ -60,6 +61,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
60
61
  document.removeEventListener('mouseup', this.onMouseUp);
61
62
  document.removeEventListener('mousemove', this.onMouseMove);
62
63
  ui.seeking.removeEventListener('seektimeupdate', this.onSeekTimeUpdate);
64
+ ui.seeking.removeEventListener('stopautomaticseek', this.onStopAutomaticSeek);
63
65
  ui.getPlayer().removeEventListener('timeupdate', this.onTimeUpdate);
64
66
  ui.getPlayer().removeEventListener('durationchange', this.onDurationUpdate);
65
67
  }
@@ -114,13 +116,13 @@ export class Seekbar extends Component<Props> implements IFocusable {
114
116
 
115
117
  onLeft = () => {
116
118
  const { ui } = this.props;
117
- this.leftConsecutives += 1;
119
+ this.leftConsecutiveness += 1;
118
120
  if (ui.seeking.isAutomaticallySeeking()) {
119
121
  ui.seeking.stopAutomaticSeek();
120
122
  }
121
- ui.seeking.seekBackward(this.leftConsecutives >= FAST_TRESHOLD ? FAST_TIME : SLOW_TIME);
123
+ ui.seeking.seekBackward(this.leftConsecutiveness >= FAST_TRESHOLD ? FAST_TIME : SLOW_TIME);
122
124
  this.leftConsecutiveTimeout.set(() => {
123
- this.leftConsecutives = 0;
125
+ this.leftConsecutiveness = 0;
124
126
  }, CONSECUTIVE_TIMEOUT);
125
127
  return true;
126
128
  };
@@ -129,14 +131,14 @@ export class Seekbar extends Component<Props> implements IFocusable {
129
131
  const { ui } = this.props;
130
132
  const { seekTime } = ui.seeking;
131
133
  if (seekTime == null && this.currentTime + SLOW_TIME >= this.duration) return true;
132
- this.rightConsecutives += 1;
134
+ this.rightConsecutiveness += 1;
133
135
  if (ui.seeking.isAutomaticallySeeking()) {
134
136
  ui.seeking.stopAutomaticSeek();
135
137
  }
136
- const isFastSeeking = this.rightConsecutives >= FAST_TRESHOLD;
138
+ const isFastSeeking = this.rightConsecutiveness >= FAST_TRESHOLD;
137
139
  if (
138
- (seekTime != null &&
139
- (seekTime + SLOW_TIME >= this.duration - SAFE_END_MARGIN
140
+ (seekTime != null
141
+ && (seekTime + SLOW_TIME >= this.duration - SAFE_END_MARGIN
140
142
  || (isFastSeeking && seekTime + FAST_TIME >= this.duration - SAFE_END_MARGIN)))
141
143
  || (seekTime == null && this.currentTime >= this.duration - SAFE_END_MARGIN)
142
144
  ) {
@@ -145,7 +147,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
145
147
  ui.seeking.seekForward(isFastSeeking ? FAST_TIME : SLOW_TIME);
146
148
  }
147
149
  this.rightConsecutiveTimeout.set(() => {
148
- this.rightConsecutives = 0;
150
+ this.rightConsecutiveness = 0;
149
151
  }, CONSECUTIVE_TIMEOUT);
150
152
  return true;
151
153
  };
@@ -163,6 +165,11 @@ export class Seekbar extends Component<Props> implements IFocusable {
163
165
  this.updateSeekBar();
164
166
  };
165
167
 
168
+ onStopAutomaticSeek = () => {
169
+ const { ui } = this.props;
170
+ ui.enableHiding();
171
+ };
172
+
166
173
  onTimeUpdate = ({ currentTime }: ITimeUpdateEvent) => {
167
174
  const { ui } = this.props;
168
175
  this.currentTime = currentTime;
@@ -1,9 +1,12 @@
1
1
  import { IEvents, EventMapType, EventsManager } from '@24i/bigscreen-sdk/events-manager';
2
- import { createInterval } from '@24i/bigscreen-sdk/utils/timers';
2
+ import { createInterval, createTimeout } from '@24i/bigscreen-sdk/utils/timers';
3
3
  import { PlayerBase } from '@24i/player-base';
4
4
 
5
- const DEFAULT_SEEK_TIME = 20000;
5
+ const DEFAULT_SEEK_TIME = 10000;
6
+ const FAST_SEEK_TIME = 30000;
6
7
  const AUTOMATIC_SEEK_INTERVAL = 300;
8
+ const FAST_THRESHOLD = 5;
9
+ const CONSECUTIVE_TIMEOUT = 500;
7
10
 
8
11
  interface SeekingEventsMap extends EventMapType {
9
12
  'seektimeupdate': (payload: number) => void,
@@ -29,6 +32,14 @@ export class Seeking implements IEvents<SeekingEventsMap> {
29
32
 
30
33
  isAutomaticallySeekingBackward = false;
31
34
 
35
+ ffConsecutiveness = 0;
36
+
37
+ rewConsecutiveness = 0;
38
+
39
+ ffConsecutiveTimeout = createTimeout();
40
+
41
+ rewConsecutiveTimeout = createTimeout();
42
+
32
43
  constructor(player: PlayerBase<any> | null = null) {
33
44
  this.player = player;
34
45
  }
@@ -76,28 +87,53 @@ export class Seeking implements IEvents<SeekingEventsMap> {
76
87
  this.triggerEvent('seektimeupdate', this.seekTime!);
77
88
  }
78
89
 
79
- startAutomaticSeekForward() {
80
- this.stopAutomaticSeek();
90
+ automaticSeekForward() {
91
+ if (this.isAutomaticallySeekingForward) {
92
+ this.ffConsecutiveTimeout.set(() => {
93
+ this.ffConsecutiveness = 0;
94
+ }, CONSECUTIVE_TIMEOUT);
95
+ this.ffConsecutiveness += 1;
96
+ if (this.ffConsecutiveness === FAST_THRESHOLD) {
97
+ this.automaticSeekInterval.set(
98
+ () => this.seekForward(FAST_SEEK_TIME),
99
+ AUTOMATIC_SEEK_INTERVAL,
100
+ );
101
+ }
102
+ return;
103
+ }
81
104
  this.isAutomaticallySeekingForward = true;
105
+ this.ffConsecutiveness += 1;
82
106
  this.seekForward();
83
- this.automaticSeekInterval.set(() => {
84
- this.seekForward();
85
- }, AUTOMATIC_SEEK_INTERVAL);
107
+ this.automaticSeekInterval.set(() => this.seekForward(), AUTOMATIC_SEEK_INTERVAL);
86
108
  }
87
109
 
88
- startAutomaticSeekBackward() {
89
- this.stopAutomaticSeek();
110
+ automaticSeekBackward() {
111
+ if (this.isAutomaticallySeekingBackward) {
112
+ this.rewConsecutiveTimeout.set(() => {
113
+ this.rewConsecutiveness = 0;
114
+ }, CONSECUTIVE_TIMEOUT);
115
+ this.rewConsecutiveness += 1;
116
+ if (this.rewConsecutiveness === FAST_THRESHOLD) {
117
+ this.automaticSeekInterval.set(
118
+ () => this.seekBackward(FAST_SEEK_TIME),
119
+ AUTOMATIC_SEEK_INTERVAL,
120
+ );
121
+ }
122
+ return;
123
+ }
90
124
  this.isAutomaticallySeekingBackward = true;
125
+ this.rewConsecutiveness += 1;
91
126
  this.seekBackward();
92
- this.automaticSeekInterval.set(() => {
93
- this.seekBackward();
94
- }, AUTOMATIC_SEEK_INTERVAL);
127
+ this.automaticSeekInterval.set(() => this.seekBackward(), AUTOMATIC_SEEK_INTERVAL);
95
128
  }
96
129
 
97
130
  stopAutomaticSeek() {
131
+ if (this.isAutomaticallySeeking()) this.triggerEvent('stopautomaticseek');
98
132
  this.automaticSeekInterval.clear();
99
133
  this.isAutomaticallySeekingForward = false;
100
134
  this.isAutomaticallySeekingBackward = false;
135
+ this.ffConsecutiveness = 0;
136
+ this.rewConsecutiveness = 0;
101
137
  }
102
138
 
103
139
  cancelSeek() {
@@ -13,12 +13,16 @@ const getSeekingMock = (): jest.Mocked<IPlayerUI['seeking']> => ({
13
13
  isAutomaticallySeeking: jest.fn(),
14
14
  isAutomaticallySeekingForward: false,
15
15
  isAutomaticallySeekingBackward: false,
16
+ ffConsecutiveness: 0,
17
+ rewConsecutiveness: 0,
18
+ ffConsecutiveTimeout: null!,
19
+ rewConsecutiveTimeout: null!,
16
20
  isSeeking: jest.fn(),
17
21
  startSeeking: jest.fn(),
18
22
  seekForward: jest.fn(),
19
23
  seekBackward: jest.fn(),
20
- startAutomaticSeekForward: jest.fn(),
21
- startAutomaticSeekBackward: jest.fn(),
24
+ automaticSeekForward: jest.fn(),
25
+ automaticSeekBackward: jest.fn(),
22
26
  stopAutomaticSeek: jest.fn(),
23
27
  cancelSeek: jest.fn(),
24
28
  confirmSeek: jest.fn(),
@@ -31,8 +35,8 @@ const getSeekingMock = (): jest.Mocked<IPlayerUI['seeking']> => ({
31
35
 
32
36
  const PLAYER_MOCK_DURATION = 60000;
33
37
 
34
- export const getPlayerMock = (): PlayerBase<any> =>{
35
- const playerMock = new PlayerBaseMock();
38
+ export const getPlayerMock = (): PlayerBase<any> => {
39
+ const playerMock = new PlayerBaseMock();
36
40
  jest.spyOn(playerMock, 'duration', 'get').mockReturnValue(PLAYER_MOCK_DURATION);
37
41
 
38
42
  return playerMock;
@@ -63,9 +63,9 @@ export const formatTime = (duration: number, pattern: string) => {
63
63
  const unitValues = calculateUnitValues(duration, units);
64
64
  let result: string = pattern;
65
65
  forEach(matches, (match) => {
66
- const formatedValue = Number.isNaN(unitValues[match.unit]) ?
67
- '--' :
68
- unitValues[match.unit]!.toString().padStart(match.count, '0');
66
+ const formatedValue = Number.isNaN(unitValues[match.unit])
67
+ ? '--'
68
+ : unitValues[match.unit]!.toString().padStart(match.count, '0');
69
69
  result = result.replace(
70
70
  match.completeMatch,
71
71
  formatedValue,
@@ -0,0 +1,47 @@
1
+ import { HOUR_IN_MS, MINUTE_IN_MS } from '@24i/bigscreen-sdk/utils';
2
+ import * as TwentyFourIMediaTimeApi from '../services/24iMediaTimeApi';
3
+
4
+ type ParamOptions = TwentyFourIMediaTimeApi.ParamOptions;
5
+
6
+ const defaultOptions: ParamOptions = {
7
+ timeoutInMs: 30000,
8
+ retryConfig: {
9
+ retry: 2,
10
+ },
11
+ };
12
+
13
+ /**
14
+ * Returns a tring in ISO format followed by +/-hh:mm suffix for timezone offset.
15
+ * @param resp Time api response.
16
+ * @returns ISO string with timezone data.
17
+ */
18
+ const getIsoDateString = (resp: TwentyFourIMediaTimeApi.ServiceDataResponse): string => {
19
+ let datetime = new Date(resp.timestamp + resp.timezone_offset).toISOString();
20
+ const offset = resp.timezone_offset / HOUR_IN_MS;
21
+ if (offset) {
22
+ const sign = offset > 0 ? '+' : '-';
23
+ const hours = Math.abs(offset);
24
+ const minutes = Math.floor((resp.timezone_offset % HOUR_IN_MS) / MINUTE_IN_MS);
25
+ // eslint-disable-next-line no-magic-numbers, sonarjs/no-nested-template-literals
26
+ const timeString = `${sign + `0${hours}`.slice(-2)}:${`0${minutes}`.slice(-2)}`;
27
+ datetime = datetime.replace('Z', timeString);
28
+ }
29
+ return datetime;
30
+ };
31
+
32
+ /**
33
+ * Wrapper over "TwentyFourIMediaTimeApi.getTime" method requesting the current time
34
+ * from timekeeping server. Transforms the result to ISO string with timezone offset data
35
+ * included.
36
+ *
37
+ * @param options Options object
38
+ */
39
+ export const getTime = async (options: ParamOptions = {}) => {
40
+ const opts = { ...defaultOptions, ...options };
41
+ const resp = await TwentyFourIMediaTimeApi.getTime(opts);
42
+
43
+ if ('timestamp' in resp && 'timezone_offset' in resp && 'in_dst' in resp) {
44
+ return { datetime: getIsoDateString(resp) };
45
+ }
46
+ return null;
47
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Package "24iMediaTimeApi" provides a single function to obtain the current time
3
+ * adjusted by local timezone and daylight savings time for use in time package,
4
+ * from 24i time API at https://time.24imedia.tv/
5
+ */
6
+ import {
7
+ xhrSendRetry, type RetryConfig, type XhrResponse,
8
+ } from '@24i/bigscreen-sdk/utils/xhr';
9
+
10
+ const API_URL = 'https://time.24imedia.tv/';
11
+
12
+ export type Options = {
13
+ /** The request timeout in milliseconds. The value 0 for no timeout */
14
+ timeoutInMs: number,
15
+ /** Retry configuration */
16
+ retryConfig: RetryConfig,
17
+ };
18
+ export type ParamOptions = Partial<Options>;
19
+
20
+ export type ServiceDataResponse = {
21
+ /** Is dailight savings time */
22
+ in_dst: boolean,
23
+ /** Current time in GMT, in milliseconds */
24
+ timestamp: number,
25
+ /** Timezeone offset in milliseconds */
26
+ timezone_offset: number,
27
+ };
28
+
29
+ /**
30
+ * Request the current time based on public IP of request.
31
+ *
32
+ * @param options Options object
33
+ */
34
+ export async function getTime(options?: ParamOptions): Promise<ServiceDataResponse> {
35
+ try {
36
+ const resp = (await xhrSendRetry(API_URL, options)) as XhrResponse;
37
+ const data: any = resp.dataJson || {};
38
+ return data as ServiceDataResponse;
39
+ } catch (e) {
40
+ console.error('[24iMediaTimeApi] Failed to get time', e);
41
+ throw e;
42
+ }
43
+ }
@@ -0,0 +1,6 @@
1
+ export {
2
+ Options,
3
+ ParamOptions,
4
+ ServiceDataResponse,
5
+ getTime,
6
+ } from './24iMediaTimeApi';
@@ -27,6 +27,7 @@
27
27
  // Victor Magalhães <https://github.com/vhfmag>
28
28
  // Dale Tan <https://github.com/hellatan>
29
29
  // Priyanshu Rav <https://github.com/priyanshurav>
30
+ // Dmitry Semigradsky <https://github.com/Semigradsky>
30
31
  // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
31
32
  // TypeScript Version: 2.8
32
33
 
@@ -1250,6 +1251,8 @@ declare namespace React {
1250
1251
  target: EventTarget & T;
1251
1252
  }
1252
1253
 
1254
+ export type ModifierKey = "Alt" | "AltGraph" | "CapsLock" | "Control" | "Fn" | "FnLock" | "Hyper" | "Meta" | "NumLock" | "ScrollLock" | "Shift" | "Super" | "Symbol" | "SymbolLock";
1255
+
1253
1256
  interface KeyboardEvent<T = Element> extends UIEvent<T, NativeKeyboardEvent> {
1254
1257
  altKey: boolean;
1255
1258
  /** @deprecated */
@@ -1259,7 +1262,7 @@ declare namespace React {
1259
1262
  /**
1260
1263
  * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
1261
1264
  */
1262
- getModifierState(key: string): boolean;
1265
+ getModifierState(key: ModifierKey): boolean;
1263
1266
  /**
1264
1267
  * See the [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#named-key-attribute-values). for possible values
1265
1268
  */
@@ -1285,7 +1288,7 @@ declare namespace React {
1285
1288
  /**
1286
1289
  * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
1287
1290
  */
1288
- getModifierState(key: string): boolean;
1291
+ getModifierState(key: ModifierKey): boolean;
1289
1292
  metaKey: boolean;
1290
1293
  movementX: number;
1291
1294
  movementY: number;
@@ -1304,7 +1307,7 @@ declare namespace React {
1304
1307
  /**
1305
1308
  * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method.
1306
1309
  */
1307
- getModifierState(key: string): boolean;
1310
+ getModifierState(key: ModifierKey): boolean;
1308
1311
  metaKey: boolean;
1309
1312
  shiftKey: boolean;
1310
1313
  targetTouches: TouchList;
@@ -1459,6 +1462,8 @@ declare namespace React {
1459
1462
  onProgressCapture?: ReactEventHandler<T> | undefined;
1460
1463
  onRateChange?: ReactEventHandler<T> | undefined;
1461
1464
  onRateChangeCapture?: ReactEventHandler<T> | undefined;
1465
+ onResize?: ReactEventHandler<T> | undefined;
1466
+ onResizeCapture?: ReactEventHandler<T> | undefined;
1462
1467
  onSeeked?: ReactEventHandler<T> | undefined;
1463
1468
  onSeekedCapture?: ReactEventHandler<T> | undefined;
1464
1469
  onSeeking?: ReactEventHandler<T> | undefined;
@@ -1858,6 +1863,7 @@ declare namespace React {
1858
1863
  hidden?: boolean | undefined;
1859
1864
  id?: string | undefined;
1860
1865
  lang?: string | undefined;
1866
+ nonce?: string | undefined;
1861
1867
  placeholder?: string | undefined;
1862
1868
  slot?: string | undefined;
1863
1869
  spellCheck?: Booleanish | undefined;
@@ -1978,7 +1984,6 @@ declare namespace React {
1978
1984
  multiple?: boolean | undefined;
1979
1985
  muted?: boolean | undefined;
1980
1986
  name?: string | undefined;
1981
- nonce?: string | undefined;
1982
1987
  noValidate?: boolean | undefined;
1983
1988
  open?: boolean | undefined;
1984
1989
  optimum?: number | undefined;
@@ -2391,7 +2396,6 @@ declare namespace React {
2391
2396
  defer?: boolean | undefined;
2392
2397
  integrity?: string | undefined;
2393
2398
  noModule?: boolean | undefined;
2394
- nonce?: string | undefined;
2395
2399
  referrerPolicy?: HTMLAttributeReferrerPolicy | undefined;
2396
2400
  src?: string | undefined;
2397
2401
  type?: string | undefined;
@@ -2422,7 +2426,6 @@ declare namespace React {
2422
2426
 
2423
2427
  interface StyleHTMLAttributes<T> extends HTMLAttributes<T> {
2424
2428
  media?: string | undefined;
2425
- nonce?: string | undefined;
2426
2429
  scoped?: boolean | undefined;
2427
2430
  type?: string | undefined;
2428
2431
  }
@@ -4,11 +4,12 @@ const trimWithEllipsis = (
4
4
  row: HTMLSpanElement | HTMLDivElement,
5
5
  requestedNumberOfLines: number,
6
6
  ellipsis = ELLIPSIS,
7
- ending = false
7
+ ending = false,
8
8
  ) : void => {
9
9
  if (!row.textContent) {
10
10
  return;
11
11
  }
12
+ const { visibility } = row.style;
12
13
  row.style.visibility = 'hidden';
13
14
  const lineHeightString = window.getComputedStyle(row, null).getPropertyValue('line-height');
14
15
  const lineHeight = parseInt(lineHeightString, 10);
@@ -27,6 +28,7 @@ const trimWithEllipsis = (
27
28
  } while (numberOfLines <= requestedNumberOfLines
28
29
  && currentPosition < originalTextContent.length);
29
30
  row.textContent = stringBuffer.substring(0, stringBuffer.length - 1) + ellipsis;
31
+
30
32
  } else {
31
33
  let currentPosition = originalTextContent.length - 1;
32
34
  do {
@@ -39,21 +41,17 @@ const trimWithEllipsis = (
39
41
  row.textContent = `${ellipsis}${stringBuffer.substring(1)}`;
40
42
  }
41
43
  }
42
- row.style.visibility = '';
44
+ row.style.visibility = visibility;
43
45
  };
44
46
 
45
47
  export const trimWithLeadingEllipsis = (
46
48
  row: HTMLSpanElement | HTMLDivElement,
47
49
  requestedNumberOfLines: number,
48
50
  ellipsis = ELLIPSIS,
49
- ) => {
50
- return trimWithEllipsis(row, requestedNumberOfLines, ellipsis, false);
51
- };
51
+ ) => trimWithEllipsis(row, requestedNumberOfLines, ellipsis, false);
52
52
 
53
53
  export const trimWithEndingEllipsis = (
54
54
  row: HTMLSpanElement | HTMLDivElement,
55
55
  requestedNumberOfLines: number,
56
56
  ellipsis = ELLIPSIS,
57
- ) => {
58
- return trimWithEllipsis(row, requestedNumberOfLines, ellipsis, true);
59
- };
57
+ ) => trimWithEllipsis(row, requestedNumberOfLines, ellipsis, true);