@gaulatti/giorgia 0.1.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/carousels.d.ts +1 -0
- package/dist/carousels.js +65 -0
- package/dist/components/live-program/LiveProgram.d.ts +9 -0
- package/dist/components/live-program/LiveProgram.js +751 -0
- package/dist/components/live-program/assets.d.ts +14 -0
- package/dist/components/live-program/assets.js +14 -0
- package/dist/components/live-program/components/Marquee.d.ts +16 -0
- package/dist/components/live-program/components/Marquee.js +88 -0
- package/dist/components/live-program/components/MarqueeCurtain.d.ts +5 -0
- package/dist/components/live-program/components/MarqueeCurtain.js +30 -0
- package/dist/components/live-program/components/WorldClocks.d.ts +19 -0
- package/dist/components/live-program/components/WorldClocks.js +101 -0
- package/dist/components/live-program/components/slides/ArticleSlide.d.ts +14 -0
- package/dist/components/live-program/components/slides/ArticleSlide.js +32 -0
- package/dist/components/live-program/components/slides/CallsignSlide.d.ts +6 -0
- package/dist/components/live-program/components/slides/CallsignSlide.js +49 -0
- package/dist/components/live-program/components/slides/LiveEventSlide.d.ts +9 -0
- package/dist/components/live-program/components/slides/LiveEventSlide.js +86 -0
- package/dist/components/live-program/components/slides/slideStyles.d.ts +1 -0
- package/dist/components/live-program/components/slides/slideStyles.js +64 -0
- package/dist/components/live-program/events.d.ts +34 -0
- package/dist/components/live-program/events.js +167 -0
- package/dist/components/live-program/hooks/useSSE.d.ts +11 -0
- package/dist/components/live-program/hooks/useSSE.js +107 -0
- package/dist/components/live-program/i18n.d.ts +4 -0
- package/dist/components/live-program/i18n.js +305 -0
- package/dist/components/live-program/segments/ArticlesSegment.d.ts +6 -0
- package/dist/components/live-program/segments/ArticlesSegment.js +160 -0
- package/dist/components/live-program/segments/EarthquakeSegment.d.ts +16 -0
- package/dist/components/live-program/segments/EarthquakeSegment.js +130 -0
- package/dist/components/live-program/segments/LiveEventSegment.d.ts +4 -0
- package/dist/components/live-program/segments/LiveEventSegment.js +27 -0
- package/dist/components/live-program/segments/MarketsSegment.d.ts +12 -0
- package/dist/components/live-program/segments/MarketsSegment.js +76 -0
- package/dist/components/live-program/segments/WeatherSegment.d.ts +15 -0
- package/dist/components/live-program/segments/WeatherSegment.js +119 -0
- package/dist/components/live-program/segments/fetchLiveEvents.d.ts +20 -0
- package/dist/components/live-program/segments/fetchLiveEvents.js +76 -0
- package/dist/components/live-program/segments/index.d.ts +8 -0
- package/dist/components/live-program/segments/index.js +8 -0
- package/dist/components/live-program/segments/types.d.ts +23 -0
- package/dist/components/live-program/segments/types.js +1 -0
- package/dist/components/live-program/segments/usePlaylistEngine.d.ts +9 -0
- package/dist/components/live-program/segments/usePlaylistEngine.js +108 -0
- package/dist/components/live-program/utils/broadcastTime.d.ts +12 -0
- package/dist/components/live-program/utils/broadcastTime.js +33 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGD_jZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGFPjZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGGHiZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGGHjZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGIbkZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGL_kZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGMjkZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGOHjZtU.ttf +0 -0
- package/dist/fonts/LDIcapOFNxEwR-Bd1O9uYNmnUQomAgE25imKSbHhROjLsZBWTSrQGOHkZtU.ttf +0 -0
- package/dist/fonts/SlGDmQSNjdsmc35JDF1K5E55YMjF_7DPuGi-2fRUAw.ttf +0 -0
- package/dist/fonts/SlGDmQSNjdsmc35JDF1K5E55YMjF_7DPuGi-6_RUAw.ttf +0 -0
- package/dist/fonts/SlGDmQSNjdsmc35JDF1K5E55YMjF_7DPuGi-DPNUAw.ttf +0 -0
- package/dist/fonts/SlGDmQSNjdsmc35JDF1K5E55YMjF_7DPuGi-NfNUAw.ttf +0 -0
- package/dist/fonts/SlGDmQSNjdsmc35JDF1K5E55YMjF_7DPuGi-a_NUAw.ttf +0 -0
- package/dist/fonts/SlGFmQSNjdsmc35JDF1K5GRwUjcdlttVFm-rI7c8R496.ttf +0 -0
- package/dist/fonts/SlGFmQSNjdsmc35JDF1K5GRwUjcdlttVFm-rI7dbR496.ttf +0 -0
- package/dist/fonts/SlGFmQSNjdsmc35JDF1K5GRwUjcdlttVFm-rI7diR496.ttf +0 -0
- package/dist/fonts/SlGFmQSNjdsmc35JDF1K5GRwUjcdlttVFm-rI7e8QI96.ttf +0 -0
- package/dist/fonts/SlGFmQSNjdsmc35JDF1K5GRwUjcdlttVFm-rI7eOQI96.ttf +0 -0
- package/dist/fonts/TK3_WkUHHAIjg75cFRf3bXL8LICs13FvgUE.ttf +0 -0
- package/dist/fonts/TK3_WkUHHAIjg75cFRf3bXL8LICs169vgUE.ttf +0 -0
- package/dist/fonts/TK3_WkUHHAIjg75cFRf3bXL8LICs18NvgUE.ttf +0 -0
- package/dist/fonts/TK3_WkUHHAIjg75cFRf3bXL8LICs1_FvgUE.ttf +0 -0
- package/dist/fonts/TK3_WkUHHAIjg75cFRf3bXL8LICs1xZogUE.ttf +0 -0
- package/dist/fonts/TK3_WkUHHAIjg75cFRf3bXL8LICs1y9ogUE.ttf +0 -0
- package/dist/fonts/fonts.css +378 -0
- package/dist/fonts/j8_16_LD37rqfuwxyIuaZhE6cRXOLtm2gfTGgQ.ttf +0 -0
- package/dist/fonts/j8_46_LD37rqfuwxyIuaZhE6cRXOLtm2gfT-BYipBw.ttf +0 -0
- package/dist/fonts/j8_46_LD37rqfuwxyIuaZhE6cRXOLtm2gfT-IYmpBw.ttf +0 -0
- package/dist/fonts/j8_46_LD37rqfuwxyIuaZhE6cRXOLtm2gfT-PYqpBw.ttf +0 -0
- package/dist/fonts/j8_46_LD37rqfuwxyIuaZhE6cRXOLtm2gfT-WYupBw.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05oYiRNDM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05oZ8RNDM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05oZ8RdDM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05oZORNDM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05oabQ9DM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05oaiQ9DM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05ob8Q9DM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05ob8RNDM.ttf +0 -0
- package/dist/fonts/jizMREVItHgc8qDIbSTKq4XkRiUawTk7f45UM9y05obVQ9DM.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduh8MKUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhHMWUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhLsSUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhLsWUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhcMWUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhh8KUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhrsKUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhrsWUBw.ttf +0 -0
- package/dist/fonts/jizOREVItHgc8qDIbSTKq4XkRg8T88bjFuXOnduhycKUBw.ttf +0 -0
- package/dist/homepage-distributor.d.ts +55 -0
- package/dist/homepage-distributor.js +68 -0
- package/dist/instagram-image-template.d.ts +9 -0
- package/dist/instagram-image-template.js +51 -0
- package/dist/live-program/main.d.ts +1 -0
- package/dist/live-program/main.js +10 -0
- package/dist/live-program-page/fifthbell/audio/pipes.ogg +0 -0
- package/dist/live-program-page/fifthbell/images/berlin.jpg +0 -0
- package/dist/live-program-page/fifthbell/images/fifthbell.png +0 -0
- package/dist/live-program-page/fifthbell/images/nyc.jpg +0 -0
- package/dist/live-program-page/fifthbell/images/nyse.jpg +0 -0
- package/dist/live-program-page/fifthbell/images/santiago.jpg +0 -0
- package/dist/live-program-page/fifthbell/images/seismograph.jpg +0 -0
- package/dist/live-program-page/fifthbell/images/tokyo.jpg +0 -0
- package/dist/live-program-page/index.html +16 -0
- package/dist/live-program-page/live-program.css +3 -0
- package/dist/live-program-page/live-program.js +76 -0
- package/dist/outlet-config.d.ts +29 -0
- package/dist/outlet-config.js +29 -0
- package/dist/renderer.browser.d.ts +2 -0
- package/dist/renderer.browser.js +132 -0
- package/dist/renderer.core.d.ts +9 -0
- package/dist/renderer.core.js +354 -0
- package/dist/renderer.d.ts +6 -0
- package/dist/renderer.js +4 -0
- package/dist/renderer.node.d.ts +16 -0
- package/dist/renderer.node.js +166 -0
- package/dist/types/canonical-article.d.ts +249 -0
- package/dist/types/canonical-article.js +236 -0
- package/dist/utils/sofascore.d.ts +3 -0
- package/dist/utils/sofascore.js +31 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/package.json +89 -0
- package/src/partial-deps.json +53 -0
- package/src/styles/compiled.css +2 -0
- package/src/templates/layouts/404.hbs +5 -0
- package/src/templates/layouts/article-page.hbs +5 -0
- package/src/templates/layouts/category-page.hbs +5 -0
- package/src/templates/layouts/homepage.hbs +5 -0
- package/src/templates/layouts/link-in-bio.hbs +226 -0
- package/src/templates/layouts/live-story.hbs +5 -0
- package/src/templates/layouts/media-page.hbs +5 -0
- package/src/templates/layouts/search-page.hbs +5 -0
- package/src/templates/partials/blocks/audio.hbs +12 -0
- package/src/templates/partials/blocks/data-table.hbs +23 -0
- package/src/templates/partials/blocks/divider.hbs +1 -0
- package/src/templates/partials/blocks/heading.hbs +9 -0
- package/src/templates/partials/blocks/image.hbs +6 -0
- package/src/templates/partials/blocks/info-box.hbs +8 -0
- package/src/templates/partials/blocks/instagram.hbs +28 -0
- package/src/templates/partials/blocks/key-points.hbs +8 -0
- package/src/templates/partials/blocks/list.hbs +13 -0
- package/src/templates/partials/blocks/live-update.hbs +24 -0
- package/src/templates/partials/blocks/pull-quote.hbs +6 -0
- package/src/templates/partials/blocks/rich-text.hbs +1 -0
- package/src/templates/partials/blocks/tiktok.hbs +15 -0
- package/src/templates/partials/blocks/x.hbs +74 -0
- package/src/templates/partials/blocks/youtube.hbs +12 -0
- package/src/templates/partials/components/article-main.hbs +159 -0
- package/src/templates/partials/components/breaking-news/live-updates-column.hbs +29 -0
- package/src/templates/partials/components/breaking-news.hbs +56 -0
- package/src/templates/partials/components/category/header.hbs +5 -0
- package/src/templates/partials/components/category/main-grid.hbs +55 -0
- package/src/templates/partials/components/category/main.hbs +7 -0
- package/src/templates/partials/components/category/more-grid.hbs +26 -0
- package/src/templates/partials/components/editorial-hero.hbs +73 -0
- package/src/templates/partials/components/headline.hbs +15 -0
- package/src/templates/partials/components/hero-editorial.hbs +1 -0
- package/src/templates/partials/components/hero.hbs +1 -0
- package/src/templates/partials/components/home/landing.hbs +111 -0
- package/src/templates/partials/components/home/main.hbs +63 -0
- package/src/templates/partials/components/home/more-stories.hbs +23 -0
- package/src/templates/partials/components/home/must-read.hbs +77 -0
- package/src/templates/partials/components/live-story/main.hbs +229 -0
- package/src/templates/partials/components/media/main.hbs +220 -0
- package/src/templates/partials/components/not-found/main.hbs +28 -0
- package/src/templates/partials/components/search/main.hbs +420 -0
- package/src/templates/partials/components/snack.hbs +92 -0
- package/src/templates/partials/components/spotlight-hero.hbs +59 -0
- package/src/templates/partials/components/trending.hbs +14 -0
- package/src/templates/partials/components/ui/accordion.hbs +30 -0
- package/src/templates/partials/components/ui/breadcrumb.hbs +16 -0
- package/src/templates/partials/components/ui/icon-button.hbs +19 -0
- package/src/templates/partials/components/ui/loading-spinner.hbs +27 -0
- package/src/templates/partials/components/ui/pagination.hbs +56 -0
- package/src/templates/partials/components/ui/scroll-area.hbs +12 -0
- package/src/templates/partials/components/ui/status-badge.hbs +21 -0
- package/src/templates/partials/footers/footer-full.hbs +79 -0
- package/src/templates/partials/footers/footer-minimal.hbs +5 -0
- package/src/templates/partials/headers/header-main.hbs +400 -0
- package/src/templates/partials/headers/header-minimal.hbs +16 -0
- package/src/templates/partials/nav/nav-categories.hbs +5 -0
- package/src/templates/partials/shell/doc-end.hbs +282 -0
- package/src/templates/partials/shell/doc-start-404.hbs +41 -0
- package/src/templates/partials/shell/doc-start-standard.hbs +86 -0
- package/src/templates/templates/instagram-image.hbs +175 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
function extractUpdatesFromAuburnaleEvent(updates) {
|
|
2
|
+
if (!updates || updates.length === 0)
|
|
3
|
+
return [];
|
|
4
|
+
return updates.map((u) => {
|
|
5
|
+
let text = '';
|
|
6
|
+
try {
|
|
7
|
+
const parsed = typeof u.content === 'string' ? JSON.parse(u.content) : u.content;
|
|
8
|
+
text = parsed?.root?.children?.[0]?.children?.[0]?.text || '';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
text = String(u.content || '');
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
timestamp: u.timestamp,
|
|
15
|
+
text: text || 'Update available',
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function fetchFromAuburnale(language) {
|
|
20
|
+
const baseUrl = typeof window !== 'undefined' ? `http://${window.location.hostname}:3000` : 'http://127.0.0.1:3000';
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(`${baseUrl}/events?status=published&limit=1&sort=-eventDate&language=${language}`, {
|
|
23
|
+
signal: AbortSignal.timeout(3000),
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
return null;
|
|
27
|
+
const body = await res.json();
|
|
28
|
+
const items = body.data || [];
|
|
29
|
+
const event = items.find((e) => typeof e.sofascore_id === 'number' && e.sofascore_id > 0);
|
|
30
|
+
if (!event)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
category: event.categories?.[0]?.name || 'EVENT',
|
|
34
|
+
title: event.title,
|
|
35
|
+
excerpt: '',
|
|
36
|
+
image: event.featuredImage?.url || '',
|
|
37
|
+
alt: event.featuredImage?.alt || event.title,
|
|
38
|
+
url: `/${event.slug}`,
|
|
39
|
+
sofascore_id: event.sofascore_id,
|
|
40
|
+
updatedAt: event.eventDate || new Date().toISOString(),
|
|
41
|
+
updates: extractUpdatesFromAuburnaleEvent(event.updates),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function fetchLiveEvent(language = 'en') {
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(`https://cdn.fifthbell.com/content/homepage-current-${language}.json?_=${Date.now()}`, { cache: 'no-store' });
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
return fetchFromAuburnale(language);
|
|
53
|
+
}
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
const main = data.breakingNews?.main;
|
|
56
|
+
if (!main || !main.title) {
|
|
57
|
+
return fetchFromAuburnale(language);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
category: main.category || 'LIVE',
|
|
61
|
+
title: main.title,
|
|
62
|
+
excerpt: main.excerpt,
|
|
63
|
+
image: main.image || '',
|
|
64
|
+
alt: main.alt || main.title,
|
|
65
|
+
url: main.url || '/',
|
|
66
|
+
liveUrl: main.liveUrl,
|
|
67
|
+
sofascore_id: main.sofascore_id,
|
|
68
|
+
updatedAt: data.updatedAt || new Date().toISOString(),
|
|
69
|
+
updates: data.breakingNews?.updates || [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('Error fetching live event:', error);
|
|
74
|
+
return fetchFromAuburnale(language);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './usePlaylistEngine.js';
|
|
3
|
+
export { createArticlesSegment, fetchArticles, type NewsItem } from './ArticlesSegment.js';
|
|
4
|
+
export { createWeatherSegment, fetchWeatherData, type WeatherRegionData, type WeatherCityData } from './WeatherSegment.js';
|
|
5
|
+
export { createEarthquakeSegment, fetchEarthquakes, type EarthquakeData } from './EarthquakeSegment.js';
|
|
6
|
+
export * from './MarketsSegment.js';
|
|
7
|
+
export { createLiveEventSegment } from './LiveEventSegment.js';
|
|
8
|
+
export { fetchLiveEvent, type LiveEventData, type LiveEventUpdate } from './fetchLiveEvents.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './usePlaylistEngine.js';
|
|
3
|
+
export { createArticlesSegment, fetchArticles } from './ArticlesSegment.js';
|
|
4
|
+
export { createWeatherSegment, fetchWeatherData } from './WeatherSegment.js';
|
|
5
|
+
export { createEarthquakeSegment, fetchEarthquakes } from './EarthquakeSegment.js';
|
|
6
|
+
export * from './MarketsSegment.js';
|
|
7
|
+
export { createLiveEventSegment } from './LiveEventSegment.js';
|
|
8
|
+
export { fetchLiveEvent } from './fetchLiveEvents.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface Segment {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
itemCount: number;
|
|
6
|
+
durationMsPerItem: number;
|
|
7
|
+
render(itemIndex: number, progress: number): React.ReactNode;
|
|
8
|
+
onEnter?(): void;
|
|
9
|
+
onExit?(): void;
|
|
10
|
+
prefetch?(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface PlaylistState {
|
|
13
|
+
currentSegmentIndex: number;
|
|
14
|
+
currentItemIndex: number;
|
|
15
|
+
progress: number;
|
|
16
|
+
isPaused: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface PlaylistConfig {
|
|
19
|
+
segments: Segment[];
|
|
20
|
+
defaultDurationMs?: number;
|
|
21
|
+
updateIntervalMs?: number;
|
|
22
|
+
onPlaylistLoop?: () => void;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PlaylistConfig, PlaylistState, Segment } from './types.js';
|
|
2
|
+
export declare function usePlaylistEngine(config: PlaylistConfig): {
|
|
3
|
+
state: PlaylistState;
|
|
4
|
+
currentSegment: Segment | null;
|
|
5
|
+
pause: () => void;
|
|
6
|
+
resume: () => void;
|
|
7
|
+
reset: () => void;
|
|
8
|
+
advanceToNext: () => void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
const DEFAULT_DURATION_MS = 10000;
|
|
3
|
+
const DEFAULT_UPDATE_INTERVAL_MS = 100;
|
|
4
|
+
export function usePlaylistEngine(config) {
|
|
5
|
+
const { segments, defaultDurationMs = DEFAULT_DURATION_MS, updateIntervalMs = DEFAULT_UPDATE_INTERVAL_MS, onPlaylistLoop } = config;
|
|
6
|
+
const [state, setState] = useState({
|
|
7
|
+
currentSegmentIndex: 0,
|
|
8
|
+
currentItemIndex: 0,
|
|
9
|
+
progress: 0,
|
|
10
|
+
isPaused: false
|
|
11
|
+
});
|
|
12
|
+
const prevSegmentIndexRef = useRef(0);
|
|
13
|
+
const getCurrentSegment = useCallback(() => {
|
|
14
|
+
if (segments.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
return segments[state.currentSegmentIndex] || null;
|
|
17
|
+
}, [segments, state.currentSegmentIndex]);
|
|
18
|
+
const pause = useCallback(() => {
|
|
19
|
+
setState((prev) => ({ ...prev, isPaused: true }));
|
|
20
|
+
}, []);
|
|
21
|
+
const resume = useCallback(() => {
|
|
22
|
+
setState((prev) => ({ ...prev, isPaused: false }));
|
|
23
|
+
}, []);
|
|
24
|
+
const reset = useCallback(() => {
|
|
25
|
+
setState({
|
|
26
|
+
currentSegmentIndex: 0,
|
|
27
|
+
currentItemIndex: 0,
|
|
28
|
+
progress: 0,
|
|
29
|
+
isPaused: false
|
|
30
|
+
});
|
|
31
|
+
}, []);
|
|
32
|
+
const calculateNextState = useCallback((prevState) => {
|
|
33
|
+
const currentSegment = segments[prevState.currentSegmentIndex];
|
|
34
|
+
if (!currentSegment) {
|
|
35
|
+
return prevState;
|
|
36
|
+
}
|
|
37
|
+
const nextItemIndex = prevState.currentItemIndex + 1;
|
|
38
|
+
if (nextItemIndex >= currentSegment.itemCount) {
|
|
39
|
+
const nextSegmentIndex = (prevState.currentSegmentIndex + 1) % segments.length;
|
|
40
|
+
return {
|
|
41
|
+
...prevState,
|
|
42
|
+
currentSegmentIndex: nextSegmentIndex,
|
|
43
|
+
currentItemIndex: 0,
|
|
44
|
+
progress: 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...prevState,
|
|
49
|
+
currentItemIndex: nextItemIndex,
|
|
50
|
+
progress: 0
|
|
51
|
+
};
|
|
52
|
+
}, [segments]);
|
|
53
|
+
const advanceToNext = useCallback(() => {
|
|
54
|
+
setState((prevState) => calculateNextState(prevState));
|
|
55
|
+
}, [calculateNextState]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const currentSegmentIndex = state.currentSegmentIndex;
|
|
58
|
+
const previousSegmentIndex = prevSegmentIndexRef.current;
|
|
59
|
+
if (currentSegmentIndex !== previousSegmentIndex) {
|
|
60
|
+
const previousSegment = segments[previousSegmentIndex];
|
|
61
|
+
previousSegment?.onExit?.();
|
|
62
|
+
const currentSegment = segments[currentSegmentIndex];
|
|
63
|
+
currentSegment?.onEnter?.();
|
|
64
|
+
const nextSegmentIndex = (currentSegmentIndex + 1) % segments.length;
|
|
65
|
+
const nextSegment = segments[nextSegmentIndex];
|
|
66
|
+
nextSegment?.prefetch?.().catch((error) => {
|
|
67
|
+
console.error(`[Playlist] Failed to prefetch ${nextSegment.label}:`, error);
|
|
68
|
+
});
|
|
69
|
+
if (currentSegmentIndex === 0 &&
|
|
70
|
+
previousSegmentIndex === segments.length - 1 &&
|
|
71
|
+
segments.length > 0) {
|
|
72
|
+
onPlaylistLoop?.();
|
|
73
|
+
}
|
|
74
|
+
prevSegmentIndexRef.current = currentSegmentIndex;
|
|
75
|
+
}
|
|
76
|
+
}, [state.currentSegmentIndex, segments, onPlaylistLoop]);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (state.isPaused || segments.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const currentSegment = segments[state.currentSegmentIndex];
|
|
82
|
+
if (!currentSegment) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const duration = currentSegment.durationMsPerItem || defaultDurationMs;
|
|
86
|
+
const progressIncrement = (updateIntervalMs / duration) * 100;
|
|
87
|
+
const timer = setInterval(() => {
|
|
88
|
+
setState((prevState) => {
|
|
89
|
+
if (prevState.progress >= 100) {
|
|
90
|
+
return calculateNextState(prevState);
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...prevState,
|
|
94
|
+
progress: prevState.progress + progressIncrement
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}, updateIntervalMs);
|
|
98
|
+
return () => clearInterval(timer);
|
|
99
|
+
}, [state.isPaused, state.currentSegmentIndex, segments, defaultDurationMs, updateIntervalMs, calculateNextState]);
|
|
100
|
+
return {
|
|
101
|
+
state,
|
|
102
|
+
currentSegment: getCurrentSegment(),
|
|
103
|
+
pause,
|
|
104
|
+
resume,
|
|
105
|
+
reset,
|
|
106
|
+
advanceToNext
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface GlobalTimeOverride {
|
|
2
|
+
startTime: string;
|
|
3
|
+
startedAt: string;
|
|
4
|
+
}
|
|
5
|
+
interface ClockParts {
|
|
6
|
+
hours: number;
|
|
7
|
+
minutes: number;
|
|
8
|
+
seconds: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function getOverrideClockParts(override: GlobalTimeOverride, now?: Date): ClockParts | null;
|
|
11
|
+
export declare function formatClockParts(parts: ClockParts): string;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function parseStartTime(startTime) {
|
|
2
|
+
const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(startTime.trim());
|
|
3
|
+
if (!match) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
return {
|
|
7
|
+
hours: Number(match[1]),
|
|
8
|
+
minutes: Number(match[2]),
|
|
9
|
+
seconds: 0,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function getOverrideClockParts(override, now = new Date()) {
|
|
13
|
+
const parsed = parseStartTime(override.startTime);
|
|
14
|
+
if (!parsed) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const startedAtMillis = new Date(override.startedAt).getTime();
|
|
18
|
+
if (!Number.isFinite(startedAtMillis)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const startSeconds = parsed.hours * 3600 + parsed.minutes * 60;
|
|
22
|
+
const elapsedSeconds = Math.max(0, Math.floor((now.getTime() - startedAtMillis) / 1000));
|
|
23
|
+
const totalSeconds = (startSeconds + elapsedSeconds) % 86400;
|
|
24
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
25
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
26
|
+
const seconds = totalSeconds % 60;
|
|
27
|
+
return { hours, minutes, seconds };
|
|
28
|
+
}
|
|
29
|
+
export function formatClockParts(parts) {
|
|
30
|
+
const hours = String(parts.hours).padStart(2, '0');
|
|
31
|
+
const minutes = String(parts.minutes).padStart(2, '0');
|
|
32
|
+
return `${hours}:${minutes}`;
|
|
33
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|