@openeventkit/event-site 2.1.17 → 2.1.18-beta.2
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/.nvmrc +1 -1
- package/package.json +6 -6
- package/src/actions/event-actions.js +31 -0
- package/src/cms/preview-templates/ContentPagePreview.js +27 -29
- package/src/components/ErrorMessage.js +25 -0
- package/src/components/Mdx.js +29 -0
- package/src/components/VideoComponent.js +3 -1
- package/src/content/site-settings/index.json +1 -1
- package/src/content/sponsors.json +1 -1
- package/src/i18n/index.js +20 -0
- package/src/i18n/locales/en.json +13 -0
- package/src/pages/a/overflow-player.js +149 -0
- package/src/reducers/event-reducer.js +4 -2
- package/src/styles/colors.scss +2 -2
- package/src/styles/video.module.scss +1 -0
- package/src/templates/marketing-page-template/MainColumn.js +40 -42
- package/src/utils/arrayUtils.js +22 -1
- package/src/workers/sync_strategies/activity_synch_strategy.js +3 -5
- package/src/workers/sync_strategies/speaker_synch_strategy.js +72 -85
- package/src/workers/sync_strategies/summit_synch_strategy.js +8 -1
- package/src/workers/sync_strategies/venue_room_synch_strategy.js +11 -1
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
20.19.4
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeventkit/event-site",
|
|
3
3
|
"description": "Event Site",
|
|
4
|
-
"version": "2.1.
|
|
4
|
+
"version": "2.1.18-beta.2",
|
|
5
5
|
"author": "Tipit LLC",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@emotion/server": "^11.11.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
|
10
10
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
|
11
11
|
"@loadable/component": "^5.16.4",
|
|
12
|
-
"@mdx-js/
|
|
13
|
-
"@mdx-js/
|
|
12
|
+
"@mdx-js/mdx": "^3",
|
|
13
|
+
"@mdx-js/react": "^3",
|
|
14
14
|
"@mui/base": "^5.0.0-beta.40",
|
|
15
15
|
"@mui/icons-material": "^5.15.20",
|
|
16
16
|
"@mui/material": "^5.15.20",
|
|
@@ -123,9 +123,9 @@
|
|
|
123
123
|
"redux-thunk": "^2.4.1",
|
|
124
124
|
"rehype-external-links": "^3.0.0",
|
|
125
125
|
"rehype-mdx-import-media": "^1.2.0",
|
|
126
|
-
"remark-gfm": "^4.0.
|
|
126
|
+
"remark-gfm": "^4.0.1",
|
|
127
127
|
"sass": "^1.49.9",
|
|
128
|
-
"schedule-filter-widget": "3.0.
|
|
128
|
+
"schedule-filter-widget": "3.0.4-beta.2",
|
|
129
129
|
"simple-chat-widget": "^1.0.31",
|
|
130
130
|
"simple-oauth2": "^4.1.0",
|
|
131
131
|
"slick-carousel": "^1.8.1",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"stream-browserify": "^3.0.0",
|
|
136
136
|
"stream-chat": "^2.7.2",
|
|
137
137
|
"stream-chat-react": "3.1.7",
|
|
138
|
-
"summit-registration-lite": "6.0.
|
|
138
|
+
"summit-registration-lite": "6.0.4-beta.1",
|
|
139
139
|
"superagent": "8.0.9",
|
|
140
140
|
"sweetalert2": "^11.11.1",
|
|
141
141
|
"upcoming-events-widget": "3.0.7",
|
|
@@ -112,3 +112,34 @@ export const getEventStreamingInfoById = (
|
|
|
112
112
|
return (e);
|
|
113
113
|
});
|
|
114
114
|
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Fetch overflow event by stream key
|
|
118
|
+
* @param {string} overflowStreamKey
|
|
119
|
+
* @returns {(function(*): Promise<*>)|*}
|
|
120
|
+
*/
|
|
121
|
+
export const getOverflowEventByKey = (
|
|
122
|
+
overflowStreamKey
|
|
123
|
+
) => async (dispatch) => {
|
|
124
|
+
dispatch(startLoading());
|
|
125
|
+
|
|
126
|
+
let params = {
|
|
127
|
+
k: overflowStreamKey
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return getRequest(
|
|
131
|
+
null,
|
|
132
|
+
createAction(GET_EVENT_DATA),
|
|
133
|
+
`${window.SUMMIT_API_BASE_URL}/api/public/v1/summits/${window.SUMMIT_ID}/events/all/published/overflow`,
|
|
134
|
+
customErrorHandler
|
|
135
|
+
)(params)(dispatch).then((payload) => {
|
|
136
|
+
dispatch(stopLoading());
|
|
137
|
+
return payload.response;
|
|
138
|
+
}).catch(e => {
|
|
139
|
+
dispatch(stopLoading());
|
|
140
|
+
dispatch(createAction(GET_EVENT_DATA_ERROR)(e));
|
|
141
|
+
console.error("ERROR: ", e);
|
|
142
|
+
return (e);
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
@@ -1,49 +1,47 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
|
-
import Mdx from "@mdx-js/runtime";
|
|
4
3
|
import ContentPageTemplate from "../../templates/content-page/template";
|
|
5
4
|
import shortcodes from "../../templates/content-page/shortcodes";
|
|
5
|
+
import Mdx from "../../components/Mdx";
|
|
6
6
|
|
|
7
7
|
// function to transform content by replacing relative image URLs with absolute ones
|
|
8
8
|
const transformContent = (mdx, getAsset) => {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// regex to identify Markdown image tags 
|
|
10
|
+
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
return mdx.replace(imageRegex, (match, alt, url) => {
|
|
13
|
+
// check if the URL is relative (does not start with http:// or https://)
|
|
14
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
15
|
+
const asset = getAsset(url);
|
|
16
|
+
if (asset && asset.url) {
|
|
17
|
+
return ``;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return match; // return the original match if it's already an absolute URL
|
|
21
|
+
});
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
// function to render transformed content with Mdx
|
|
25
25
|
const renderContent = (mdx, getAsset) => (
|
|
26
|
-
|
|
27
|
-
{transformContent(mdx, getAsset)}
|
|
28
|
-
</Mdx>
|
|
26
|
+
<Mdx shortcodes={shortcodes} content={transformContent(mdx, getAsset)}/>
|
|
29
27
|
);
|
|
30
28
|
|
|
31
29
|
const ContentPagePreview = ({ entry, getAsset }) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
const title = entry.getIn(["data", "title"]);
|
|
31
|
+
const body = entry.getIn(["data", "body"]);
|
|
32
|
+
return (
|
|
33
|
+
<ContentPageTemplate
|
|
34
|
+
title={title}
|
|
35
|
+
content={renderContent(body, getAsset)}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
40
38
|
};
|
|
41
39
|
|
|
42
40
|
ContentPagePreview.propTypes = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
entry: PropTypes.shape({
|
|
42
|
+
getIn: PropTypes.func.isRequired
|
|
43
|
+
}).isRequired,
|
|
44
|
+
getAsset: PropTypes.func.isRequired
|
|
47
45
|
};
|
|
48
46
|
|
|
49
|
-
export default ContentPagePreview;
|
|
47
|
+
export default ContentPagePreview;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Container, Box, Typography } from "@mui/material";
|
|
3
|
+
|
|
4
|
+
const ErrorMessage = ({ title, message, fullScreen = false }) => {
|
|
5
|
+
const containerStyles = fullScreen
|
|
6
|
+
? { height: "100vh", display: "flex", justifyContent: "center", alignItems: "center" }
|
|
7
|
+
: {};
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Container maxWidth={false} style={containerStyles}>
|
|
11
|
+
<Box display="flex" justifyContent="center" alignItems="center" height={fullScreen ? "auto" : "100vh"}>
|
|
12
|
+
<Box textAlign="center" padding={4}>
|
|
13
|
+
<Typography variant="h3" component="h1" gutterBottom style={{ fontWeight: "bold", color: "#333" }}>
|
|
14
|
+
{title}
|
|
15
|
+
</Typography>
|
|
16
|
+
<Typography variant="h4" component="p" style={{ color: "#666", lineHeight: 1.6, whiteSpace: "pre-line" }}>
|
|
17
|
+
{message}
|
|
18
|
+
</Typography>
|
|
19
|
+
</Box>
|
|
20
|
+
</Box>
|
|
21
|
+
</Container>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default ErrorMessage;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { evaluateSync } from "@mdx-js/mdx";
|
|
3
|
+
import * as jsxRuntime from "react/jsx-runtime";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
5
|
+
import rehypeExternalLinks from "rehype-external-links";
|
|
6
|
+
import { MDXProvider } from "@mdx-js/react";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const Mdx = ({ content, shortcodes }) => {
|
|
10
|
+
const mdxContent = useMemo(() => {
|
|
11
|
+
if ( !content) return null;
|
|
12
|
+
try {
|
|
13
|
+
const { default: Comp } = evaluateSync(content, {
|
|
14
|
+
...jsxRuntime,
|
|
15
|
+
remarkPlugins: [remarkGfm],
|
|
16
|
+
rehypePlugins: [[rehypeExternalLinks, { target: "_blank", rel: ["nofollow","noopener","noreferrer"] }]],
|
|
17
|
+
useDynamicImport: false, // ensure no async imports in runtime content
|
|
18
|
+
});
|
|
19
|
+
return Comp;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error("MDX evaluate error:", err);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}, [content]);
|
|
25
|
+
|
|
26
|
+
return <MDXProvider components={shortcodes}>{mdxContent}</MDXProvider>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default Mdx;
|
|
@@ -20,7 +20,7 @@ import SynchWordsPlayer from "./SyncWordsPlayer";
|
|
|
20
20
|
* @returns {JSX.Element}
|
|
21
21
|
* @constructor
|
|
22
22
|
*/
|
|
23
|
-
const VideoComponent = ({ url, title, namespace, isLive, firstHalf, autoPlay, start, tokens, onError = () => {} }) => {
|
|
23
|
+
const VideoComponent = ({ url, title, namespace, isLive, firstHalf, autoPlay, start, tokens, onError = () => {}, onLoaded = () => {} }) => {
|
|
24
24
|
|
|
25
25
|
if (url) {
|
|
26
26
|
// using mux player
|
|
@@ -46,6 +46,8 @@ const VideoComponent = ({ url, title, namespace, isLive, firstHalf, autoPlay, st
|
|
|
46
46
|
autoplay={autoPlay}
|
|
47
47
|
start={start}
|
|
48
48
|
className={styles.vimeoPlayer}
|
|
49
|
+
onLoaded={onLoaded}
|
|
50
|
+
onError={onError}
|
|
49
51
|
/>
|
|
50
52
|
);
|
|
51
53
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"favicon":{"asset":"icon.png"},"widgets":{"chat":{"enabled":true,"showQA":false,"showHelp":false,"defaultScope":"page"},"schedule":{"allowClick":true}},"identityProviderButtons":[{"buttonColor":"#082238","providerLabel":"Continue with FNid","providerLogo":"logo_fn.svg","providerLogoSize":27},{"buttonColor":"#0A66C2","providerLabel":"Sign in with LinkedIn","providerParam":"linkedin","providerLogo":"logo_linkedin.svg","providerLogoSize":18},{"buttonColor":"#000000","providerLabel":"Sign in with Apple","providerParam":"apple","providerLogoSize":17,"providerLogo":"logo_apple.svg"},{"buttonColor":"#1877F2","providerLabel":"Login with Facebook","providerParam":"facebook","providerLogo":"logo_facebook.svg","providerLogoSize":20}],"maintenanceMode":{"enabled":false,"title":"Site under maintenance","subtitle":"Please reload page shortly"},"staticJsonFilesBuildTime":[{"file":"src/data/summit.json","build_time":
|
|
1
|
+
{"favicon":{"asset":"icon.png"},"widgets":{"chat":{"enabled":true,"showQA":false,"showHelp":false,"defaultScope":"page"},"schedule":{"allowClick":true}},"identityProviderButtons":[{"buttonColor":"#082238","providerLabel":"Continue with FNid","providerLogo":"logo_fn.svg","providerLogoSize":27},{"buttonColor":"#0A66C2","providerLabel":"Sign in with LinkedIn","providerParam":"linkedin","providerLogo":"logo_linkedin.svg","providerLogoSize":18},{"buttonColor":"#000000","providerLabel":"Sign in with Apple","providerParam":"apple","providerLogoSize":17,"providerLogo":"logo_apple.svg"},{"buttonColor":"#1877F2","providerLabel":"Login with Facebook","providerParam":"facebook","providerLogo":"logo_facebook.svg","providerLogoSize":20}],"maintenanceMode":{"enabled":false,"title":"Site under maintenance","subtitle":"Please reload page shortly"},"staticJsonFilesBuildTime":[{"file":"src/data/summit.json","build_time":1755200450530},{"file":"src/data/events.json","build_time":1755200471128},{"file":"src/data/events.idx.json","build_time":1755200471179},{"file":"src/data/speakers.json","build_time":1755200476104},{"file":"src/data/speakers.idx.json","build_time":1755200476112},{"file":"src/content/sponsors.json","build_time":1755200476553},{"file":"src/data/voteable-presentations.json","build_time":1755200476925}],"lastBuild":1755200476925}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
[
|
|
1
|
+
[]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import i18n from "i18next";
|
|
2
|
+
import { initReactI18next } from "react-i18next";
|
|
3
|
+
import LanguageDetector from "i18next-browser-languagedetector";
|
|
4
|
+
import en from "./locales/en.json";
|
|
5
|
+
|
|
6
|
+
i18n
|
|
7
|
+
.use(LanguageDetector)
|
|
8
|
+
.use(initReactI18next)
|
|
9
|
+
.init({
|
|
10
|
+
fallbackLng: "en",
|
|
11
|
+
debug: false,
|
|
12
|
+
resources: {
|
|
13
|
+
en: { translation: en }
|
|
14
|
+
},
|
|
15
|
+
interpolation: {
|
|
16
|
+
escapeValue: false,
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default i18n;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"overflow_player": {
|
|
3
|
+
"no_stream_key": "No stream key provided. Please check the URL.",
|
|
4
|
+
"stream_unavailable": {
|
|
5
|
+
"title": "Stream Unavailable",
|
|
6
|
+
"message": "The overflow stream you're looking for is not currently available.\nPlease check with event staff or try again later."
|
|
7
|
+
},
|
|
8
|
+
"no_stream_available": {
|
|
9
|
+
"title": "No Stream Available",
|
|
10
|
+
"message": "No event data found for this overflow stream."
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { useParams } from "react-router-dom";
|
|
3
|
+
import { connect } from "react-redux";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import { getOverflowEventByKey } from "../../actions/event-actions";
|
|
6
|
+
import { Container, Box, CircularProgress, Typography, Fade } from "@mui/material";
|
|
7
|
+
import VideoComponent from "../../components/VideoComponent";
|
|
8
|
+
import ErrorMessage from "../../components/ErrorMessage";
|
|
9
|
+
import "../../i18n";
|
|
10
|
+
|
|
11
|
+
const OverflowPlayerPage = ({
|
|
12
|
+
location,
|
|
13
|
+
fetchOverflowEvent,
|
|
14
|
+
loading,
|
|
15
|
+
event,
|
|
16
|
+
error
|
|
17
|
+
}) => {
|
|
18
|
+
const params = new URLSearchParams(location.search);
|
|
19
|
+
const overflowStreamKey = params.get("k");
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
const [showTitleFlash, setShowTitleFlash] = useState(false);
|
|
22
|
+
const [videoError, setVideoError] = useState(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (overflowStreamKey) {
|
|
26
|
+
fetchOverflowEvent(overflowStreamKey);
|
|
27
|
+
}
|
|
28
|
+
}, [overflowStreamKey, fetchOverflowEvent]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (event && !loading && !error) {
|
|
32
|
+
setShowTitleFlash(true);
|
|
33
|
+
}
|
|
34
|
+
}, [event, loading, error]);
|
|
35
|
+
|
|
36
|
+
const handleVideoLoad = () => {
|
|
37
|
+
setShowTitleFlash(false);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleVideoError = (e) => {
|
|
41
|
+
console.error("Video error:", e);
|
|
42
|
+
setVideoError(true);
|
|
43
|
+
setShowTitleFlash(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (!overflowStreamKey) {
|
|
47
|
+
return (
|
|
48
|
+
<ErrorMessage
|
|
49
|
+
title={t('overflow_player.stream_unavailable.title')}
|
|
50
|
+
message={t('overflow_player.no_stream_key')}
|
|
51
|
+
fullScreen={true}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
{loading || (!event && !error && overflowStreamKey) ? (
|
|
59
|
+
<Container maxWidth={false}>
|
|
60
|
+
<Box display="flex" justifyContent="center" alignItems="center" height="100vh">
|
|
61
|
+
<CircularProgress sx={{ color: 'var(--color_accent)' }} />
|
|
62
|
+
</Box>
|
|
63
|
+
</Container>
|
|
64
|
+
) : error ? (
|
|
65
|
+
<ErrorMessage
|
|
66
|
+
title={t('overflow_player.stream_unavailable.title')}
|
|
67
|
+
message={t('overflow_player.stream_unavailable.message')}
|
|
68
|
+
fullScreen={true}
|
|
69
|
+
/>
|
|
70
|
+
) : event ? (
|
|
71
|
+
videoError ? (
|
|
72
|
+
<ErrorMessage
|
|
73
|
+
title={t('overflow_player.stream_unavailable.title')}
|
|
74
|
+
message={t('overflow_player.stream_unavailable.message')}
|
|
75
|
+
fullScreen={true}
|
|
76
|
+
/>
|
|
77
|
+
) : (
|
|
78
|
+
<Box
|
|
79
|
+
width="100vw"
|
|
80
|
+
height="100vh"
|
|
81
|
+
position="relative"
|
|
82
|
+
sx={{
|
|
83
|
+
'& .video-module--vimeoPlayer--ac603': {
|
|
84
|
+
paddingBottom: '0 !important',
|
|
85
|
+
height: '100vh !important',
|
|
86
|
+
'& iframe': {
|
|
87
|
+
width: '100vw !important',
|
|
88
|
+
height: '100vh !important',
|
|
89
|
+
position: 'absolute',
|
|
90
|
+
top: 0,
|
|
91
|
+
left: 0
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<VideoComponent
|
|
97
|
+
url={event.overflow_streaming_url}
|
|
98
|
+
title={event.title}
|
|
99
|
+
isLive={true}
|
|
100
|
+
autoPlay={true}
|
|
101
|
+
tokens={event.overflow_tokens}
|
|
102
|
+
onLoaded={handleVideoLoad}
|
|
103
|
+
onError={handleVideoError}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<Fade in={showTitleFlash} timeout={500}>
|
|
107
|
+
<Typography
|
|
108
|
+
variant="h3"
|
|
109
|
+
component="h1"
|
|
110
|
+
position="absolute"
|
|
111
|
+
top="50%"
|
|
112
|
+
left="50%"
|
|
113
|
+
sx={{
|
|
114
|
+
transform: 'translate(-50%, -50%)',
|
|
115
|
+
color: 'white',
|
|
116
|
+
fontWeight: 'bold',
|
|
117
|
+
textShadow: '2px 2px 4px rgba(0,0,0,0.8)',
|
|
118
|
+
zIndex: 1000,
|
|
119
|
+
maxWidth: '80%',
|
|
120
|
+
textAlign: 'center'
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
{event.title}
|
|
124
|
+
</Typography>
|
|
125
|
+
</Fade>
|
|
126
|
+
</Box>
|
|
127
|
+
)
|
|
128
|
+
) : (
|
|
129
|
+
<ErrorMessage
|
|
130
|
+
title={t('overflow_player.no_stream_available.title')}
|
|
131
|
+
message={t('overflow_player.no_stream_available.message')}
|
|
132
|
+
fullScreen={true}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
</>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const mapStateToProps = ({ eventState }) => ({
|
|
140
|
+
loading: eventState.loading,
|
|
141
|
+
event: eventState.event,
|
|
142
|
+
error: eventState.error
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const mapDispatchToProps = (dispatch) => ({
|
|
146
|
+
fetchOverflowEvent: (streamKey) => dispatch(getOverflowEventByKey(streamKey))
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
export default connect(mapStateToProps, mapDispatchToProps)(OverflowPlayerPage);
|
|
@@ -8,6 +8,7 @@ const DEFAULT_STATE = {
|
|
|
8
8
|
event: null,
|
|
9
9
|
lastUpdate: null,
|
|
10
10
|
tokens:null,
|
|
11
|
+
error: null,
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
const eventReducer = (state = DEFAULT_STATE, action) => {
|
|
@@ -31,7 +32,7 @@ const eventReducer = (state = DEFAULT_STATE, action) => {
|
|
|
31
32
|
const event = payload?.response ?? payload.event;
|
|
32
33
|
// check if we need to update the current event or do we need to just use the new one
|
|
33
34
|
const updatedEvent = event.id === state?.event?.id ? {...state, ...event} : event;
|
|
34
|
-
return { ...state, loading: false, event: updatedEvent, tokens: null };
|
|
35
|
+
return { ...state, loading: false, event: updatedEvent, tokens: null, error: null };
|
|
35
36
|
}
|
|
36
37
|
case SYNC_DATA:{
|
|
37
38
|
// update current event if we have one on data sync
|
|
@@ -46,7 +47,8 @@ const eventReducer = (state = DEFAULT_STATE, action) => {
|
|
|
46
47
|
return state;
|
|
47
48
|
}
|
|
48
49
|
case GET_EVENT_DATA_ERROR: {
|
|
49
|
-
|
|
50
|
+
const errorMessage = payload?.message || payload?.error || 'Failed to load event';
|
|
51
|
+
return { ...state, loading: false, event: null, tokens: null, error: errorMessage }
|
|
50
52
|
}
|
|
51
53
|
// reload event state
|
|
52
54
|
case RELOAD_EVENT_STATE:{
|
package/src/styles/colors.scss
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
$color_accent: #
|
|
1
|
+
$color_accent: #8ac82d;
|
|
2
2
|
$color_alerts: #ff0000;
|
|
3
3
|
$color_background_light: #ffffff;
|
|
4
4
|
$color_background_dark: #000000;
|
|
@@ -21,7 +21,7 @@ $color_input_text_color_disabled_light: #ffffff;
|
|
|
21
21
|
$color_input_text_color_disabled_dark: #ffffff;
|
|
22
22
|
$color_primary: #8DC63F;
|
|
23
23
|
$color_primary_contrast: #FFFFFF;
|
|
24
|
-
$color_secondary: #
|
|
24
|
+
$color_secondary: #26387f;
|
|
25
25
|
$color_secondary_contrast: #005870;
|
|
26
26
|
$color_text_light: #ffffff;
|
|
27
27
|
$color_text_med: #828282;
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { navigate } from "gatsby";
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { GatsbyImage, getImage } from "gatsby-plugin-image";
|
|
5
|
-
import Mdx from "
|
|
5
|
+
import Mdx from "../../components/Mdx";
|
|
6
6
|
import Container from "./Container";
|
|
7
7
|
import LiteScheduleComponent from "../../components/LiteScheduleComponent";
|
|
8
8
|
import DisqusComponent from "../../components/DisqusComponent";
|
|
@@ -22,49 +22,47 @@ const onEventClick = (ev) => navigate(`/a/event/${ev.id}`);
|
|
|
22
22
|
|
|
23
23
|
const MainColumn = ({ widgets, summitPhase, isLoggedUser, onEventClick, lastDataSync, fullWidth, maxHeight }) => {
|
|
24
24
|
const { content, schedule, disqus, image } = widgets || {};
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
const scheduleProps = schedule && isLoggedUser && summitPhase !== PHASES.BEFORE ? { onEventClick } : {};
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
</Container>
|
|
67
|
-
</div>
|
|
29
|
+
<div
|
|
30
|
+
className={`column pt-6 pb-5 px-6 ${!fullWidth ? "is-half" : ""} ${styles.mainColumn || ""}`}
|
|
31
|
+
style={{ maxHeight: !fullWidth && maxHeight ? maxHeight : "none", overflowY: "auto" }}
|
|
32
|
+
>
|
|
33
|
+
<Container>
|
|
34
|
+
{content?.display && content?.body && (
|
|
35
|
+
<Mdx shortcodes={shortcodes} content={content.body}/>
|
|
36
|
+
)}
|
|
37
|
+
{schedule?.display && (
|
|
38
|
+
<>
|
|
39
|
+
<h2><b>{schedule.title}</b></h2>
|
|
40
|
+
<LiteScheduleComponent
|
|
41
|
+
{...scheduleProps}
|
|
42
|
+
lastDataSync={lastDataSync}
|
|
43
|
+
id={`marketing_lite_schedule_${lastDataSync}`}
|
|
44
|
+
page="marketing-site"
|
|
45
|
+
showAllEvents={true}
|
|
46
|
+
showSearch={false}
|
|
47
|
+
showNav={true}
|
|
48
|
+
/>
|
|
49
|
+
</>
|
|
50
|
+
)}
|
|
51
|
+
{disqus?.display && (
|
|
52
|
+
<>
|
|
53
|
+
<h2><b>{disqus.title}</b></h2>
|
|
54
|
+
<DisqusComponent page="marketing-site" />
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
57
|
+
{image?.display && image?.image?.src && (
|
|
58
|
+
<>
|
|
59
|
+
<h2><b>{image.title}</b></h2>
|
|
60
|
+
<br/>
|
|
61
|
+
<GatsbyImage image={getImage(image.image.src)} alt={image.image.alt ?? ""} />
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
</Container>
|
|
65
|
+
</div>
|
|
68
66
|
);
|
|
69
67
|
};
|
|
70
68
|
|
|
@@ -77,4 +75,4 @@ MainColumn.propTypes = {
|
|
|
77
75
|
maxHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
|
78
76
|
};
|
|
79
77
|
|
|
80
|
-
export default MainColumn;
|
|
78
|
+
export default MainColumn;
|
package/src/utils/arrayUtils.js
CHANGED
|
@@ -12,4 +12,25 @@ export const insertSorted = (arr, elem, fnCheck) =>
|
|
|
12
12
|
arr[i + 1] = arr[i];
|
|
13
13
|
arr[i + 1] = elem;
|
|
14
14
|
return i+1;
|
|
15
|
-
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const rebuildIndex = (array, key = 'id') => {
|
|
18
|
+
return array.reduce((acc, item, index) => {
|
|
19
|
+
acc[item[key]] = index;
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getIndexedItem = (indexMap, array, id) => {
|
|
25
|
+
const idx = indexMap?.[id] ?? -1;
|
|
26
|
+
const item = idx !== -1 ? array[idx] : null;
|
|
27
|
+
return item && item.id === id ? { idx, item } : null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const replaceInArray = (array, predicate, newItem) => {
|
|
31
|
+
const found = array.findIndex(predicate);
|
|
32
|
+
if (found !== -1) {
|
|
33
|
+
return [...array.slice(0, found), newItem, ...array.slice(found + 1)];
|
|
34
|
+
}
|
|
35
|
+
return array;
|
|
36
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import AbstractSynchStrategy from "./abstract_synch_strategy";
|
|
2
2
|
import {fetchEventById, fetchStreamingInfoByEventId} from "../../actions/fetch-entities-actions";
|
|
3
|
-
import {insertSorted, intCheck} from "../../utils/arrayUtils";
|
|
3
|
+
import {insertSorted, intCheck, rebuildIndex} from "../../utils/arrayUtils";
|
|
4
4
|
import {
|
|
5
5
|
BUCKET_EVENTS_DATA_KEY,
|
|
6
6
|
BUCKET_EVENTS_IDX_DATA_KEY,
|
|
@@ -62,10 +62,8 @@ class ActivitySynchStrategy extends AbstractSynchStrategy{
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
// Rebuild the full event index to be safe
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return acc;
|
|
68
|
-
}, {});
|
|
65
|
+
|
|
66
|
+
this.allIDXEvents = rebuildIndex(eventsData);
|
|
69
67
|
|
|
70
68
|
// Update speakers
|
|
71
69
|
if (entity.speakers) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import AbstractSynchStrategy from "./abstract_synch_strategy";
|
|
2
2
|
import {fetchSpeakerById} from "../../actions/fetch-entities-actions";
|
|
3
3
|
import {
|
|
4
|
+
BUCKET_SUMMIT_DATA_KEY,
|
|
4
5
|
BUCKET_EVENTS_DATA_KEY,
|
|
5
6
|
BUCKET_EVENTS_IDX_DATA_KEY,
|
|
6
7
|
BUCKET_SPEAKERS_DATA_KEY,
|
|
@@ -8,120 +9,106 @@ import {
|
|
|
8
9
|
saveFile
|
|
9
10
|
} from "../../utils/dataUpdatesUtils";
|
|
10
11
|
|
|
12
|
+
import {rebuildIndex, getIndexedItem} from "../../utils/arrayUtils";
|
|
13
|
+
import moment from "moment-timezone";
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* SpeakerSynchStrategy
|
|
13
17
|
*/
|
|
14
|
-
class SpeakerSynchStrategy extends AbstractSynchStrategy{
|
|
18
|
+
class SpeakerSynchStrategy extends AbstractSynchStrategy {
|
|
15
19
|
async process(payload) {
|
|
16
20
|
|
|
17
21
|
console.log(`SpeakerSynchStrategy::process`, payload);
|
|
18
22
|
|
|
19
23
|
const {entity_operator, entity_id} = payload;
|
|
20
24
|
|
|
25
|
+
if (entity_operator !== 'UPDATE') return;
|
|
26
|
+
|
|
21
27
|
const entity = await fetchSpeakerById(this.summit.id, entity_id, this.accessToken);
|
|
22
28
|
|
|
29
|
+
if (!entity) return Promise.reject('SpeakerSynchStrategy::process entity not retrieved.');
|
|
30
|
+
|
|
23
31
|
let eventsData = [...this.allEvents];
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
return res;
|
|
38
|
-
});
|
|
39
|
-
if(!formerEntity){
|
|
40
|
-
return Promise.reject(`SpeakerSynchStrategy::process entity id ${entity.id} not found`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const updatedSpeaker = formerEntity ? {...formerEntity, ...entity} : entity;
|
|
44
|
-
if(idx === -1){
|
|
45
|
-
console.log(`SpeakerSynchStrategy::process entity does not exists, inserting it at end`, updatedSpeaker);
|
|
46
|
-
this.allSpeakers.push(updatedSpeaker);
|
|
47
|
-
this.allIDXSpeakers[updatedSpeaker.id] = this.allSpeakers.length - 1;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
console.log(`SpeakerSynchStrategy::process updating speaker at idx ${idx}`, updatedSpeaker);
|
|
51
|
-
this.allSpeakers[idx] = updatedSpeaker;
|
|
33
|
+
// Remove speaker and re-insert
|
|
34
|
+
this.allSpeakers = this.allSpeakers.filter(s => s.id !== entity_id);
|
|
35
|
+
this.allSpeakers.push(entity);
|
|
36
|
+
this.allIDXSpeakers = rebuildIndex(this.allSpeakers);
|
|
37
|
+
|
|
38
|
+
// Update events where speaker is listed
|
|
39
|
+
for (const eventId of entity.all_presentations || []) {
|
|
40
|
+
const res = getIndexedItem(this.allIDXEvents, eventsData, eventId);
|
|
41
|
+
if (!res || !Array.isArray(res.item.speakers)) {
|
|
42
|
+
console.log(`SpeakerSynchStrategy::process: event ${eventId} not found or invalid`);
|
|
43
|
+
continue;
|
|
52
44
|
}
|
|
53
45
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const eventIdx = this.allIDXEvents.hasOwnProperty(publishedEventId) ? this.allIDXEvents[publishedEventId] : -1;
|
|
58
|
-
let formerEntity = eventIdx === -1 ? null : ( (eventsData.length - 1) >= eventIdx ? eventsData[eventIdx] : null);
|
|
59
|
-
if(formerEntity === null){
|
|
60
|
-
console.log(`SpeakerSynchStrategy::process all_presentations activity ${publishedEventId} not found on data set`);
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (formerEntity && formerEntity.id !== publishedEventId) continue;
|
|
64
|
-
// check if speakers collection
|
|
65
|
-
let speakers = formerEntity.speakers.map( s => {
|
|
66
|
-
if(s.id === entity_id){
|
|
67
|
-
return updatedSpeaker;
|
|
68
|
-
}
|
|
69
|
-
return s;
|
|
70
|
-
})
|
|
71
|
-
eventsData[eventIdx] = {...formerEntity, speakers: speakers};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
46
|
+
const updatedSpeakers = res.item.speakers.map(s =>
|
|
47
|
+
s.id === entity.id ? entity : s
|
|
48
|
+
);
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if(formerEntity === null) {
|
|
81
|
-
console.log(`SpeakerSynchStrategy::process all_moderated_presentations activity ${publishedEventId} not found on data set`);
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (formerEntity && formerEntity.id !== publishedEventId) continue; // it's not the same
|
|
85
|
-
// check if speakers collection
|
|
86
|
-
eventsData[eventIdx] = {...formerEntity, moderator: updatedSpeaker};
|
|
87
|
-
}
|
|
88
|
-
}
|
|
50
|
+
eventsData[res.idx] = {
|
|
51
|
+
...res.item,
|
|
52
|
+
speakers: updatedSpeakers,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
89
55
|
|
|
90
|
-
|
|
91
|
-
|
|
56
|
+
// Update events where speaker is moderator
|
|
57
|
+
for (const eventId of entity.all_moderated_presentations || []) {
|
|
58
|
+
const res = getIndexedItem(this.allIDXEvents, eventsData, eventId);
|
|
59
|
+
if (!res) {
|
|
60
|
+
console.log(`SpeakerSynchStrategy::process: moderator event ${eventId} not found`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
92
63
|
|
|
93
|
-
|
|
94
|
-
|
|
64
|
+
eventsData[res.idx] = {
|
|
65
|
+
...res.item,
|
|
66
|
+
moderator: entity,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
95
69
|
|
|
96
|
-
|
|
70
|
+
// Update summit timestamp
|
|
71
|
+
this.summit = {
|
|
72
|
+
...this.summit,
|
|
73
|
+
timestamp: moment().unix(),
|
|
74
|
+
};
|
|
97
75
|
|
|
98
|
-
|
|
76
|
+
// update files on cache
|
|
77
|
+
console.log(`SpeakerSynchStrategy::process updating cache files`);
|
|
99
78
|
|
|
100
|
-
|
|
79
|
+
try {
|
|
80
|
+
const localNowUtc = Date.now();
|
|
101
81
|
|
|
102
|
-
|
|
82
|
+
await saveFile(this.summit.id, BUCKET_SUMMIT_DATA_KEY, this.summit, localNowUtc);
|
|
103
83
|
|
|
104
|
-
|
|
105
|
-
catch (e){
|
|
106
|
-
console.log(e);
|
|
107
|
-
}
|
|
84
|
+
await saveFile(this.summit.id, BUCKET_EVENTS_DATA_KEY, eventsData, localNowUtc);
|
|
108
85
|
|
|
109
|
-
|
|
110
|
-
payload,
|
|
111
|
-
entity,
|
|
112
|
-
summit : this.summit,
|
|
113
|
-
eventsData,
|
|
114
|
-
allIDXEvents : this.allIDXEvents,
|
|
115
|
-
allSpeakers : this.allSpeakers,
|
|
116
|
-
allIDXSpeakers : this.allIDXSpeakers
|
|
117
|
-
};
|
|
86
|
+
await saveFile(this.summit.id, BUCKET_EVENTS_IDX_DATA_KEY, this.allIDXEvents, localNowUtc);
|
|
118
87
|
|
|
119
|
-
|
|
88
|
+
await saveFile(this.summit.id, BUCKET_SPEAKERS_DATA_KEY, this.allSpeakers, localNowUtc);
|
|
120
89
|
|
|
121
|
-
|
|
90
|
+
await saveFile(this.summit.id, BUCKET_SPEAKERS_IDX_DATA_KEY, this.allIDXSpeakers, localNowUtc);
|
|
122
91
|
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.log(e);
|
|
123
94
|
}
|
|
95
|
+
|
|
96
|
+
let res = {
|
|
97
|
+
payload,
|
|
98
|
+
entity,
|
|
99
|
+
summit: this.summit,
|
|
100
|
+
eventsData,
|
|
101
|
+
allIDXEvents: this.allIDXEvents,
|
|
102
|
+
allSpeakers: this.allSpeakers,
|
|
103
|
+
allIDXSpeakers: this.allIDXSpeakers
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
console.log(`SpeakerSynchStrategy::process done`, res);
|
|
107
|
+
|
|
108
|
+
return Promise.resolve(res);
|
|
109
|
+
|
|
124
110
|
}
|
|
111
|
+
|
|
125
112
|
}
|
|
126
113
|
|
|
127
114
|
export default SpeakerSynchStrategy;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
BUCKET_SUMMIT_DATA_KEY,
|
|
5
5
|
saveFile
|
|
6
6
|
} from "../../utils/dataUpdatesUtils";
|
|
7
|
+
import moment from "moment-timezone";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* SummitSynchStrategy
|
|
@@ -16,13 +17,19 @@ class SummitSynchStrategy extends AbstractSynchStrategy {
|
|
|
16
17
|
|
|
17
18
|
const {entity_operator} = payload;
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
let entity = await fetchSummitById(this.summit.id, this.accessToken);
|
|
20
21
|
|
|
21
22
|
let eventsData = [...this.allEvents];
|
|
22
23
|
|
|
23
24
|
if (entity_operator === 'UPDATE') {
|
|
24
25
|
if (!entity) return Promise.reject('SummitSynchStrategy::process entity not found.');
|
|
25
26
|
|
|
27
|
+
// Update summit timestamp
|
|
28
|
+
entity = {
|
|
29
|
+
...entity,
|
|
30
|
+
timestamp: moment().unix(),
|
|
31
|
+
};
|
|
32
|
+
|
|
26
33
|
// update files on cache
|
|
27
34
|
console.log(`SummitSynchStrategy::process updating cache files`);
|
|
28
35
|
|
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
BUCKET_EVENTS_DATA_KEY,
|
|
5
5
|
BUCKET_EVENTS_IDX_DATA_KEY,
|
|
6
6
|
BUCKET_SPEAKERS_DATA_KEY,
|
|
7
|
-
BUCKET_SPEAKERS_IDX_DATA_KEY,
|
|
7
|
+
BUCKET_SPEAKERS_IDX_DATA_KEY, BUCKET_SUMMIT_DATA_KEY,
|
|
8
8
|
saveFile,
|
|
9
9
|
} from "../../utils/dataUpdatesUtils";
|
|
10
|
+
import moment from "moment-timezone";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* VenueRoomSynchStrategy
|
|
@@ -39,12 +40,21 @@ class VenueRoomSynchStrategy extends AbstractSynchStrategy{
|
|
|
39
40
|
eventsData[idx] = {...formerEntity, location: entity};
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
// update summit
|
|
44
|
+
// Update summit timestamp
|
|
45
|
+
this.summit = {
|
|
46
|
+
...this.summit,
|
|
47
|
+
timestamp: moment().unix(),
|
|
48
|
+
};
|
|
49
|
+
|
|
42
50
|
// update files on cache
|
|
43
51
|
console.log(`VenueRoomSynchStrategy::process updating cache files`);
|
|
44
52
|
|
|
45
53
|
try {
|
|
46
54
|
const localNowUtc = Date.now();
|
|
47
55
|
|
|
56
|
+
await saveFile(this.summit.id, BUCKET_SUMMIT_DATA_KEY, this.summit, localNowUtc);
|
|
57
|
+
|
|
48
58
|
await saveFile(this.summit.id, BUCKET_EVENTS_DATA_KEY, eventsData, localNowUtc);
|
|
49
59
|
|
|
50
60
|
await saveFile(this.summit.id, BUCKET_EVENTS_IDX_DATA_KEY, this.allIDXEvents, localNowUtc);
|