@24i/bigscreen-sdk 1.0.8 → 1.0.9-alpha.2203

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +7 -7
  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/developer-tools/src/PlayerDebugWindow/constants.ts +15 -0
  10. package/packages/digital-clock/src/DigitalClock.tsx +1 -1
  11. package/packages/epg/src/v2/DataManager.ts +1 -0
  12. package/packages/epg/src/v2/DatePicker.tsx +4 -3
  13. package/packages/epg/src/v2/DateToast/DateToast.tsx +10 -8
  14. package/packages/epg/src/v2/Day.tsx +4 -3
  15. package/packages/epg/src/v2/Epg.tsx +2 -4
  16. package/packages/epg/src/v2/ProgramInfo/utils.ts +4 -3
  17. package/packages/epg/src/v2/TimelineSection.tsx +4 -1
  18. package/packages/focus/src/Layout/Matrix.tsx +18 -18
  19. package/packages/grid/src/Base/Base.tsx +10 -10
  20. package/packages/icon/src/Icon.tsx +1 -2
  21. package/packages/keyboard/src/WhitelabelKeyboard/Keyboard.tsx +20 -18
  22. package/packages/l10n/src/date/common.ts +6 -6
  23. package/packages/l10n/src/date/de.ts +6 -6
  24. package/packages/l10n/src/date/en-common.ts +1 -1
  25. package/packages/l10n/src/date/fr.ts +5 -4
  26. package/packages/l10n/src/date/he.ts +6 -6
  27. package/packages/l10n/src/date/types.ts +1 -1
  28. package/packages/list/src/Base/Base.tsx +8 -8
  29. package/packages/player-ui/src/PlayerUI.tsx +5 -5
  30. package/packages/player-ui/src/Seekbar.tsx +6 -3
  31. package/packages/player-ui/src/Seeking.ts +2 -2
  32. package/packages/player-ui/src/mocks.ts +2 -2
  33. package/packages/player-ui/src/timeUtils.ts +3 -3
  34. package/packages/time/README.md +4 -1
  35. package/packages/time/src/Time.ts +14 -2
  36. package/packages/time/src/adapters/24iMediaTimeApi.ts +2 -2
  37. package/packages/time/src/constants.ts +1 -0
  38. package/packages/types-bigscreen-jsx/index.d.ts +9 -6
  39. package/packages/utils/src/textUtils.ts +3 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@24i/bigscreen-sdk",
3
- "version": "1.0.8",
3
+ "version": "1.0.9-alpha.2203",
4
4
  "description": "SmartApps BIGscreen SDK monorepo",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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"
