@screenly/edge-apps 0.0.1 → 0.0.3

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 (143) hide show
  1. package/dist/assets/fonts/Inter-Medium.woff2 +0 -0
  2. package/dist/assets/fonts/Inter-Regular.woff2 +0 -0
  3. package/dist/assets/fonts/Inter-SemiBold.woff2 +0 -0
  4. package/dist/assets/images/icons/chancesleet.svg +4 -0
  5. package/dist/assets/images/icons/clear-night.svg +5 -0
  6. package/dist/assets/images/icons/clear.svg +11 -0
  7. package/dist/assets/images/icons/cloudy.svg +4 -0
  8. package/dist/assets/images/icons/drizzle.svg +5 -0
  9. package/dist/assets/images/icons/fewdrops.svg +4 -0
  10. package/dist/assets/images/icons/fog.svg +6 -0
  11. package/dist/assets/images/icons/haze.svg +6 -0
  12. package/dist/assets/images/icons/mostly-cloudy-night.svg +7 -0
  13. package/dist/assets/images/icons/mostly-cloudy.svg +13 -0
  14. package/dist/assets/images/icons/partially-cloudy-night.svg +6 -0
  15. package/dist/assets/images/icons/partially-cloudy.svg +12 -0
  16. package/dist/assets/images/icons/partlysunny.svg +6 -0
  17. package/dist/assets/images/icons/rain-night.svg +8 -0
  18. package/dist/assets/images/icons/rainy.svg +6 -0
  19. package/dist/assets/images/icons/sleet-night.svg +14 -0
  20. package/dist/assets/images/icons/sleet.svg +4 -0
  21. package/dist/assets/images/icons/snow.svg +19 -0
  22. package/dist/assets/images/icons/thunderstorm-night.svg +9 -0
  23. package/dist/assets/images/icons/thunderstorm.svg +7 -0
  24. package/dist/assets/images/icons/windy.svg +6 -0
  25. package/dist/assets/images/screenly.svg +10 -0
  26. package/dist/components/app-header/app-header.d.ts +43 -0
  27. package/dist/components/app-header/app-header.d.ts.map +1 -0
  28. package/dist/components/app-header/app-header.js +244 -0
  29. package/dist/components/auto-scaler/auto-scaler.d.ts +65 -0
  30. package/dist/components/auto-scaler/auto-scaler.d.ts.map +1 -0
  31. package/dist/components/auto-scaler/auto-scaler.js +304 -0
  32. package/dist/components/brand-logo/brand-logo.d.ts +42 -0
  33. package/dist/components/brand-logo/brand-logo.d.ts.map +1 -0
  34. package/dist/components/brand-logo/brand-logo.js +209 -0
  35. package/dist/components/calendar-views/calendar-view-utils.d.ts +5 -0
  36. package/dist/components/calendar-views/calendar-view-utils.d.ts.map +1 -0
  37. package/dist/components/calendar-views/calendar-view-utils.js +73 -0
  38. package/dist/components/calendar-views/calendar-window-utils.d.ts +9 -0
  39. package/dist/components/calendar-views/calendar-window-utils.d.ts.map +1 -0
  40. package/dist/components/calendar-views/calendar-window-utils.js +57 -0
  41. package/dist/components/calendar-views/daily-calendar-view/daily-calendar-view-styles.d.ts +2 -0
  42. package/dist/components/calendar-views/daily-calendar-view/daily-calendar-view-styles.d.ts.map +1 -0
  43. package/dist/components/calendar-views/daily-calendar-view/daily-calendar-view-styles.js +175 -0
  44. package/dist/components/calendar-views/daily-calendar-view/daily-calendar-view.d.ts +21 -0
  45. package/dist/components/calendar-views/daily-calendar-view/daily-calendar-view.d.ts.map +1 -0
  46. package/dist/components/calendar-views/daily-calendar-view/daily-calendar-view.js +130 -0
  47. package/dist/components/calendar-views/daily-calendar-view/index.d.ts +2 -0
  48. package/dist/components/calendar-views/daily-calendar-view/index.d.ts.map +1 -0
  49. package/dist/components/calendar-views/daily-calendar-view/index.js +1 -0
  50. package/dist/components/calendar-views/event-layout.d.ts +18 -0
  51. package/dist/components/calendar-views/event-layout.d.ts.map +1 -0
  52. package/dist/components/calendar-views/event-layout.js +139 -0
  53. package/dist/components/calendar-views/schedule-calendar-view/index.d.ts +2 -0
  54. package/dist/components/calendar-views/schedule-calendar-view/index.d.ts.map +1 -0
  55. package/dist/components/calendar-views/schedule-calendar-view/index.js +1 -0
  56. package/dist/components/calendar-views/schedule-calendar-view/schedule-calendar-view-styles.d.ts +2 -0
  57. package/dist/components/calendar-views/schedule-calendar-view/schedule-calendar-view-styles.d.ts.map +1 -0
  58. package/dist/components/calendar-views/schedule-calendar-view/schedule-calendar-view-styles.js +118 -0
  59. package/dist/components/calendar-views/schedule-calendar-view/schedule-calendar-view.d.ts +23 -0
  60. package/dist/components/calendar-views/schedule-calendar-view/schedule-calendar-view.d.ts.map +1 -0
  61. package/dist/components/calendar-views/schedule-calendar-view/schedule-calendar-view.js +167 -0
  62. package/dist/components/calendar-views/weekly-calendar-view/index.d.ts +3 -0
  63. package/dist/components/calendar-views/weekly-calendar-view/index.d.ts.map +1 -0
  64. package/dist/components/calendar-views/weekly-calendar-view/index.js +1 -0
  65. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view-styles.d.ts +2 -0
  66. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view-styles.d.ts.map +1 -0
  67. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view-styles.js +234 -0
  68. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view-utils.d.ts +16 -0
  69. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view-utils.d.ts.map +1 -0
  70. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view-utils.js +83 -0
  71. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view.d.ts +26 -0
  72. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view.d.ts.map +1 -0
  73. package/dist/components/calendar-views/weekly-calendar-view/weekly-calendar-view.js +220 -0
  74. package/dist/components/dev-tools/dev-tools.d.ts +48 -0
  75. package/dist/components/dev-tools/dev-tools.d.ts.map +1 -0
  76. package/dist/components/dev-tools/dev-tools.js +186 -0
  77. package/dist/components/index.d.ts +14 -0
  78. package/dist/components/index.d.ts.map +1 -0
  79. package/dist/components/index.js +13 -0
  80. package/dist/components/register.d.ts +12 -0
  81. package/dist/components/register.d.ts.map +1 -0
  82. package/dist/components/register.js +11 -0
  83. package/dist/core/index.d.ts +30 -0
  84. package/dist/core/index.d.ts.map +1 -0
  85. package/dist/core/index.js +77 -0
  86. package/dist/index.d.ts +5 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +8 -0
  89. package/dist/test/index.d.ts +3 -0
  90. package/dist/test/index.d.ts.map +1 -0
  91. package/dist/test/index.js +2 -0
  92. package/dist/test/mock.d.ts +23 -0
  93. package/dist/test/mock.d.ts.map +1 -0
  94. package/dist/test/mock.js +49 -0
  95. package/dist/test/screenshots.d.ts +120 -0
  96. package/dist/test/screenshots.d.ts.map +1 -0
  97. package/dist/test/screenshots.js +127 -0
  98. package/dist/test/setup.d.ts +2 -0
  99. package/dist/test/setup.d.ts.map +1 -0
  100. package/dist/test/setup.js +13 -0
  101. package/dist/types/index.d.ts +138 -0
  102. package/dist/types/index.d.ts.map +1 -0
  103. package/dist/types/index.js +15 -0
  104. package/dist/utils/calendar.d.ts +28 -0
  105. package/dist/utils/calendar.d.ts.map +1 -0
  106. package/dist/utils/calendar.js +77 -0
  107. package/dist/utils/error-handling.d.ts +6 -0
  108. package/dist/utils/error-handling.d.ts.map +1 -0
  109. package/dist/utils/error-handling.js +16 -0
  110. package/dist/utils/html.d.ts +7 -0
  111. package/dist/utils/html.d.ts.map +1 -0
  112. package/dist/utils/html.js +13 -0
  113. package/dist/utils/index.d.ts +13 -0
  114. package/dist/utils/index.d.ts.map +1 -0
  115. package/dist/utils/index.js +12 -0
  116. package/dist/utils/locale.d.ts +68 -0
  117. package/dist/utils/locale.d.ts.map +1 -0
  118. package/dist/utils/locale.js +318 -0
  119. package/dist/utils/metadata.d.ts +39 -0
  120. package/dist/utils/metadata.d.ts.map +1 -0
  121. package/dist/utils/metadata.js +66 -0
  122. package/dist/utils/oauth.d.ts +16 -0
  123. package/dist/utils/oauth.d.ts.map +1 -0
  124. package/dist/utils/oauth.js +42 -0
  125. package/dist/utils/screen.d.ts +26 -0
  126. package/dist/utils/screen.d.ts.map +1 -0
  127. package/dist/utils/screen.js +44 -0
  128. package/dist/utils/settings.d.ts +38 -0
  129. package/dist/utils/settings.d.ts.map +1 -0
  130. package/dist/utils/settings.js +89 -0
  131. package/dist/utils/template.d.ts +2 -0
  132. package/dist/utils/template.d.ts.map +1 -0
  133. package/dist/utils/template.js +6 -0
  134. package/dist/utils/theme.d.ts +47 -0
  135. package/dist/utils/theme.d.ts.map +1 -0
  136. package/dist/utils/theme.js +183 -0
  137. package/dist/utils/utm.d.ts +23 -0
  138. package/dist/utils/utm.d.ts.map +1 -0
  139. package/dist/utils/utm.js +30 -0
  140. package/dist/utils/weather.d.ts +84 -0
  141. package/dist/utils/weather.d.ts.map +1 -0
  142. package/dist/utils/weather.js +272 -0
  143. package/package.json +8 -7
