@openeventkit/event-site 2.0.122 → 2.0.123-beta.1

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/package.json CHANGED
@@ -1,15 +1,19 @@
1
1
  {
2
2
  "name": "@openeventkit/event-site",
3
3
  "description": "Event Site",
4
- "version": "2.0.122",
4
+ "version": "2.0.123-beta.1",
5
5
  "author": "Tipit LLC",
6
6
  "dependencies": {
7
+ "@emotion/cache": "^11.13.1",
7
8
  "@fortawesome/fontawesome-svg-core": "^6.5.2",
8
9
  "@fortawesome/free-brands-svg-icons": "^6.5.2",
9
10
  "@fortawesome/react-fontawesome": "^0.2.2",
10
11
  "@mdx-js/react": "^3.0.1",
11
12
  "@mdx-js/runtime": "^1.6.22",
12
13
  "@mui/base": "^5.0.0-beta.40",
14
+ "@mui/icons-material": "^5.16.7",
15
+ "@mui/material": "^5.16.7",
16
+ "@mui/system": "^5.16.7",
13
17
  "@mux/mux-player-react": "^1.14.1",
14
18
  "@ncwidgets/file-relation": "^0.8.0",
15
19
  "@ncwidgets/id": "^0.8.1",
@@ -28,7 +32,7 @@
28
32
  "@vimeo/player": "^2.16.3",
29
33
  "ably": "^1.2.34",
30
34
  "assert": "^2.1.0",
31
- "attendee-to-attendee-widget": "3.1.1-beta.29",
35
+ "attendee-to-attendee-widget": "3.1.1-beta.32",
32
36
  "autoprefixer": "10.4.14",
33
37
  "awesome-bootstrap-checkbox": "^1.0.1",
34
38
  "axios": "^0.19.2",
@@ -29,23 +29,14 @@ export const setEventLastUpdate = (lastUpdate) => (dispatch) => {
29
29
 
30
30
  /**
31
31
  * @param eventId
32
- * @param checkLocal
33
32
  * @returns {(function(*, *): Promise<*>)|*}
34
33
  */
35
34
  export const getEventById = (
36
35
  eventId
37
- ) => async (dispatch, getState) => {
36
+ ) => async (dispatch) => {
38
37
 
39
38
  dispatch(startLoading());
40
- // if we have it on the reducer , provide that first
41
- let {allSchedulesState: {allEvents}} = getState();
42
- const event = allEvents.find(ev => ev.id === parseInt(eventId));
43
-
44
- if (event) {
45
- dispatch(createAction(GET_EVENT_DATA)({event}));
46
- }
47
39
 
48
- // then refresh from api
49
40
  let accessToken;
50
41
  try {
51
42
  accessToken = await getAccessToken();
@@ -0,0 +1,249 @@
1
+ import React, { useState, useRef, useEffect } from "react";
2
+ import AvatarEditor from "react-avatar-editor";
3
+ import { Box, Typography, Slider, Modal } from "@mui/material";
4
+ import { Button, IconButton } from "../ui";
5
+ import { styled } from "@mui/system";
6
+ import {
7
+ //Close as CloseIcon,
8
+ CameraAlt as CameraAltIcon,
9
+ RotateLeft as RotateLeftIcon,
10
+ RotateRight as RotateRightIcon,
11
+ } from "@mui/icons-material";
12
+
13
+ const CustomSlider = styled(Slider)(({ theme }) => ({
14
+ color: "var(--color-primary)"
15
+ }));
16
+
17
+ const AvatarEditorModal = ({
18
+ userProfile,
19
+ open,
20
+ changePicture,
21
+ handleClose
22
+ }) => {
23
+ const editorRef = useRef(null);
24
+ const fileInputRef = useRef(null);
25
+
26
+ const [image, setImage] = useState(userProfile.picture || null);
27
+ const [position, setPosition] = useState({ x: 0.5, y: 0.5 });
28
+ const [scale, setScale] = useState(1);
29
+ const [rotate, setRotate] = useState(0);
30
+ const [newImage, setNewImage] = useState(false);
31
+
32
+ useEffect(() => {
33
+ setImage(userProfile.picture);
34
+ }, [userProfile.picture]);
35
+
36
+ const handleNewImage = (e) => {
37
+ setImage(e.target.files[0]);
38
+ setNewImage(true);
39
+ };
40
+
41
+ const handleScale = (e, newValue) => {
42
+ setScale(newValue);
43
+ setNewImage(true);
44
+ };
45
+
46
+ const handlePositionChange = (newPosition) => {
47
+ setPosition(newPosition);
48
+ setNewImage(true);
49
+ };
50
+
51
+ const rotateLeft = () => {
52
+ setRotate((prev) => prev - 22.5);
53
+ setNewImage(true);
54
+ };
55
+
56
+ const rotateRight = () => {
57
+ setRotate((prev) => prev + 22.5);
58
+ setNewImage(true);
59
+ };
60
+
61
+ const handleSave = () => {
62
+ if (editorRef.current && newImage) {
63
+ const canvas = editorRef.current.getImage().toDataURL();
64
+ fetch(canvas)
65
+ .then((res) => res.blob())
66
+ .then((blob) => {
67
+ const file = new File([blob], "profile-pic.png", { type: blob.type });
68
+ changePicture(file);
69
+ });
70
+ }
71
+ };
72
+
73
+ return (
74
+ <Modal
75
+ open={open}
76
+ onClose={handleClose}
77
+ aria-labelledby="avatar-modal-title"
78
+ sx={{
79
+ display: "flex",
80
+ alignItems: "center",
81
+ justifyContent: "center"
82
+ }}
83
+ >
84
+ <Box
85
+ sx={{
86
+ width: { xs: "100%", sm: 400 },
87
+ bgcolor: "var(--color_background_light)",
88
+ color: "var(--color_text_dark)",
89
+ display: "flex",
90
+ flexDirection: "column",
91
+ position: "relative",
92
+ border: "1px solid #dbdbdb"
93
+ }}
94
+ >
95
+ <Box
96
+ sx={{
97
+ padding: "7.5px 15px",
98
+ display: "flex",
99
+ alignItems: "center",
100
+ borderBottom: "1px solid #dbdbdb"
101
+ }}
102
+ >
103
+ <Typography
104
+ id="avatar-modal-title"
105
+ component="h2"
106
+ sx={{
107
+ fontWeight: "bold",
108
+ color: "var(--color_text_dark)",
109
+ fontFamily: "var(--font_family)",
110
+ fontSize: "18px"
111
+ }}
112
+ >
113
+ Edit Profile Picture
114
+ </Typography>
115
+ <IconButton
116
+ onClick={handleClose}
117
+ sx={{
118
+ ml: "auto",
119
+ fontSize: "16px",
120
+ padding: 0
121
+ }}
122
+ >
123
+ <i className="fa fa-times" />
124
+ </IconButton>
125
+ </Box>
126
+ <Box
127
+ sx={{
128
+ padding: "20px",
129
+ textAlign: "center",
130
+ position: "relative"
131
+ }}
132
+ >
133
+ <AvatarEditor
134
+ ref={editorRef}
135
+ image={image}
136
+ width={200}
137
+ height={200}
138
+ border={50}
139
+ color={[0, 0, 0, 0.8]}
140
+ position={position}
141
+ onPositionChange={handlePositionChange}
142
+ scale={scale}
143
+ rotate={rotate}
144
+ />
145
+ <IconButton
146
+ onClick={() => fileInputRef.current.click()}
147
+ sx={{
148
+ position: "absolute",
149
+ top: "79.5%",
150
+ right: "44%",
151
+ color: "#fff"
152
+ }}
153
+ >
154
+ <CameraAltIcon fontSize="inherit" />
155
+ </IconButton>
156
+ <input
157
+ ref={fileInputRef}
158
+ type="file"
159
+ accept=".jpg,.jpeg,.png"
160
+ style={{ display: "none" }}
161
+ onChange={handleNewImage}
162
+ />
163
+ </Box>
164
+ <Box
165
+ sx={{
166
+ px: "20px",
167
+ display: "flex",
168
+ flexDirection: "column",
169
+ gap: "20px"
170
+ }}
171
+ >
172
+ <Box
173
+ sx={{
174
+ display: "flex",
175
+ alignItems: "center",
176
+ gap: "20px",
177
+ justifyContent: "center"
178
+ }}
179
+ >
180
+ <Typography
181
+ sx={{
182
+ fontSize: "1.5rem",
183
+ fontFamily: "var(--font_family)"
184
+ }
185
+ }>
186
+ Zoom
187
+ </Typography>
188
+ <CustomSlider
189
+ value={scale}
190
+ min={1}
191
+ max={2}
192
+ step={0.01}
193
+ onChange={handleScale}
194
+ sx={{ flex: 1, mx: 2 }}
195
+ />
196
+ </Box>
197
+ <Box
198
+ sx={{
199
+ display: "flex",
200
+ alignItems: "center"
201
+ }}
202
+ >
203
+ <Typography
204
+ sx={{
205
+ fontSize: "1.5rem",
206
+ fontFamily: "var(--font_family)"
207
+ }}
208
+ >
209
+ Rotate
210
+ </Typography>
211
+ <Box
212
+ sx={{
213
+ display: "flex",
214
+ flex: 1,
215
+ justifyContent: "center",
216
+ gap: "20px"
217
+ }}
218
+ >
219
+ <IconButton onClick={rotateLeft}>
220
+ <RotateLeftIcon fontSize="inherit" />
221
+ </IconButton>
222
+ <IconButton onClick={rotateRight}>
223
+ <RotateRightIcon fontSize="inherit" />
224
+ </IconButton>
225
+ </Box>
226
+ </Box>
227
+ </Box>
228
+ <Box
229
+ sx={{
230
+ backgroundColor: "var(--color_background_light)",
231
+ display: "flex",
232
+ justifyContent: "space-between",
233
+ padding: "20px",
234
+ gap: "20px"
235
+ }}
236
+ >
237
+ <Button onClick={handleClose}>
238
+ Discard
239
+ </Button>
240
+ <Button onClick={handleSave} disabled={!newImage}>
241
+ Update
242
+ </Button>
243
+ </Box>
244
+ </Box>
245
+ </Modal>
246
+ );
247
+ };
248
+
249
+ export default AvatarEditorModal;
@@ -1,13 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { useMemo } from "react";
3
3
  import { connect } from "react-redux";
4
+ import { navigate } from "gatsby";
4
5
  import NavbarTemplate from "./template";
5
6
 
6
- import {
7
- updateProfile,
8
- updateProfilePicture
9
- } from "../../actions/user-actions";
10
-
11
7
  import { userHasAccessLevel, VirtualAccessLevel } from "@utils/authorizedGroups";
12
8
  import { getDefaultLocation } from "@utils/loginUtils";
13
9
 
@@ -21,11 +17,8 @@ const Navbar = ({
21
17
  summitPhase,
22
18
  summit,
23
19
  isLoggedUser,
24
- idpLoading,
25
20
  idpProfile,
26
21
  userProfile,
27
- updateProfile,
28
- updateProfilePicture,
29
22
  eventRedirect
30
23
  }) => {
31
24
 
@@ -105,20 +98,21 @@ const Navbar = ({
105
98
  passPageRestriction;
106
99
  };
107
100
 
101
+ const handleLogoClick = () => navigate(isLoggedUser ? defaultPath : "/");
102
+
103
+ const handleProfileIconClick = () => navigate("/a/profile");
104
+
108
105
  return (
109
106
  <NavbarTemplate
110
- data={navbarContent.items.filter(showItem)}
107
+ items={navbarContent.items.filter(showItem)}
111
108
  summit={summit}
109
+ logo={summit?.logo}
110
+ onLogoClick={handleLogoClick}
112
111
  isLoggedUser={isLoggedUser}
113
- idpLoading={idpLoading}
114
112
  idpProfile={idpProfile}
115
- hasVirtualBadge={hasVirtualBadge}
116
- updateProfile={updateProfile}
117
- updateProfilePicture={updateProfilePicture}
118
- defaultPath={defaultPath}
119
- logo={summit?.logo}
113
+ onProfileIconClick={handleProfileIconClick}
120
114
  />
121
- )
115
+ );
122
116
  };
123
117
 
124
118
 
@@ -129,17 +123,13 @@ const mapStateToProps = ({
129
123
  loggedUserState,
130
124
  userState
131
125
  }) => ({
132
- summitPhase: clockState.summit_phase,
133
126
  summit: summitState.summit,
127
+ summitPhase: clockState.summit_phase,
134
128
  isLoggedUser: loggedUserState.isLoggedUser,
135
129
  idpProfile: userState.idpProfile,
136
- idpLoading: userState.loadingIDP,
137
130
  userProfile: userState.userProfile,
138
131
  // TODO: move to site settings i/o marketing page settings
139
132
  eventRedirect: settingState.marketingPageSettings.eventRedirect
140
133
  });
141
134
 
142
- export default connect(mapStateToProps, {
143
- updateProfile,
144
- updateProfilePicture
145
- })(Navbar);
135
+ export default connect(mapStateToProps, {})(Navbar);
@@ -1,108 +1,88 @@
1
1
  import React, { useState } from "react";
2
-
3
2
  import Link from "../Link";
4
- import ProfilePopupComponent from "../ProfilePopupComponent";
5
3
  import LogoutButton from "../LogoutButton";
6
4
 
7
5
  import styles from "../../styles/navbar.module.scss";
8
6
 
9
7
  const NavbarTemplate = ({
10
- data,
8
+ items,
9
+ logo,
11
10
  summit,
12
11
  isLoggedUser,
13
- idpLoading,
14
12
  idpProfile,
15
- updateProfile,
16
- updateProfilePicture,
17
- defaultPath,
18
- logo
13
+ onLogoClick,
14
+ onProfileIconClick
19
15
  }) => {
20
16
  const [active, setActive] = useState(false);
21
- const [showProfile, setShowProfile] = useState(false);
22
17
 
23
- const toggleHamburger = () => {
24
- // toggle the active boolean in the state
25
- setActive(!active);
26
- };
18
+ const toggleHamburger = () => setActive(!active);
19
+
20
+ const navBarActiveClass = active ? styles.isActive : "";
21
+
22
+ const renderLogo = () => {
23
+ if (!logo && !summit?.name) return null;
24
+
25
+ const logoContent = logo ? <img src={logo} alt={summit?.name || "Logo"} /> : <h4>{summit.name}</h4>;
27
26
 
28
- const handleTogglePopup = () => {
29
- if (showProfile) {
30
- document.body.classList.remove("is-clipped");
31
- } else {
32
- document.body.classList.add("is-clipped");
33
- }
34
- setShowProfile(!showProfile);
27
+ return onLogoClick ? (
28
+ <button className={`link ${styles.navbarItem}`} onClick={onLogoClick}>
29
+ {logoContent}
30
+ </button>
31
+ ) : (
32
+ <div className={styles.navbarItem}>{logoContent}</div>
33
+ );
35
34
  };
36
35
 
37
- const navBarActiveClass = active ? styles.isActive : "";
36
+ const renderProfileIcon = () => {
37
+ if (!isLoggedUser || !idpProfile?.picture) return null;
38
38
 
39
- return (
40
- <React.Fragment>
41
- <nav
42
- className={`${styles.navbar}`}
43
- role="navigation"
44
- aria-label="main navigation"
45
- >
46
- <div className={styles.navbarBrand}>
47
- <Link
48
- to={isLoggedUser ? defaultPath : "/"}
49
- className={styles.navbarItem}
50
- >
51
- {logo && <img src={logo} alt={summit.name} />}
52
- </Link>
39
+ const profilePic = (
40
+ <img alt={idpProfile?.name} className={styles.profilePic} src={idpProfile.picture} />
41
+ );
53
42
 
54
- <button
55
- className={`link ${styles.navbarBurger} ${styles.burger} ${navBarActiveClass}`}
56
- aria-label="menu"
57
- aria-expanded="false"
58
- data-target="navbar"
59
- onClick={() => toggleHamburger()}
60
- >
61
- <span aria-hidden="true" />
62
- <span aria-hidden="true" />
63
- <span aria-hidden="true" />
64
- </button>
65
- </div>
43
+ return onProfileIconClick ? (
44
+ <div className={styles.navbarItem}>
45
+ <button className="link" onClick={onProfileIconClick}>
46
+ {profilePic}
47
+ </button>
48
+ </div>
49
+ ) : (
50
+ <div className={styles.navbarItem}>{profilePic}</div>
51
+ );
52
+ };
66
53
 
67
- <div
68
- id="navbar"
69
- className={`${styles.navbarMenu} ${navBarActiveClass}`}
54
+ return (
55
+ <nav className={styles.navbar} role="navigation" aria-label="main navigation">
56
+ <div className={styles.navbarBrand}>
57
+ {renderLogo()}
58
+ <button
59
+ className={`link ${styles.navbarBurger} ${styles.burger} ${navBarActiveClass}`}
60
+ aria-label="menu"
61
+ aria-expanded="false"
62
+ data-target="navbar"
63
+ onClick={toggleHamburger}
70
64
  >
71
- <div className={styles.navbarStart} />
72
- <div className={styles.navbarEnd}>
73
- {data.map((item, index) => (
74
- <div className={styles.navbarItem} key={index}>
75
- <Link to={item.link} className={styles.link}>
76
- <span>{item.title}</span>
77
- </Link>
78
- </div>
79
- ))}
80
- {isLoggedUser && (
81
- <div className={styles.navbarItem}>
82
- <button className="link" onClick={() => handleTogglePopup()}>
83
- <img
84
- alt="profile pic"
85
- className={styles.profilePic}
86
- src={idpProfile?.picture}
87
- />
88
- </button>
89
- {showProfile && (
90
- <ProfilePopupComponent
91
- userProfile={idpProfile}
92
- showProfile={showProfile}
93
- idpLoading={idpLoading}
94
- changePicture={updateProfilePicture}
95
- changeProfile={updateProfile}
96
- closePopup={() => handleTogglePopup()}
97
- />
98
- )}
99
- </div>
100
- )}
101
- <LogoutButton styles={styles} isLoggedUser={isLoggedUser} />
102
- </div>
65
+ <span aria-hidden="true" />
66
+ <span aria-hidden="true" />
67
+ <span aria-hidden="true" />
68
+ </button>
69
+ </div>
70
+
71
+ <div id="navbar" className={`${styles.navbarMenu} ${navBarActiveClass}`}>
72
+ <div className={styles.navbarStart} />
73
+ <div className={styles.navbarEnd}>
74
+ {items?.map((item, index) => (
75
+ <div className={styles.navbarItem} key={index}>
76
+ <Link to={item.link} className={styles.link}>
77
+ <span>{item.title}</span>
78
+ </Link>
79
+ </div>
80
+ ))}
81
+ {renderProfileIcon()}
82
+ <LogoutButton styles={styles} isLoggedUser={isLoggedUser} />
103
83
  </div>
104
- </nav>
105
- </React.Fragment>
84
+ </div>
85
+ </nav>
106
86
  );
107
87
  };
108
88
 
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {getSynchWordsVideoFrameDdFromSrc} from "../utils/videoUtils";
4
+
5
+
6
+ class SynchWordsPlayer extends React.Component {
7
+
8
+ constructor(props) {
9
+ super(props);
10
+ }
11
+
12
+ componentDidMount() {
13
+ }
14
+
15
+ componentWillUnmount() {
16
+ }
17
+
18
+ render() {
19
+ const { video, className, autoplay } = this.props;
20
+ const id = getSynchWordsVideoFrameDdFromSrc(video);
21
+ console.log(`SynchWordsPlayer::render with id ${id}`);
22
+ let allow = "encrypted-media";
23
+ if(autoplay) allow = allow +';autoplay'
24
+ return (
25
+ <div className={className}>
26
+ <iframe id={id}
27
+ src={video}
28
+ frameBorder="0"
29
+ scrolling="no"
30
+ allow={allow}
31
+ allowFullScreen webkitallowfullscreen mozallowfullscreen oallowfullscreen msallowfullscreen
32
+ >
33
+ </iframe>
34
+ </div>
35
+ );
36
+ }
37
+ }
38
+
39
+ SynchWordsPlayer.propTypes = {
40
+ /**
41
+ * a video URL.
42
+ */
43
+ video: PropTypes.oneOfType([
44
+ PropTypes.number,
45
+ PropTypes.string,
46
+ ]),
47
+ /**
48
+ * CSS className for the player element.
49
+ */
50
+ className: PropTypes.string,
51
+ /**
52
+ * Automatically start playback of the video. Note that this won’t work on
53
+ * some devices.
54
+ */
55
+ autoplay: PropTypes.bool,
56
+ };
57
+
58
+ export default SynchWordsPlayer;
@@ -4,7 +4,8 @@ import VideoJSPlayer from './VideoJSPlayer';
4
4
  import VimeoPlayer from "./VimeoPlayer";
5
5
  import VideoMUXPlayer from './VideoMUXPlayer';
6
6
  import styles from '../styles/video.module.scss';
7
- import { isMuxVideo, isVimeoVideo, isYouTubeVideo } from '../utils/videoUtils';
7
+ import { isMuxVideo, isVimeoVideo, isYouTubeVideo, isSynchWordsVideo } from '../utils/videoUtils';
8
+ import SynchWordsPlayer from "./SyncWordsPlayer";
8
9
 
9
10
  /**
10
11
  * @param url
@@ -48,6 +49,14 @@ const VideoComponent = ({ url, title, namespace, isLive, firstHalf, autoPlay, st
48
49
  />
49
50
  );
50
51
  };
52
+ // synch words player
53
+ if(isSynchWordsVideo(url)){
54
+ return (<SynchWordsPlayer
55
+ video={url}
56
+ autoplay={autoPlay}
57
+ className={styles.synchWordsPlayer}
58
+ />);
59
+ }
51
60
 
52
61
  const defaultVideoJsOptions = isYouTubeVideo(url) ? {
53
62
  techOrder: ["youtube"],
@@ -99,7 +108,7 @@ VideoComponent.propTypes = {
99
108
  isLive: PropTypes.bool,
100
109
  firstHalf: PropTypes.bool,
101
110
  autoPlay: PropTypes.bool,
102
- start: PropTypes.number,
111
+ start: PropTypes.number,
103
112
  tokens: PropTypes.object,
104
113
  onError: PropTypes.func,
105
114
  };
@@ -108,7 +117,7 @@ VideoComponent.defaultProps = {
108
117
  title: '',
109
118
  namespace: '',
110
119
  firstHalf: true,
111
- autoPlay: false,
120
+ autoPlay: false,
112
121
  tokens: null,
113
122
  };
114
123
 
@@ -319,6 +319,11 @@ export const TicketPopupEditDetailsForm = ({
319
319
  onBlur={formik.handleBlur}
320
320
  onChange={!!initialValues[TicketKeys.company].name ? noop : formik.handleChange}
321
321
  disabled={!!initialValues[TicketKeys.company].name}
322
+ menuPortalTarget={document.body}
323
+ menuPosition="fixed"
324
+ styles={{
325
+ menuPortal: (base) => ({ ...base, zIndex: 9999 }),
326
+ }}
322
327
  tabSelectsValue={false}
323
328
  />
324
329
  {(formik.touched[TicketKeys.company] || triedSubmitting) && formik.errors[TicketKeys.company] &&
@@ -0,0 +1,41 @@
1
+ import * as React from "react";
2
+ import { Button as BaseButton } from "@mui/base";
3
+ import { styled } from "@mui/system";
4
+
5
+ const StyledButton = styled(BaseButton)({
6
+ width: "100%",
7
+ height: "5.5rem",
8
+ color: "var(--color_input_text_color)",
9
+ backgroundColor: "var(--color_input_background_color)",
10
+ borderColor: "var(--color_input_border_color)",
11
+ borderStyle: "solid",
12
+ borderWidth: 1,
13
+ borderRadius: 4,
14
+ fontSize: "1.5rem",
15
+ fontFamily: "var(--font_family)",
16
+ justifyContent: "center",
17
+ textAlign: "center",
18
+ whiteSpace: "nowrap",
19
+ padding: "calc(0.5em - 1px) 1em",
20
+ display: "inline-flex",
21
+ alignItems: "center",
22
+ lineHeight: 1.5,
23
+ ":disabled": {
24
+ opacity: 0.65
25
+ }
26
+ });
27
+
28
+ const Button = ({
29
+ children,
30
+ ...rest
31
+ }) => {
32
+ return (
33
+ <StyledButton
34
+ {...rest}
35
+ >
36
+ {children}
37
+ </StyledButton>
38
+ );
39
+ };
40
+
41
+ export default Button;
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ import { IconButton as BaseIconButton } from "@mui/material";
3
+ import { styled } from "@mui/system";
4
+
5
+ const IconButtonStyled = styled(BaseIconButton)(({ theme }) => ({
6
+ color: "var(--color_background_dark)",
7
+ fontSize: "2.5rem",
8
+ borderRadius: 4,
9
+ "&:hover": {
10
+ backgroundColor: "transparent"
11
+ },
12
+ "& .MuiTouchRipple-root span": {
13
+ backgroundColor: "transparent",
14
+ borderRadius: 4,
15
+ animation: "none"
16
+ }
17
+ }))
18
+
19
+ const IconButton = ({
20
+ children,
21
+ ...rest
22
+ }) => {
23
+ return (
24
+ <IconButtonStyled
25
+ {...rest}
26
+ >
27
+ {children}
28
+ </IconButtonStyled>
29
+ );
30
+ };
31
+
32
+ export default IconButton;
@@ -0,0 +1,7 @@
1
+ import Button from "./Button";
2
+ import IconButton from "./IconButton";
3
+
4
+ export {
5
+ Button,
6
+ IconButton
7
+ };
@@ -113,6 +113,9 @@
113
113
  margin: -3px 8px 0px;
114
114
  vertical-align: middle;
115
115
  }
116
+ &:hover {
117
+ color: var(--color_text_dark);
118
+ }
116
119
  }
117
120
 
118
121
  .buttons {
@@ -16,4 +16,24 @@
16
16
  width: 100%;
17
17
  height: 100%;
18
18
  }
19
- }
19
+ }
20
+
21
+ .synchWordsPlayer{
22
+ /* 16:9 */
23
+ --video--width: 960;
24
+ --video--height: 540;
25
+
26
+ position: relative;
27
+ padding-bottom: calc(var(--video--height) / var(--video--width) * 100%); /* 56.25% */
28
+ overflow: hidden;
29
+ max-width: 100%;
30
+ background: black;
31
+
32
+ iframe, object, embed{
33
+ position: absolute;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100%;
37
+ height: 100%;
38
+ }
39
+ }
@@ -14,7 +14,7 @@ import Layout from '../components/Layout'
14
14
  import withOrchestra from "../utils/widgetOrchestra";
15
15
 
16
16
  import LiteScheduleComponent from '../components/LiteScheduleComponent'
17
- import ProfilePopupComponent from '../components/ProfilePopupComponent'
17
+ import AvatarEditorModal from '../components/AvatarEditorModal'
18
18
  import ChangePasswordComponent from '../components/ChangePasswordComponent';
19
19
  import AccessTracker from "../components/AttendeeToAttendeeWidgetComponent";
20
20
 
@@ -440,42 +440,50 @@ export const FullProfilePageTemplate = ({ user, getIDPProfile, updateProfile, up
440
440
  </div>
441
441
  </div>
442
442
  </div>
443
+ <div className={`columns is-mobile`}>
444
+ <div className={`column is-full`}>
445
+ <span>
446
+ By electing to show your information you are indicating that other attendees at the
447
+ event(s) you are registered for will be able to see this information.
448
+ </span>
449
+ </div>
450
+ </div>
443
451
  <div className={`columns is-mobile`}>
444
452
  <div className={`column is-half`}>
445
453
  <label className={styles.checkbox}>
446
454
  <input type="checkbox" checked={showFullName} onChange={e => setShowFullName(e.target.checked)} />
447
- Show full name on public profile
455
+ Show full name (first name is always shown)
448
456
  </label>
449
457
  <br />
450
458
  <label className={styles.checkbox}>
451
459
  <input type="checkbox" checked={showEmail} onChange={e => setShowEmail(e.target.checked)} />
452
- Show email on public profile
460
+ Show email
453
461
  </label>
454
462
  <br />
455
463
  <label className={styles.checkbox}>
456
464
  <input type="checkbox" checked={showTelephone} onChange={e => setShowTelephone(e.target.checked)} />
457
- Show telephone number on public profile
465
+ Show telephone number
458
466
  </label>
459
467
  <br />
460
468
  <label className={styles.checkbox}>
461
469
  <input type="checkbox" checked={allowChatWithMe} onChange={e => setAllowChatWithMe(e.target.checked)} />
462
- Allow people to chat with me?
470
+ Allow people to chat with me
463
471
  </label>
464
472
  </div>
465
473
  <div className={`column is-half`}>
466
474
  <label className={styles.checkbox}>
467
475
  <input type="checkbox" checked={showPicture} onChange={e => setShowPicture(e.target.checked)} />
468
- Show picture on public profile
476
+ Show picture
469
477
  </label>
470
478
  <br />
471
479
  <label className={styles.checkbox}>
472
480
  <input type="checkbox" checked={showBio} onChange={e => setShowBio(e.target.checked)} />
473
- Show bio on public profile
481
+ Show bio
474
482
  </label>
475
483
  <br />
476
484
  <label className={styles.checkbox}>
477
485
  <input type="checkbox" checked={showSocialMedia} onChange={e => setShowSocialMedia(e.target.checked)} />
478
- Show social media info on public profile
486
+ Show social media info
479
487
  </label>
480
488
  </div>
481
489
  </div>
@@ -634,17 +642,15 @@ export const FullProfilePageTemplate = ({ user, getIDPProfile, updateProfile, up
634
642
  </div>
635
643
  </div>
636
644
  {showProfile &&
637
- <ProfilePopupComponent
638
- userProfile={user.idpProfile}
639
- showProfile={showProfile}
640
- idpLoading={user.loadingIDP}
641
- fromFullProfile={true}
642
- changePicture={(pic) => handlePictureUpdate(pic)}
643
- changeProfile={(profile) => handleProfileUpdate(profile)}
644
- closePopup={() => handleTogglePopup(!showProfile)}
645
- />
645
+ <AvatarEditorModal
646
+ userProfile={user.idpProfile}
647
+ open={showProfile}
648
+ idpLoading={user.loadingIDP}
649
+ changePicture={handlePictureUpdate}
650
+ handleClose={() => handleTogglePopup(false)}
651
+ />
646
652
  }
647
- <AccessTracker/>
653
+ <AccessTracker />
648
654
  </React.Fragment>
649
655
  )
650
656
  };
@@ -1,4 +1,5 @@
1
1
  const IS_MUX_VIDEO_REGEX = /https:\/\/stream.mux.com\/(.*).m3u8/;
2
+ const IS_SYNC_WORDS_VIDEO_REGEX = /https:\/\/player.syncwords.com\/iframe\/live\/(.*)\/(.*)/g;
2
3
 
3
4
  export const getMUXPlaybackId = (url) => {
4
5
  if(!url) return null;
@@ -25,3 +26,20 @@ export const isMuxVideo = (url) => {
25
26
  if(!url) return false;
26
27
  return url.match(IS_MUX_VIDEO_REGEX)
27
28
  }
29
+
30
+ export const isSynchWordsVideo = (url) => {
31
+ if(!url) return false;
32
+ return url.match(IS_SYNC_WORDS_VIDEO_REGEX);
33
+ }
34
+
35
+ /**
36
+ * @param src
37
+ * @returns {string|null}
38
+ */
39
+ export const getSynchWordsVideoFrameDdFromSrc = (src) => {
40
+ const m = [...src.matchAll(IS_SYNC_WORDS_VIDEO_REGEX)];
41
+ if(!m?.length) return null;
42
+ const parts = m[0];
43
+ if(parts.length < 3) return null;
44
+ return `${parts[1]}-live-${parts[2]}`;
45
+ }
@@ -1,369 +0,0 @@
1
- import React, { useState, useRef, useEffect } from 'react'
2
- import AvatarEditor from 'react-avatar-editor'
3
- import AjaxLoader from "openstack-uicore-foundation/lib/components/ajaxloader";
4
- import { create_UUID } from '../utils/uuidGenerator'
5
- import Link from "./Link";
6
-
7
- import styles from '../styles/profile.module.scss'
8
-
9
-
10
- const ProfilePopupComponent = ({ userProfile, idpLoading, closePopup, showProfile, changePicture, changeProfile, fromFullProfile }) => {
11
-
12
- const editorRef = useRef(null);
13
- const modalHeaderRef = useRef(null)
14
- const modalRef = useRef(null)
15
- const fileInput = useRef(null)
16
-
17
- const [firstName, setFirstName] = useState("");
18
- const [lastName, setLastName] = useState("");
19
- const [company, setCompany] = useState("");
20
- const [bio, setBio] = useState('');
21
- const [jobTitle, setJobTitle] = useState('');
22
- const [github, setGithub] = useState('');
23
- const [irc, setIRC] = useState('');
24
- const [linkedin, setLinkedin] = useState('');
25
- const [twitter, setTwitter] = useState('');
26
-
27
- const [image, setImage] = useState(null);
28
- const [newImage, setNewImage] = useState(false);
29
- const [position, setPosition] = useState({ x: 0.5, y: 0.5 });
30
- const [scale, setScale] = useState(1);
31
- const [rotate, setRotate] = useState(0);
32
- const width = 200;
33
- const height = 200;
34
-
35
- useEffect(() => {
36
- setFirstName(userProfile.given_name || '');
37
- setLastName(userProfile.family_name || '');
38
- setCompany(userProfile.company || '');
39
- setBio(userProfile.bio || '');
40
- setJobTitle(userProfile.job_title || '');
41
- setImage(userProfile.picture || '');
42
- setGithub(userProfile.github_user || '');
43
- setIRC(userProfile.irc || '');
44
- setLinkedin(userProfile.linked_in_profile || '');
45
- setTwitter(userProfile.twitter_name || '');
46
-
47
- return () => {
48
- setFirstName('');
49
- setLastName('');
50
- setCompany('');
51
- setBio('');
52
- setJobTitle('');
53
- setGithub('');
54
- setIRC('');
55
- setLinkedin('');
56
- setTwitter('');
57
- };
58
- }, [
59
- userProfile.given_name,
60
- userProfile.family_name,
61
- userProfile.company,
62
- userProfile.picture,
63
- userProfile.bio,
64
- userProfile.job_title,
65
- userProfile.github,
66
- userProfile.irc,
67
- userProfile.linkedin,
68
- userProfile.twitter_name
69
- ]);
70
-
71
- useEffect(() => {
72
- if (modalHeaderRef) {
73
- window.setTimeout(function () {
74
- modalHeaderRef.current.focus();
75
- }, 0);
76
- }
77
- }, [modalHeaderRef])
78
-
79
- useEffect(() => {
80
- window.addEventListener('keydown', handleUserKeyPress);
81
-
82
- return () => {
83
- window.removeEventListener('keydown', handleUserKeyPress);
84
- };
85
- }, []);
86
-
87
- const handleUserKeyPress = (e) => {
88
- const focusable = modalRef.current.querySelectorAll('button, input, a, textarea, select, [tabindex]:not([tabindex="-1"])');
89
- const firstFocusable = focusable[0];
90
- const lastFocusable = focusable[focusable.length - 1];
91
- const KEYCODE_TAB = 9;
92
- const KEYCODE_ESC = 27;
93
- const isTabPressed = (e.key === 'Tab' || e.keyCode === KEYCODE_TAB);
94
- const isEscapePressed = (e.key === 'Escape' || e.keyCode === KEYCODE_ESC);
95
-
96
- if (isEscapePressed) {
97
- closePopup();
98
- }
99
-
100
- if (!isTabPressed) {
101
- return;
102
- }
103
-
104
- if ( e.shiftKey ) /* shift + tab */ {
105
- if (document.activeElement === firstFocusable) {
106
- lastFocusable.focus();
107
- e.preventDefault();
108
- }
109
- } else /* tab */ {
110
- if (document.activeElement === lastFocusable) {
111
- firstFocusable.focus();
112
- e.preventDefault();
113
- }
114
- }
115
- }
116
-
117
-
118
- const handleNewImage = (e) => {
119
- setImage(e.target.files[0]);
120
- setNewImage(true);
121
- };
122
-
123
- const urltoFile = (url, filename, mimeType) => {
124
- mimeType = mimeType || (url.match(/^data:([^;]+);/) || '')[1];
125
- filename = filename || create_UUID();
126
- return (fetch(url)
127
- .then(function (res) { return res.arrayBuffer(); })
128
- .then(function (buf) { return new File([buf], filename, { type: mimeType }); })
129
- );
130
- };
131
-
132
- const handleScale = (e) => {
133
- const scale = parseFloat(e.target.value);
134
- setScale(scale);
135
- setNewImage(true);
136
- };
137
-
138
- const handlePositionChange = (position) => {
139
- setPosition(position);
140
- setNewImage(true);
141
- };
142
-
143
- const rotateLeft = (e) => {
144
- e.preventDefault();
145
- setRotate(rotate - 90);
146
- setNewImage(true);
147
- };
148
-
149
- const rotateRight = (e) => {
150
- e.preventDefault();
151
- setRotate(rotate + 90);
152
- setNewImage(true);
153
- };
154
-
155
- const onClickSave = () => {
156
- if (editorRef.current && newImage) {
157
- const canvas = editorRef.current?.getImage()?.toDataURL();
158
- if(canvas) {
159
- urltoFile(canvas, image.name)
160
- .then(file => changePicture(file));
161
- }
162
- }
163
- if (userProfile.given_name !== firstName ||
164
- userProfile.family_name !== lastName ||
165
- userProfile.company !== company ||
166
- userProfile.bio !== bio ||
167
- userProfile.job_title !== jobTitle ||
168
- userProfile.github_user !== github ||
169
- userProfile.irc !== irc ||
170
- userProfile.linked_in_profile !== linkedin ||
171
- userProfile.twitter_name != twitter) {
172
- const newProfile = {
173
- first_name: firstName,
174
- last_name: lastName,
175
- company: company,
176
- bio: bio,
177
- job_title: jobTitle,
178
- github_user: github,
179
- irc: irc,
180
- linked_in_profile: linkedin,
181
- twitter_name: twitter,
182
- };
183
- changeProfile(newProfile);
184
- }
185
- };
186
-
187
- return (
188
- <div className={`${styles.modal} ${showProfile ? styles.isActive : ''}`} ref={modalRef}>
189
- <div className={`${styles.modalCard} ${styles.profilePopup}`}>
190
- <AjaxLoader relative={true} color={'#ffffff'} show={idpLoading} size={120} />
191
- <header className={`${styles.modalCardHead}`}>
192
- <h2 className={`${styles.modalCardTitle}`} tabIndex='-1' ref={modalHeaderRef}>Edit profile</h2>
193
- <button className="link" onClick={() => closePopup()}>
194
- <i className={`${styles.closeIcon} fa fa-times icon`} />
195
- </button>
196
- </header>
197
- <section className={`${styles.modalCardBody}`}>
198
- <div className={styles.modalCardPicture}>
199
- <div className={styles.title}>Profile picture</div>
200
- <div className={styles.picture}>
201
- <AvatarEditor
202
- ref={editorRef}
203
- image={image}
204
- width={width}
205
- height={height}
206
- border={50}
207
- color={[0, 0, 0, 0.8]} // RGBA
208
- position={position}
209
- onPositionChange={handlePositionChange}
210
- scale={scale}
211
- borderRadius={5}
212
- rotate={parseFloat(rotate)}
213
- />
214
- <div className={styles.imageUpload} tabIndex="0" onKeyPress={() => {fileInput.current.click()}}>
215
- <label htmlFor="file-input" >
216
- <i className={`${styles.pictureIcon} fa fa-2x fa-camera icon is-large`} />
217
- </label>
218
- <input ref={fileInput} name="newImage" id="file-input" type="file" accept=".jpg,.jpeg,.png" onChange={handleNewImage} />
219
- </div>
220
- <div>
221
- <div className={`columns ${styles.inputRow}`}>
222
- <span id="zoomLabel" className='column is-one-quarter'>Zoom:</span>
223
- <div className='column is-two-thirds'>
224
- <input
225
- name="scale"
226
- type="range"
227
- aria-labelledby='zoomLabel'
228
- max="2"
229
- onChange={(e) => handleScale(e)}
230
- step="0.01"
231
- defaultValue="1"
232
- />
233
- </div>
234
- </div>
235
- <div className={`columns ${styles.inputRow}`}>
236
- <div className='column is-one-quarter'>Rotate:</div>
237
- <div className='column is-two-thirds'>
238
- <button className={`button is-large ${styles.button}`} onClick={rotateLeft}>
239
- <i className={`fa fa-undo icon is-large`} />Left
240
- </button>
241
- <button className={`button is-large ${styles.button}`} onClick={rotateRight}>
242
- <i className={`fa fa-undo icon is-large`} />Right
243
- </button>
244
- </div>
245
- </div>
246
- </div>
247
-
248
- </div>
249
- </div>
250
- {!fromFullProfile &&
251
- <div className={styles.modalCardForm}>
252
- <div className={styles.title}>Profile Info</div>
253
- <div className={styles.form}>
254
- <div className={`columns is-mobile ${styles.inputRow}`}>
255
- <div className='column is-one-quarter'>First Name</div>
256
- <div className='column is-two-thirds'>
257
- <input
258
- className={`${styles.input} ${styles.isMedium}`}
259
- type="text"
260
- placeholder="First Name"
261
- onChange={e => setFirstName(e.target.value)}
262
- value={firstName} />
263
- </div>
264
- </div>
265
- <div className={`columns is-mobile ${styles.inputRow}`}>
266
- <div className='column is-one-quarter'>Last Name</div>
267
- <div className='column is-two-thirds'>
268
- <input
269
- className={`${styles.input} ${styles.isMedium}`}
270
- type="text"
271
- placeholder="Last Name"
272
- onChange={e => setLastName(e.target.value)}
273
- value={lastName} />
274
- </div>
275
- </div>
276
- <div className={`columns is-mobile ${styles.inputRow}`}>
277
- <div className='column is-one-quarter'>Company</div>
278
- <div className='column is-two-thirds'>
279
- <input
280
- className={`${styles.input} ${styles.isMedium}`}
281
- type="text"
282
- placeholder="Company"
283
- onChange={e => setCompany(e.target.value)}
284
- value={company}
285
- />
286
- </div>
287
- </div>
288
- <div className={`columns is-mobile ${styles.inputRow}`}>
289
- <div className='column is-one-quarter'>Bio</div>
290
- <div className='column is-two-thirds'>
291
- <textarea
292
- className={`textarea ${styles.textarea}`}
293
- placeholder=''
294
- rows="6"
295
- onChange={e => setBio(e.target.value)}
296
- value={bio}
297
- >
298
- </textarea>
299
- </div>
300
- </div>
301
- <div className={`columns is-mobile ${styles.inputRow}`}>
302
- <div className='column is-one-quarter'>Job Title</div>
303
- <div className='column is-two-thirds'>
304
- <input
305
- className={`${styles.input} ${styles.isMedium}`}
306
- type="text"
307
- placeholder="Job Title"
308
- onChange={e => setJobTitle(e.target.value)}
309
- value={jobTitle} />
310
- </div>
311
- </div>
312
- <div className={`columns is-mobile ${styles.inputRow}`}>
313
- <div className='column is-one-quarter'>Github</div>
314
- <div className='column is-two-thirds'>
315
- <input
316
- className={`${styles.input} ${styles.isMedium}`}
317
- type="text"
318
- placeholder="Github"
319
- onChange={e => setGithub(e.target.value)}
320
- value={github} />
321
- </div>
322
- </div>
323
- <div className={`columns is-mobile ${styles.inputRow}`}>
324
- <div className='column is-one-quarter'>IRC</div>
325
- <div className='column is-two-thirds'>
326
- <input
327
- className={`${styles.input} ${styles.isMedium}`}
328
- type="text"
329
- placeholder="IRC"
330
- onChange={e => setIRC(e.target.value)}
331
- value={irc} />
332
- </div>
333
- </div>
334
- <div className={`columns is-mobile ${styles.inputRow}`}>
335
- <div className='column is-one-quarter'>LinkedIn</div>
336
- <div className='column is-two-thirds'>
337
- <input
338
- className={`${styles.input} ${styles.isMedium}`}
339
- type="text"
340
- placeholder="LinkedIn"
341
- onChange={e => setLinkedin(e.target.value)}
342
- value={linkedin} />
343
- </div>
344
- </div>
345
- <div className={`columns is-mobile ${styles.inputRow}`}>
346
- <div className='column is-one-quarter'>X</div>
347
- <div className='column is-two-thirds'>
348
- <input
349
- className={`${styles.input} ${styles.isMedium}`}
350
- type="text"
351
- placeholder="Twitter/X"
352
- onChange={e => setTwitter(e.target.value)}
353
- value={twitter} />
354
- </div>
355
- </div>
356
- </div>
357
- </div>
358
- }
359
- </section>
360
- <footer className={`${styles.modalCardFoot}`}>
361
- <button onClick={() => closePopup()} className="button is-large">Discard</button>
362
- <button onClick={() => onClickSave()} className="button is-large">Update</button>
363
- </footer>
364
- </div>
365
- </div>
366
- )
367
- };
368
-
369
- export default ProfilePopupComponent