@@ -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,
@@ -49,6 +49,21 @@ export const MEDIA_INFO_DESCRIPTION: Record<string, MediaInfo> = {
49
49
  audioCodec: {
50
50
  title: 'Audio codec',
51
51
  },
52
+ audioBitrate: {
53
+ title: 'Audio bitrate',
54
+ },
55
+ audioSamplingRate: {
56
+ title: 'Audio sampling rate',
57
+ },
58
+ audioLanguage: {
59
+ title: 'Audio language',
60
+ },
61
+ subtitlesAvailable: {
62
+ title: 'Subtitles available',
63
+ },
64
+ availableSubtitlesListString: {
65
+ title: 'List of available subtitles',
66
+ },
52
67
  drmProtection: {
53
68
  title: 'DRM protection',
54
69
  },
@@ -40,7 +40,7 @@ export class DigitalClock extends Component<Props> {
40
40
 
41
41
  setTime() {
42
42
  const { withSeconds } = this.props;
43
- const currentTimestampMs = time.getCurrentUnixTime();
43
+ const currentTimestampMs = time.getCurrentTime().getTime();
44
44
  if (this.oldUnixTimestampMs !== currentTimestampMs) {
45
45
  const options = withSeconds ? WITH_SECONDS_OPTIONS : WITHOUT_SECONDS_OPTIONS;
46
46
  this.clearMount((
@@ -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) {
@@ -1,4 +1,5 @@
1
1
  import { Component, createRef, DeclareProps } from '@24i/bigscreen-sdk/jsx';
2
+ import { time } from '@24i/bigscreen-sdk/time';
2
3
  import { Picker } from '@24i/bigscreen-sdk/picker';
3
4
  import { DatePicker as NativeDatePicker } from '@24i/bigscreen-sdk/picker/DatePicker';
4
5
  import { DAY_IN_MS, HOUR_IN_MS, MINUTE_IN_MS, SECOND_IN_MS } from '@24i/bigscreen-sdk/utils';
@@ -46,9 +47,9 @@ const prepareDateData = ({
46
47
 
47
48
  const defaultFormat = (date: Date) => {
48
49
  const locale = i18n.getLanguage();
49
- return l10n.date(date, locale).toLocaleDateString(
50
- { weekday: 'short', day: 'numeric', month: 'short' }
51
- );
50
+ return l10n
51
+ .date(date.getTime() + time.getTimezoneOffsetMs(), locale)
52
+ .toLocaleDateString({ weekday: 'short', day: 'numeric', month: 'short' });
52
53
  };
53
54
 
54
55
  export class DatePicker extends Component<Props> implements IDatePicker {
@@ -6,28 +6,30 @@ import { time } from '@24i/bigscreen-sdk/time';
6
6
  import { HOUR_IN_MS } from '@24i/bigscreen-sdk/utils/timeConstants';
7
7
  import { InternalEpg, Plugin } from '../interface';
8
8
 
9
- const formatTime = (timestamp: number) => l10n.date(timestamp).toLocaleTimeString(
9
+ const formatTime = (localTimestamp: number) => l10n.date(localTimestamp).toLocaleTimeString(
10
10
  { hour: 'numeric', minute: '2-digit' },
11
11
  );
12
12
 
13
- const formatDate = (timestamp: number) => {
14
- if (time.isToday(timestamp)) {
13
+ const formatDate = (localTimestamp: number) => {
14
+ if (time.isToday(localTimestamp)) {
15
15
  return i18n.t('date.today');
16
16
  }
17
- if (time.isYesterday(timestamp)) {
17
+ if (time.isYesterday(localTimestamp)) {
18
18
  return i18n.t('date.yesterday');
19
19
  }
20
- if (time.isTomorrow(timestamp)) {
20
+ if (time.isTomorrow(localTimestamp)) {
21
21
  return i18n.t('date.tomorrow');
22
22
  }
23
- return l10n.date(timestamp).toLocaleString({
23
+ return l10n.date(localTimestamp).toLocaleString({
24
24
  weekday: 'short',
25
25
  day: 'numeric',
26
26
  month: 'short',
27
27
  });
28
28
  };
29
29
 
30
- const formatDatetime = (timestamp: number) => `${formatDate(timestamp)} • ${formatTime(timestamp)}`;
30
+ const formatDatetime = (localTimestamp: number) => (
31
+ `${formatDate(localTimestamp)} • ${formatTime(localTimestamp)}`
32
+ );
31
33
 
32
34
  type Props = {
33
35
  epg: InternalEpg<any, any, any, any>,
@@ -44,7 +46,7 @@ export class DateToast extends Component<Props> implements Plugin {
44
46
 
45
47
  onScrolledToTimestamp(timestamp: number) {
46
48
  const { epg } = this.props;
47
- const day = formatDatetime(timestamp);
49
+ const day = formatDatetime(timestamp + time.getTimezoneOffsetMs());
48
50
  if (this.lastDay === null) { // we do not want to display it after render and first move
49
51
  this.lastDay = day;
50
52
  return;
@@ -1,4 +1,5 @@
1
1
  import { Component, createRef, setText } from '@24i/bigscreen-sdk/jsx';
2
+ import { time } from '@24i/bigscreen-sdk/time';
2
3
  import { l10n } from '@24i/bigscreen-sdk/l10n';
3
4
  import { DayProps as Props, Day as IDay } from './interface';
4
5
 
@@ -15,9 +16,9 @@ export class Day extends Component<Props> implements IDay {
15
16
  }
16
17
 
17
18
  private formatCurrentTimestamp() {
18
- return l10n.date(this.timestamp).toLocaleDateString(
19
- { weekday: 'long', day: 'numeric', month: 'short' },
20
- );
19
+ return l10n
20
+ .date(this.timestamp + time.getTimezoneOffsetMs())
21
+ .toLocaleDateString({ weekday: 'long', day: 'numeric', month: 'short' });
21
22
  }
22
23
 
23
24
  render() {
@@ -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,
@@ -1,4 +1,5 @@
1
1
  import { l10n } from '@24i/bigscreen-sdk/l10n';
2
+ import { time } from '@24i/bigscreen-sdk/time';
2
3
  import { Asset } from '@24i/smartapps-datalayer/models';
3
4
 
4
5
  export const cutString = (string: string, length: number) => {
@@ -16,9 +17,9 @@ export const getSubtitle = (asset: Asset) => (
16
17
  : ''
17
18
  );
18
19
 
19
- const toLocaleTimeString = (timestamp: number) => l10n.date(timestamp).toLocaleTimeString(
20
- { hour: 'numeric', minute: '2-digit' },
21
- );
20
+ const toLocaleTimeString = (timestamp: number) => l10n
21
+ .date(timestamp + time.getTimezoneOffsetMs())
22
+ .toLocaleTimeString({ hour: 'numeric', minute: '2-digit' });
22
23
 
23
24
  export const getBroadcastTime = (asset: Asset) => {
24
25
  const start = toLocaleTimeString(asset.program!.startTime);
@@ -1,4 +1,5 @@
1
1
  import { Component, createRef, setText } from '@24i/bigscreen-sdk/jsx';
2
+ import { time } from '@24i/bigscreen-sdk/time';
2
3
  import { l10n } from '@24i/bigscreen-sdk/l10n';
3
4
  import { offsetPosition } from '@24i/bigscreen-sdk/utils';
4
5
  import { TimelineSectionProps as Props, TimelineSection as ITimelineSection } from './interface';
@@ -23,7 +24,9 @@ export class TimelineSection extends Component<Props> implements ITimelineSectio
23
24
  }
24
25
 
25
26
  private formatCurrentTimestamp() {
26
- return l10n.date(this.timestamp).toLocaleTimeString({ hour: 'numeric', minute: '2-digit' });
27
+ return l10n
28
+ .date(this.timestamp + time.getTimezoneOffsetMs())
29
+ .toLocaleTimeString({ hour: 'numeric', minute: '2-digit' });
27
30
  }
28
31
 
29
32
  render() {
@@ -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
  }
@@ -62,8 +62,7 @@ export class Icon<T> extends Component<Props<T>> {
62
62
  return (
63
63
  <div
64
64
  ref={this.ref}
65
- className={`${className
66
- ? className : ''} icon ${icon
65
+ className={`${className || ''} icon ${icon
67
66
  ? this.getClass(icon) : ''} ${initialIsHidden
68
67
  ? DISPLAY_NONE : ''}`}
69
68
  />
@@ -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}
@@ -7,7 +7,7 @@ const NOON = 12;
7
7
 
8
8
  export const SECOND_FROM_END = 2;
9
9
 
10
- export const h24 = (date: Date) => date.getHours().toString();
10
+ export const h24 = (date: Date) => date.getUTCHours().toString();
11
11
 
12
12
  export const hh24 = (date: Date) => {
13
13
  const h = h24(date);
@@ -15,7 +15,7 @@ export const hh24 = (date: Date) => {
15
15
  };
16
16
 
17
17
  export const h12 = (date: Date) => {
18
- const h = date.getHours() % NOON;
18
+ const h = date.getUTCHours() % NOON;
19
19
  return h === 0 ? '12' : h.toString();
20
20
  };
21
21
 
@@ -24,14 +24,14 @@ export const hh12 = (date: Date) => {
24
24
  return h.length === 1 ? `0${h}` : h;
25
25
  };
26
26
 
27
- export const m = (date: Date) => date.getMinutes().toString();
27
+ export const m = (date: Date) => date.getUTCMinutes().toString();
28
28
 
29
29
  export const mm = (date: Date) => {
30
30
  const min = m(date);
31
31
  return min.length === 1 ? `0${min}` : min;
32
32
  };
33
33
 
34
- export const s = (date: Date) => date.getSeconds().toString();
34
+ export const s = (date: Date) => date.getUTCSeconds().toString();
35
35
 
36
36
  export const ss = (date: Date) => {
37
37
  const sec = s(date);
@@ -39,7 +39,7 @@ export const ss = (date: Date) => {
39
39
  };
40
40
 
41
41
  export const amOrPm = (date: Date) => (
42
- date.getHours() < NOON ? 'AM' : 'PM'
42
+ date.getUTCHours() < NOON ? 'AM' : 'PM'
43
43
  );
44
44
 
45
45
  export const h24Mm = (date: Date) => `${h24(date)}:${mm(date)}`;
@@ -107,7 +107,7 @@ export const collonSeparatedHour12MinuteSecond = (date: Date, options: DateTimeF
107
107
 
108
108
  export const processYear = (dateStringParts: string[], year: Year | undefined, date: Date) => {
109
109
  if (typeof year === 'undefined') return;
110
- const fullYear = date.getFullYear().toString();
110
+ const fullYear = date.getUTCFullYear().toString();
111
111
  dateStringParts.push(
112
112
  year === '2-digit'
113
113
  ? fullYear.substring(fullYear.length - SECOND_FROM_END)
@@ -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
  }
@@ -113,7 +113,8 @@ export class PlayerUI extends Component<Props>
113
113
  };
114
114
 
115
115
  onKeyPause = () => {
116
- if (!this.props.isPauseAllowed()) return false;
116
+ const { isPauseAllowed } = this.props;
117
+ if (!isPauseAllowed()) return false;
117
118
  if (this.seeking.isAutomaticallySeeking()) {
118
119
  this.seeking.stopAutomaticSeek();
119
120
  if (this.isPlaying()) this.trigger(PlayerAction.PAUSE);
@@ -128,7 +129,8 @@ export class PlayerUI extends Component<Props>
128
129
  };
129
130
 
130
131
  onKeyPlayPause = () => {
131
- if (!this.props.isPauseAllowed()) return false;
132
+ const { isPauseAllowed } = this.props;
133
+ if (!isPauseAllowed()) return false;
132
134
  if (this.seeking.isSeeking()) {
133
135
  this.seeking.confirmSeek();
134
136
  if (!this.isPlaying()) this.trigger(PlayerAction.PLAY);
@@ -161,9 +163,7 @@ export class PlayerUI extends Component<Props>
161
163
 
162
164
  };
163
165
 
164
- getPlayer = () => {
165
- return this.player!;
166
- };
166
+ getPlayer = () => this.player!;
167
167
 
168
168
  setPlayer(player: PlayerBase<any>) {
169
169
  this.player = player;
@@ -137,8 +137,8 @@ export class Seekbar extends Component<Props> implements IFocusable {
137
137
  }
138
138
  const isFastSeeking = this.rightConsecutiveness >= FAST_TRESHOLD;
139
139
  if (
140
- (seekTime != null &&
141
- (seekTime + SLOW_TIME >= this.duration - SAFE_END_MARGIN
140
+ (seekTime != null
141
+ && (seekTime + SLOW_TIME >= this.duration - SAFE_END_MARGIN
142
142
  || (isFastSeeking && seekTime + FAST_TIME >= this.duration - SAFE_END_MARGIN)))
143
143
  || (seekTime == null && this.currentTime >= this.duration - SAFE_END_MARGIN)
144
144
  ) {
@@ -165,7 +165,10 @@ export class Seekbar extends Component<Props> implements IFocusable {
165
165
  this.updateSeekBar();
166
166
  };
167
167
 
168
- onStopAutomaticSeek = () => this.props.ui.enableHiding();
168
+ onStopAutomaticSeek = () => {
169
+ const { ui } = this.props;
170
+ ui.enableHiding();
171
+ };
169
172
 
170
173
  onTimeUpdate = ({ currentTime }: ITimeUpdateEvent) => {
171
174
  const { ui } = this.props;
@@ -96,7 +96,7 @@ export class Seeking implements IEvents<SeekingEventsMap> {
96
96
  if (this.ffConsecutiveness === FAST_THRESHOLD) {
97
97
  this.automaticSeekInterval.set(
98
98
  () => this.seekForward(FAST_SEEK_TIME),
99
- AUTOMATIC_SEEK_INTERVAL
99
+ AUTOMATIC_SEEK_INTERVAL,
100
100
  );
101
101
  }
102
102
  return;
@@ -116,7 +116,7 @@ export class Seeking implements IEvents<SeekingEventsMap> {
116
116
  if (this.rewConsecutiveness === FAST_THRESHOLD) {
117
117
  this.automaticSeekInterval.set(
118
118
  () => this.seekBackward(FAST_SEEK_TIME),
119
- AUTOMATIC_SEEK_INTERVAL
119
+ AUTOMATIC_SEEK_INTERVAL,
120
120
  );
121
121
  }
122
122
  return;
@@ -35,8 +35,8 @@ const getSeekingMock = (): jest.Mocked<IPlayerUI['seeking']> => ({
35
35
 
36
36
  const PLAYER_MOCK_DURATION = 60000;
37
37
 
38
- export const getPlayerMock = (): PlayerBase<any> =>{
39
- const playerMock = new PlayerBaseMock();
38
+ export const getPlayerMock = (): PlayerBase<any> => {
39
+ const playerMock = new PlayerBaseMock();
40
40
  jest.spyOn(playerMock, 'duration', 'get').mockReturnValue(PLAYER_MOCK_DURATION);
41
41
 
42
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,
@@ -52,7 +52,7 @@ await time.init({
52
52
  ```
53
53
 
54
54
  ### `getCurrentTime(): Date`
55
- It returns date object having local time set to server current time.
55
+ It returns date object having UTC time set to local time.
56
56
 
57
57
  ### `getCurrentUnixTime(): number`
58
58
  It returns UNIX time in milliseconds of server UTC time.
@@ -60,6 +60,9 @@ It returns UNIX time in milliseconds of server UTC time.
60
60
  ### `getTimezoneOffset(): number`
61
61
  It returns timezone offset in minutes, for UTC+1 (Germany) is 60.
62
62
 
63
+ ### `getTimezoneOffsetMs(): number`
64
+ It returns timezone offset in milliseconds, for UTC+1 (Germany) is 3600000.
65
+
63
66
  ### `initStatus: InitStatus`
64
67
  To check what time you are using, just check the `initStatus` property.
65
68
 
@@ -1,6 +1,6 @@
1
1
  import { createInterval } from '@24i/bigscreen-sdk/utils/timers';
2
2
  import { parseIsoDate, getDayStart, getDayEnd } from './utils';
3
- import { InitStatus, ErrorCode } from './constants';
3
+ import { InitStatus, ErrorCode, MS_PER_MINUTE } from './constants';
4
4
  import { Config, TimeServiceResp } from './types';
5
5
 
6
6
  /**
@@ -22,6 +22,9 @@ class Time {
22
22
  /** The difference in milliseconds between System Date and Server Date */
23
23
  offsetMs = 0;
24
24
 
25
+ /** The difference Date object internally adds to timestamp if not asking for UTC time. */
26
+ deviceOffset = new Date().getTimezoneOffset() * MS_PER_MINUTE;
27
+
25
28
  /** Timer for periodical time update */
26
29
  syncInterval = createInterval();
27
30
 
@@ -113,7 +116,7 @@ class Time {
113
116
  this.currentTime = currentTime;
114
117
  this.serverTime = date;
115
118
  this.serverUnixTime = unixTime;
116
- this.offsetMs = this.currentTime.getTime() - this.serverTime.getTime();
119
+ this.offsetMs = this.currentTime.getTime() - this.serverTime.getTime() + this.deviceOffset;
117
120
  this.timezoneOffset = timezoneOffset;
118
121
  }
119
122
 
@@ -168,6 +171,15 @@ class Time {
168
171
  return this.timezoneOffset;
169
172
  }
170
173
 
174
+ /**
175
+ * Returns the timezone offset in milliseconds.
176
+ *
177
+ * @returns Timezone offset in milliseconds, e.g. for UTC+1 (Germany) is 3600000.
178
+ */
179
+ getTimezoneOffsetMs() {
180
+ return this.timezoneOffset * MS_PER_MINUTE;
181
+ }
182
+
171
183
  /**
172
184
  * Returns offset (milliseconds) between System and Server Date object.
173
185
  *
@@ -22,8 +22,8 @@ const getIsoDateString = (resp: TwentyFourIMediaTimeApi.ServiceDataResponse): st
22
22
  const sign = offset > 0 ? '+' : '-';
23
23
  const hours = Math.abs(offset);
24
24
  const minutes = Math.floor((resp.timezone_offset % HOUR_IN_MS) / MINUTE_IN_MS);
25
- // eslint-disable-next-line no-magic-numbers
26
- const timeString = sign + `0${hours}`.slice(-2) + ':' + `0${minutes}`.slice(-2);
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
27
  datetime = datetime.replace('Z', timeString);
28
28
  }
29
29
  return datetime;
@@ -9,5 +9,6 @@ export const ErrorCode = {
9
9
  NO_TIME_SERVICE_PROVIDER: 'NO_TIME_SERVICE_PROVIDER',
10
10
  };
11
11
 
12
+ export const MS_PER_MINUTE = 60000;
12
13
  export const MINUTES_PER_HOUR = 60;
13
14
  export const HOURS_PER_DAY = 24;
@@ -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,7 +4,7 @@ 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;
@@ -48,14 +48,10 @@ export const trimWithLeadingEllipsis = (
48
48
  row: HTMLSpanElement | HTMLDivElement,
49
49
  requestedNumberOfLines: number,
50
50
  ellipsis = ELLIPSIS,
51
- ) => {
52
- return trimWithEllipsis(row, requestedNumberOfLines, ellipsis, false);
53
- };
51
+ ) => trimWithEllipsis(row, requestedNumberOfLines, ellipsis, false);
54
52
 
55
53
  export const trimWithEndingEllipsis = (
56
54
  row: HTMLSpanElement | HTMLDivElement,
57
55
  requestedNumberOfLines: number,
58
56
  ellipsis = ELLIPSIS,
59
- ) => {
60
- return trimWithEllipsis(row, requestedNumberOfLines, ellipsis, true);
61
- };
57
+ ) => trimWithEllipsis(row, requestedNumberOfLines, ellipsis, true);