@@ -0,0 +1,16 @@
1
+ import panic from 'panic-overlay';
2
+ import { getSettingWithDefault, signalReady } from './settings.js';
3
+ /**
4
+ * Set up error handling with panic-overlay
5
+ * Configures panic-overlay to display errors on screen if the display_errors setting is enabled
6
+ */
7
+ export function setupErrorHandling() {
8
+ const displayErrors = getSettingWithDefault('display_errors', false);
9
+ panic.configure({
10
+ handleErrors: displayErrors,
11
+ });
12
+ if (displayErrors) {
13
+ window.addEventListener('error', signalReady);
14
+ window.addEventListener('unhandledrejection', signalReady);
15
+ }
16
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Escapes HTML special characters to prevent XSS attacks
3
+ * @param text - The text to escape
4
+ * @returns The escaped text safe for insertion into HTML
5
+ */
6
+ export declare function escapeHtml(text: string): string;
7
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/utils/html.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Escapes HTML special characters to prevent XSS attacks
3
+ * @param text - The text to escape
4
+ * @returns The escaped text safe for insertion into HTML
5
+ */
6
+ export function escapeHtml(text) {
7
+ return text
8
+ .replace(/&/g, '&')
9
+ .replace(/</g, '&lt;')
10
+ .replace(/>/g, '&gt;')
11
+ .replace(/"/g, '&quot;')
12
+ .replace(/'/g, '&#039;');
13
+ }
@@ -0,0 +1,13 @@
1
+ export * from './calendar.js';
2
+ export * from './error-handling.js';
3
+ export * from './html.js';
4
+ export * from './theme.js';
5
+ export * from './locale.js';
6
+ export * from './metadata.js';
7
+ export * from './oauth.js';
8
+ export * from './settings.js';
9
+ export * from './screen.js';
10
+ export * from './weather.js';
11
+ export * from './utm.js';
12
+ export * from './template.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA"}
@@ -0,0 +1,12 @@
1
+ export * from './calendar.js';
2
+ export * from './error-handling.js';
3
+ export * from './html.js';
4
+ export * from './theme.js';
5
+ export * from './locale.js';
6
+ export * from './metadata.js';
7
+ export * from './oauth.js';
8
+ export * from './settings.js';
9
+ export * from './screen.js';
10
+ export * from './weather.js';
11
+ export * from './utm.js';
12
+ export * from './template.js';
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Resolve timezone configuration with fallback chain
3
+ * Fallback order: override setting (validated) → GPS-based detection → 'UTC'
4
+ */
5
+ export declare function getTimeZone(): Promise<string>;
6
+ /**
7
+ * Resolve locale configuration with fallback chain
8
+ * Fallback order: override setting (validated) → GPS-based detection → browser locale → 'en'
9
+ */
10
+ export declare function getLocale(): Promise<string>;
11
+ /**
12
+ * Format coordinates into a human-readable string
13
+ * Example: "37.3861° N, 122.0839° W"
14
+ */
15
+ export declare function formatCoordinates(coordinates: [number, number]): string;
16
+ /**
17
+ * Format a date in a locale-aware way.
18
+ *
19
+ * Examples:
20
+ * - "December 25, 2023" in en-US
21
+ * - "25 December 2023" in en-GB
22
+ * - "2023年12月25日" in ja-JP
23
+ * - "25.12.2023" in de-DE
24
+ *
25
+ * By default, formats as a full date (year, month, day). Callers can
26
+ * override or extend the formatting via the `options` parameter.
27
+ */
28
+ export declare function formatLocalizedDate(date: Date, locale: string, options?: Intl.DateTimeFormatOptions): string;
29
+ /**
30
+ * Get localized day names (Sunday-Saturday)
31
+ * Returns both full and short forms
32
+ */
33
+ export declare function getLocalizedDayNames(locale: string): {
34
+ full: string[];
35
+ short: string[];
36
+ };
37
+ /**
38
+ * Get localized month names (January-December)
39
+ * Returns both full and short forms
40
+ */
41
+ export declare function getLocalizedMonthNames(locale: string): {
42
+ full: string[];
43
+ short: string[];
44
+ };
45
+ /**
46
+ * Detect if a locale uses 12-hour or 24-hour format
47
+ */
48
+ export declare function detectHourFormat(locale: string): 'hour12' | 'hour24';
49
+ /**
50
+ * Determine measurement unit based on country code
51
+ * @param countryCode - Two-character ISO country code (e.g., 'US', 'GB')
52
+ * @returns 'imperial' for Fahrenheit countries, 'metric' for all others
53
+ */
54
+ export declare function getMeasurementUnitByCountry(countryCode: string): 'metric' | 'imperial';
55
+ /**
56
+ * Format time with locale and timezone awareness
57
+ * Returns structured time parts for flexible composition
58
+ */
59
+ export declare function formatTime(date: Date, locale: string, timezone: string, options?: {
60
+ hour12?: boolean;
61
+ }): {
62
+ hour: string;
63
+ minute: string;
64
+ second: string;
65
+ dayPeriod?: string;
66
+ formatted: string;
67
+ };
68
+ //# sourceMappingURL=locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/utils/locale.ts"],"names":[],"mappings":"AAgEA;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAuBnD;AAED;;;GAGG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAmCjD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CASvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,GACnC,MAAM,CAqBR;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG;IACpD,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAuBA;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG;IACtD,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAgBA;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAWpE;AAqED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,GAClB,QAAQ,GAAG,UAAU,CAIvB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,GACA;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB,CA4CA"}
@@ -0,0 +1,318 @@
1
+ import tzlookup from '@photostructure/tz-lookup';
2
+ import clm from 'country-locale-map';
3
+ import { getSettingWithDefault } from './settings.js';
4
+ import { getMetadata } from './metadata.js';
5
+ /**
6
+ * Validate timezone using native Intl API
7
+ */
8
+ function isValidTimezone(timezone) {
9
+ try {
10
+ new Intl.DateTimeFormat('en-US', { timeZone: timezone });
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ /**
18
+ * Validate locale using native Intl API
19
+ * Ensures the resolved locale's language code matches the requested locale
20
+ */
21
+ function isValidLocale(locale) {
22
+ // Basic format validation: should be at least 2 characters
23
+ // and not end with a hyphen
24
+ if (!locale || locale.length < 2 || locale.endsWith('-')) {
25
+ return false;
26
+ }
27
+ // Language code should be 2-3 characters, followed by optional region/script
28
+ const localeRegex = /^[a-z]{2,3}(-[a-z]{2,})*$/i;
29
+ if (!localeRegex.test(locale)) {
30
+ return false;
31
+ }
32
+ try {
33
+ const formatter = new Intl.DateTimeFormat(locale);
34
+ const resolved = formatter.resolvedOptions().locale;
35
+ // Check if the resolved locale's language code matches the requested one
36
+ // e.g., 'zh-CN' → language 'zh', 'en-US' → language 'en'
37
+ const requestedLanguage = locale.toLowerCase().split('-')[0];
38
+ const resolvedLanguage = resolved.toLowerCase().split('-')[0];
39
+ if (requestedLanguage !== resolvedLanguage) {
40
+ return false;
41
+ }
42
+ // If request includes a region/script, verify it's preserved in resolution
43
+ // This catches cases like 'en-INVALID' resolving to 'en'
44
+ const requestedParts = locale.toLowerCase().split('-');
45
+ if (requestedParts.length > 1) {
46
+ const resolvedParts = resolved.toLowerCase().split('-');
47
+ // If we requested more than just language, the resolved should have similar depth
48
+ // (or not drop the requested parts entirely)
49
+ if (resolvedParts.length < requestedParts.length) {
50
+ return false;
51
+ }
52
+ }
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ /**
60
+ * Resolve timezone configuration with fallback chain
61
+ * Fallback order: override setting (validated) → GPS-based detection → 'UTC'
62
+ */
63
+ export async function getTimeZone() {
64
+ // Priority 1: Use override setting if provided and valid
65
+ const overrideTimezone = getSettingWithDefault('override_timezone', '');
66
+ if (overrideTimezone) {
67
+ // Validate using native Intl API
68
+ if (isValidTimezone(overrideTimezone)) {
69
+ return overrideTimezone;
70
+ }
71
+ console.warn(`Invalid timezone override: "${overrideTimezone}", falling back to GPS detection`);
72
+ }
73
+ try {
74
+ const [latitude, longitude] = getMetadata().coordinates;
75
+ return tzlookup(latitude, longitude);
76
+ }
77
+ catch (error) {
78
+ console.warn('Failed to get timezone from coordinates, using UTC:', error);
79
+ return 'UTC';
80
+ }
81
+ }
82
+ /**
83
+ * Resolve locale configuration with fallback chain
84
+ * Fallback order: override setting (validated) → GPS-based detection → browser locale → 'en'
85
+ */
86
+ export async function getLocale() {
87
+ // Priority 1: Use override setting if provided and valid
88
+ const overrideLocale = getSettingWithDefault('override_locale', '');
89
+ if (overrideLocale) {
90
+ const normalizedLocale = overrideLocale.replaceAll('_', '-');
91
+ // Validate the override locale
92
+ if (isValidLocale(normalizedLocale)) {
93
+ return normalizedLocale;
94
+ }
95
+ console.warn(`Invalid locale override: "${overrideLocale}", falling back to GPS detection`);
96
+ }
97
+ const [lat, lng] = getMetadata().coordinates;
98
+ const defaultLocale = (navigator?.languages?.length
99
+ ? navigator.languages[0]
100
+ : navigator.language) || 'en';
101
+ try {
102
+ // Lazy-load offline-geocode-city so apps that never call getLocale
103
+ // don't need to bundle it (and won't hit its lz-string dependency).
104
+ const { getNearestCity } = await import('offline-geocode-city');
105
+ const data = await getNearestCity(lat, lng);
106
+ const countryCode = data.countryIso2.toUpperCase();
107
+ const locale = clm.getLocaleByAlpha2(countryCode) || defaultLocale;
108
+ return locale.replace('_', '-');
109
+ }
110
+ catch (error) {
111
+ console.warn('Failed to get locale from coordinates, using default:', error);
112
+ return defaultLocale;
113
+ }
114
+ }
115
+ /**
116
+ * Format coordinates into a human-readable string
117
+ * Example: "37.3861° N, 122.0839° W"
118
+ */
119
+ export function formatCoordinates(coordinates) {
120
+ const [latitude, longitude] = coordinates;
121
+ const latString = `${Math.abs(latitude).toFixed(4)}\u00B0`;
122
+ const latDirection = latitude > 0 ? 'N' : 'S';
123
+ const lngString = `${Math.abs(longitude).toFixed(4)}\u00B0`;
124
+ const lngDirection = longitude > 0 ? 'E' : 'W';
125
+ return `${latString} ${latDirection}, ${lngString} ${lngDirection}`;
126
+ }
127
+ /**
128
+ * Format a date in a locale-aware way.
129
+ *
130
+ * Examples:
131
+ * - "December 25, 2023" in en-US
132
+ * - "25 December 2023" in en-GB
133
+ * - "2023年12月25日" in ja-JP
134
+ * - "25.12.2023" in de-DE
135
+ *
136
+ * By default, formats as a full date (year, month, day). Callers can
137
+ * override or extend the formatting via the `options` parameter.
138
+ */
139
+ export function formatLocalizedDate(date, locale, options) {
140
+ const baseOptions = {
141
+ year: 'numeric',
142
+ month: 'long',
143
+ day: 'numeric',
144
+ };
145
+ try {
146
+ const formatter = new Intl.DateTimeFormat(locale, {
147
+ ...baseOptions,
148
+ ...options,
149
+ });
150
+ return formatter.format(date);
151
+ }
152
+ catch {
153
+ // Fallback to a safe default for unrecognized locales
154
+ const fallbackFormatter = new Intl.DateTimeFormat('en-US', {
155
+ ...baseOptions,
156
+ ...options,
157
+ });
158
+ return fallbackFormatter.format(date);
159
+ }
160
+ }
161
+ /**
162
+ * Get localized day names (Sunday-Saturday)
163
+ * Returns both full and short forms
164
+ */
165
+ export function getLocalizedDayNames(locale) {
166
+ const full = [];
167
+ const short = [];
168
+ // Find the first Sunday of the current year using local time
169
+ // (avoid Date.UTC — toLocaleDateString uses local time, so UTC midnight
170
+ // maps to the previous day in negative-offset timezones like PST)
171
+ const now = new Date();
172
+ const year = now.getFullYear();
173
+ const firstDay = new Date(year, 0, 1);
174
+ const dayOfWeek = firstDay.getDay(); // 0 = Sunday, 1 = Monday, etc.
175
+ // Calculate offset to get to the first Sunday
176
+ const offset = dayOfWeek === 0 ? 0 : 7 - dayOfWeek;
177
+ for (let i = 0; i < 7; i++) {
178
+ const date = new Date(year, 0, 1 + offset + i);
179
+ full.push(date.toLocaleDateString(locale, { weekday: 'long' }));
180
+ short.push(date.toLocaleDateString(locale, { weekday: 'short' }));
181
+ }
182
+ return { full, short };
183
+ }
184
+ /**
185
+ * Get localized month names (January-December)
186
+ * Returns both full and short forms
187
+ */
188
+ export function getLocalizedMonthNames(locale) {
189
+ const full = [];
190
+ const short = [];
191
+ // Iterate through each month of the current year
192
+ const now = new Date();
193
+ const year = now.getFullYear();
194
+ for (let i = 0; i < 12; i++) {
195
+ const date = new Date(year, i, 1);
196
+ full.push(date.toLocaleDateString(locale, { month: 'long' }));
197
+ short.push(date.toLocaleDateString(locale, { month: 'short' }));
198
+ }
199
+ return { full, short };
200
+ }
201
+ /**
202
+ * Detect if a locale uses 12-hour or 24-hour format
203
+ */
204
+ export function detectHourFormat(locale) {
205
+ try {
206
+ const formatter = new Intl.DateTimeFormat(locale, {
207
+ hour: 'numeric',
208
+ });
209
+ return formatter.resolvedOptions().hour12 ? 'hour12' : 'hour24';
210
+ }
211
+ catch {
212
+ // Fallback to 24-hour for unrecognized locales
213
+ return 'hour24';
214
+ }
215
+ }
216
+ /**
217
+ * Extract time parts from a DateTimeFormat formatter
218
+ */
219
+ function extractTimePartsFromFormatter(date, formatter) {
220
+ const parts = formatter.formatToParts(date);
221
+ const partMap = {};
222
+ parts.forEach((part) => {
223
+ if (part.type !== 'literal') {
224
+ partMap[part.type] = part.value;
225
+ }
226
+ });
227
+ return {
228
+ hour: partMap.hour || '00',
229
+ minute: partMap.minute || '00',
230
+ second: partMap.second || '00',
231
+ dayPeriod: partMap.dayPeriod,
232
+ };
233
+ }
234
+ /**
235
+ * Get locale extension for numeral system based on language
236
+ * This enables locale-specific number representations (e.g., Thai numerals, Chinese numerals)
237
+ * Uses Intl.Locale API to robustly handle existing extensions
238
+ */
239
+ function getLocaleWithNumeralSystem(locale) {
240
+ const language = locale.toLowerCase().split('-')[0];
241
+ // Map of languages to their numeral system extensions
242
+ const numeralSystemMap = {
243
+ th: 'thai', // Thai numerals: ๐๑๒๓๔๕๖๗๘๙
244
+ zh: 'hanidec', // Chinese numerals: 〇一二三四五六七八九
245
+ };
246
+ const numeralSystem = numeralSystemMap[language];
247
+ if (!numeralSystem) {
248
+ return locale;
249
+ }
250
+ try {
251
+ // Use Intl.Locale API to robustly handle existing extensions
252
+ // This properly merges extensions instead of creating duplicates
253
+ const localeObj = new Intl.Locale(locale, {
254
+ numberingSystem: numeralSystem,
255
+ });
256
+ return localeObj.toString();
257
+ }
258
+ catch (error) {
259
+ // Fallback to original locale if Intl.Locale fails
260
+ console.warn(`Failed to apply numeral system to locale "${locale}":`, error);
261
+ return locale;
262
+ }
263
+ }
264
+ // Countries that use Fahrenheit scale
265
+ // United States, Bahamas, Cayman Islands, Liberia, Palau,
266
+ // Federated States of Micronesia, Marshall Islands
267
+ const FAHRENHEIT_COUNTRIES = ['US', 'BS', 'KY', 'LR', 'PW', 'FM', 'MH'];
268
+ /**
269
+ * Determine measurement unit based on country code
270
+ * @param countryCode - Two-character ISO country code (e.g., 'US', 'GB')
271
+ * @returns 'imperial' for Fahrenheit countries, 'metric' for all others
272
+ */
273
+ export function getMeasurementUnitByCountry(countryCode) {
274
+ return FAHRENHEIT_COUNTRIES.includes(countryCode.toUpperCase())
275
+ ? 'imperial'
276
+ : 'metric';
277
+ }
278
+ /**
279
+ * Format time with locale and timezone awareness
280
+ * Returns structured time parts for flexible composition
281
+ */
282
+ export function formatTime(date, locale, timezone, options) {
283
+ try {
284
+ // Determine hour format if not explicitly provided
285
+ const hour12 = options?.hour12 ?? detectHourFormat(locale) === 'hour12';
286
+ // Get locale with numeral system extension if applicable
287
+ const localeWithNumerals = getLocaleWithNumeralSystem(locale);
288
+ // Format with Intl API for proper localization
289
+ const formatter = new Intl.DateTimeFormat(localeWithNumerals, {
290
+ hour: '2-digit',
291
+ minute: '2-digit',
292
+ second: '2-digit',
293
+ hour12,
294
+ timeZone: timezone,
295
+ });
296
+ const timeParts = extractTimePartsFromFormatter(date, formatter);
297
+ return {
298
+ ...timeParts,
299
+ formatted: formatter.format(date),
300
+ };
301
+ }
302
+ catch (error) {
303
+ console.warn(`Failed to format time for locale "${locale}" and timezone "${timezone}":`, error);
304
+ // Fallback to UTC in English
305
+ const fallbackFormatter = new Intl.DateTimeFormat('en', {
306
+ hour: '2-digit',
307
+ minute: '2-digit',
308
+ second: '2-digit',
309
+ hour12: false,
310
+ timeZone: 'UTC',
311
+ });
312
+ const timeParts = extractTimePartsFromFormatter(date, fallbackFormatter);
313
+ return {
314
+ ...timeParts,
315
+ formatted: fallbackFormatter.format(date),
316
+ };
317
+ }
318
+ }
@@ -0,0 +1,39 @@
1
+ import type { ScreenlyMetadata } from '../types/index.js';
2
+ import { Hardware } from '../types/index.js';
3
+ /**
4
+ * Get the Screenly metadata
5
+ */
6
+ export declare function getMetadata(): ScreenlyMetadata;
7
+ /**
8
+ * Get formatted coordinates from metadata
9
+ */
10
+ export declare function getFormattedCoordinates(): string;
11
+ /**
12
+ * Get the screen name
13
+ */
14
+ export declare function getScreenName(): string;
15
+ /**
16
+ * Get the hostname
17
+ */
18
+ export declare function getHostname(): string;
19
+ /**
20
+ * Get the location
21
+ */
22
+ export declare function getLocation(): string;
23
+ /**
24
+ * Get the hardware type
25
+ */
26
+ export declare function getHardware(): Hardware;
27
+ /**
28
+ * Get the Screenly version
29
+ */
30
+ export declare function getScreenlyVersion(): string;
31
+ /**
32
+ * Get the tags
33
+ */
34
+ export declare function getTags(): string[];
35
+ /**
36
+ * Check if a specific tag exists
37
+ */
38
+ export declare function hasTag(tag: string): boolean;
39
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/utils/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAG5C;;GAEG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAE9C;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAgBtC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;GAEG;AACH,wBAAgB,OAAO,IAAI,MAAM,EAAE,CAElC;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE3C"}
@@ -0,0 +1,66 @@
1
+ import { Hardware } from '../types/index.js';
2
+ import { formatCoordinates } from './locale.js';
3
+ /**
4
+ * Get the Screenly metadata
5
+ */
6
+ export function getMetadata() {
7
+ return screenly.metadata;
8
+ }
9
+ /**
10
+ * Get formatted coordinates from metadata
11
+ */
12
+ export function getFormattedCoordinates() {
13
+ return formatCoordinates(screenly.metadata.coordinates);
14
+ }
15
+ /**
16
+ * Get the screen name
17
+ */
18
+ export function getScreenName() {
19
+ return screenly.metadata.screen_name;
20
+ }
21
+ /**
22
+ * Get the hostname
23
+ */
24
+ export function getHostname() {
25
+ return screenly.metadata.hostname;
26
+ }
27
+ /**
28
+ * Get the location
29
+ */
30
+ export function getLocation() {
31
+ return screenly.metadata.location;
32
+ }
33
+ /**
34
+ * Get the hardware type
35
+ */
36
+ export function getHardware() {
37
+ const hardware = screenly.metadata.hardware;
38
+ if (hardware === undefined) {
39
+ return Hardware.Anywhere;
40
+ }
41
+ if (hardware === 'Raspberry Pi') {
42
+ return Hardware.RaspberryPi;
43
+ }
44
+ if (hardware === 'Screenly Player Max') {
45
+ return Hardware.ScreenlyPlayerMax;
46
+ }
47
+ return Hardware.Unknown;
48
+ }
49
+ /**
50
+ * Get the Screenly version
51
+ */
52
+ export function getScreenlyVersion() {
53
+ return screenly.metadata.screenly_version;
54
+ }
55
+ /**
56
+ * Get the tags
57
+ */
58
+ export function getTags() {
59
+ return screenly.metadata.tags;
60
+ }
61
+ /**
62
+ * Check if a specific tag exists
63
+ */
64
+ export function hasTag(tag) {
65
+ return screenly.metadata.tags.includes(tag);
66
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Starts a background loop that calls `onRefresh` every 30 minutes, with
3
+ * exponential back-off on failure (up to ~128 minutes between retries).
4
+ * Stops retrying after 7 consecutive failures.
5
+ */
6
+ export declare const initTokenRefreshLoop: (onRefresh: () => Promise<void>) => void;
7
+ /**
8
+ * Retrieves credentials from the Screenly OAuth service
9
+ * @param tokenType The token endpoint type (default: 'access_token')
10
+ * @returns An object containing the token and optional metadata from the OAuth provider
11
+ */
12
+ export declare const getCredentials: (tokenType?: string) => Promise<{
13
+ token: string;
14
+ metadata?: Record<string, unknown>;
15
+ }>;
16
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/utils/oauth.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,WAAW,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,IAsBrE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,YAAW,MAAuB,KACjC,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAc/D,CAAA"}
@@ -0,0 +1,42 @@
1
+ const TOKEN_REFRESH_INTERVAL_SEC = 30 * 60;
2
+ /**
3
+ * Starts a background loop that calls `onRefresh` every 30 minutes, with
4
+ * exponential back-off on failure (up to ~128 minutes between retries).
5
+ * Stops retrying after 7 consecutive failures.
6
+ */
7
+ export const initTokenRefreshLoop = (onRefresh) => {
8
+ let errorStep = 0;
9
+ const initErrorDelaySec = 15;
10
+ const maxErrorStep = 7;
11
+ const run = async () => {
12
+ let nextTimeout = TOKEN_REFRESH_INTERVAL_SEC;
13
+ try {
14
+ await onRefresh();
15
+ errorStep = 0;
16
+ }
17
+ catch {
18
+ nextTimeout = Math.min(initErrorDelaySec * Math.pow(2, errorStep), nextTimeout);
19
+ if (errorStep >= maxErrorStep)
20
+ return;
21
+ errorStep++;
22
+ }
23
+ setTimeout(run, nextTimeout * 1000);
24
+ };
25
+ setTimeout(run, TOKEN_REFRESH_INTERVAL_SEC * 1000);
26
+ };
27
+ /**
28
+ * Retrieves credentials from the Screenly OAuth service
29
+ * @param tokenType The token endpoint type (default: 'access_token')
30
+ * @returns An object containing the token and optional metadata from the OAuth provider
31
+ */
32
+ export const getCredentials = async (tokenType = 'access_token') => {
33
+ const response = await fetch(screenly.settings.screenly_oauth_tokens_url + tokenType + '/', {
34
+ method: 'GET',
35
+ headers: {
36
+ Accept: 'application/json',
37
+ Authorization: `Bearer ${screenly.settings.screenly_app_auth_token}`,
38
+ },
39
+ });
40
+ const { token, metadata } = await response.json();
41
+ return { token, metadata };
42
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Screen Utilities
3
+ * Utilities for screen orientation, dimensions, and other screen-related functions
4
+ */
5
+ /**
6
+ * Check if the current screen orientation is portrait
7
+ * @returns true if portrait, false if landscape
8
+ */
9
+ export declare function isPortrait(): boolean;
10
+ /**
11
+ * Check if the current screen orientation is landscape
12
+ * @returns true if landscape, false if portrait
13
+ */
14
+ export declare function isLandscape(): boolean;
15
+ /**
16
+ * Get the current screen orientation
17
+ * @returns 'portrait' | 'landscape'
18
+ */
19
+ export declare function getOrientation(): 'portrait' | 'landscape';
20
+ /**
21
+ * Center the auto-scaler element vertically within the viewport.
22
+ * Useful for apps that restrict display to a single orientation (portrait or
23
+ * landscape), where the auto-scaler may not fill the full viewport height.
24
+ */
25
+ export declare function centerAutoScalerVertically(): void;
26
+ //# sourceMappingURL=screen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../src/utils/screen.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAKpC;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAKrC;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,UAAU,GAAG,WAAW,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAMjD"}