@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 +6 -2
- package/src/actions/event-actions.js +1 -10
- package/src/components/AvatarEditorModal/index.js +249 -0
- package/src/components/Navbar/index.js +12 -22
- package/src/components/Navbar/template.js +64 -84
- package/src/components/SyncWordsPlayer.js +58 -0
- package/src/components/VideoComponent.js +12 -3
- package/src/components/summit-my-orders-tickets/components/TicketPopup/TicketPopupEditDetailsForm/TicketPopupEditDetailsForm.js +5 -0
- package/src/components/ui/Button/index.js +41 -0
- package/src/components/ui/IconButton/index.js +32 -0
- package/src/components/ui/index.js +7 -0
- package/src/styles/full-profile.module.scss +3 -0
- package/src/styles/video.module.scss +21 -1
- package/src/templates/full-profile-page.js +24 -18
- package/src/utils/videoUtils.js +18 -0
- package/src/components/ProfilePopupComponent.js +0 -369
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8
|
+
items,
|
|
9
|
+
logo,
|
|
11
10
|
summit,
|
|
12
11
|
isLoggedUser,
|
|
13
|
-
idpLoading,
|
|
14
12
|
idpProfile,
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
36
|
+
const renderProfileIcon = () => {
|
|
37
|
+
if (!isLoggedUser || !idpProfile?.picture) return null;
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
<
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
</
|
|
105
|
-
</
|
|
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;
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
};
|
package/src/utils/videoUtils.js
CHANGED
|
@@ -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
|