more updates to lobby page
includes showing chat notifications. also ui improvements
This commit is contained in:
parent
5a8c85e765
commit
d2c525f498
|
|
@ -12,11 +12,13 @@ import AppContext from '../../context/Context';
|
|||
import { getPageName } from '../../helpers/utils';
|
||||
|
||||
import useScript from '../../hooks/useScript';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { addMessage } from '../../store/features/textMessagesSlice';
|
||||
import { add as addNotification } from '../../store/features/notificationSlice';
|
||||
import { useLobbyChat } from '../sessions/JKLobbyChatContext';
|
||||
|
||||
import { useAuth } from '../../context/UserAuth';
|
||||
import { truncate } from '../../helpers/utils';
|
||||
|
||||
import HomePage from '../page/JKHomePage';
|
||||
|
||||
|
|
@ -38,9 +40,9 @@ import JKMusicSessionsLobby from '../page/JKMusicSessionsLobby';
|
|||
|
||||
const Msg = ({ closeToast, toastProps, title }) => (
|
||||
<div>
|
||||
<a href='#'>{title}</a>
|
||||
<a href="#">{title}</a>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
function JKDashboardMain() {
|
||||
const { isFluid, isVertical, navbarStyle } = useContext(AppContext);
|
||||
|
|
@ -48,21 +50,36 @@ function JKDashboardMain() {
|
|||
|
||||
const { isAuthenticated, currentUser, setCurrentUser, logout } = useAuth();
|
||||
|
||||
const scriptLoaded = useRef(false)
|
||||
const scriptLoaded = useRef(false);
|
||||
|
||||
const [showMessageModal, setShowMessageModal] = useState(false)
|
||||
const [messageUser, setMessageUser] = useState(null)
|
||||
const [showMessageModal, setShowMessageModal] = useState(false);
|
||||
const [messageUser, setMessageUser] = useState(null);
|
||||
|
||||
const {
|
||||
setMessages: setChatMessages,
|
||||
lobbyChatOffset,
|
||||
setLobbyChatOffset,
|
||||
fetchLobbyMessages,
|
||||
lobbyChatLimit,
|
||||
goToBottom
|
||||
} = useLobbyChat();
|
||||
|
||||
useEffect(() => {
|
||||
//DashboardRoutes.preload();
|
||||
//PublicRoutes.preload();
|
||||
}, []);
|
||||
|
||||
const [visibilityState, setVisibilityState] = useState('visible');
|
||||
|
||||
useEffect(() => {
|
||||
setVisibilityState(document.visibilityState);
|
||||
}, [document.visibilityState]);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const initJKScripts = () => {
|
||||
if(scriptLoaded.current){
|
||||
return
|
||||
if (scriptLoaded.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const app = window.JK.JamKazam();
|
||||
|
|
@ -91,7 +108,7 @@ function JKDashboardMain() {
|
|||
registerFriendRequestAccepted();
|
||||
registerChatMessageCallback();
|
||||
|
||||
scriptLoaded.current = true
|
||||
scriptLoaded.current = true;
|
||||
};
|
||||
|
||||
const registerTextMessageCallback = () => {
|
||||
|
|
@ -117,25 +134,43 @@ function JKDashboardMain() {
|
|||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
onClick: () => {
|
||||
setMessageUser({ id: msg.senderId, name: msg.senderName })
|
||||
setShowMessageModal(true)
|
||||
setMessageUser({ id: msg.senderId, name: msg.senderName });
|
||||
setShowMessageModal(true);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
handleNotification(payload, header.type);
|
||||
});
|
||||
};
|
||||
|
||||
const registerChatMessageCallback = () => {
|
||||
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.CHAT_MESSAGE, function (header, payload) {
|
||||
console.log("registerChatMessageCallback " + JSON.stringify(payload));
|
||||
// chatMessageReceived(payload);
|
||||
// context.ChatActions.msgReceived(payload);
|
||||
|
||||
// handledNotification(payload);
|
||||
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.CHAT_MESSAGE, function(header, payload) {
|
||||
console.log('registerChatMessageCallback ' + JSON.stringify(payload));
|
||||
if ( payload !== undefined && payload.sender_id !== window.currentUser.id) {
|
||||
if (visibilityState === 'hidden' && Notification.permission === 'granted') {
|
||||
try{
|
||||
const notification = new Notification("JamKazam Lobby Message", {
|
||||
body: `${payload.sender_name}: ${truncate(payload.msg, 100)}`,
|
||||
//icon: `${process.env.REACT_APP_CLIENT_BASE_URL}/assets/img/jamkazam-logo.png`
|
||||
});
|
||||
|
||||
notification.onclick = function(event) {
|
||||
event.preventDefault(); // prevent the browser from focusing the Notification's tab
|
||||
window.focus();
|
||||
event.target.close();
|
||||
};
|
||||
}catch(err){
|
||||
console.log('Error when showing notification', err);
|
||||
}
|
||||
}
|
||||
setChatMessages([]);
|
||||
fetchLobbyMessages({
|
||||
start: 0,
|
||||
limit: lobbyChatOffset === 0 ? lobbyChatLimit : lobbyChatOffset * lobbyChatLimit
|
||||
});
|
||||
//goToBottom();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const registerFriendRequest = () => {
|
||||
window.JK.JamServer.registerMessageCallback(window.JK.MessageType.FRIEND_REQUEST, function(header, payload) {
|
||||
|
|
@ -197,13 +232,10 @@ function JKDashboardMain() {
|
|||
<PrivateRoute path="/notifications" component={JKNotifications} />
|
||||
{/*Redirect*/}
|
||||
<Redirect to="/errors/404" />
|
||||
|
||||
</Switch>
|
||||
{!isKanban && <Footer />}
|
||||
</div>
|
||||
{ messageUser &&
|
||||
<JKMessageModal show={showMessageModal} setShow={setShowMessageModal} user={messageUser} />
|
||||
}
|
||||
{messageUser && <JKMessageModal show={showMessageModal} setShow={setShowMessageModal} user={messageUser} />}
|
||||
|
||||
{/* <SidePanelModal path={location.pathname} /> */}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import useNativeAppCheck from '../../hooks/useNativeAppCheck';
|
|||
import { useNativeApp } from '../../context/NativeAppContext';
|
||||
import JKModalDialog from '../common/JKModalDialog';
|
||||
import JKTooltip from '../common/JKTooltip';
|
||||
import { updateUser } from '../../helpers/rest';
|
||||
|
||||
function JKMusicSessionsLobby() {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -31,24 +32,24 @@ function JKMusicSessionsLobby() {
|
|||
const [submitted, setSubmitted] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('1');
|
||||
const [showNotificationsModal, setShowNotificationsModal] = useState(false);
|
||||
const NOTIFICATION_INTERVAL = 1000 * 10 //10 seconds
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchOnlineMusicians());
|
||||
|
||||
//check if browser notifications are enabled
|
||||
try {
|
||||
const notificationsEnabled = localStorage.getItem('showLobbyChatNotifications');
|
||||
const dontAskAgain = localStorage.getItem('dontAskLobbyChatNotificationPermission');
|
||||
if (notificationsEnabled || dontAskAgain) {
|
||||
if (notificationsEnabled === 'true' || dontAskAgain === 'true') {
|
||||
return;
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (true) {
|
||||
setShowNotificationsModal(true);
|
||||
}, NOTIFICATION_INTERVAL);
|
||||
}
|
||||
}, 10000);
|
||||
} catch (error) {
|
||||
console.log('Error reading localStorage', error);
|
||||
}
|
||||
} catch (error) {}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -96,6 +97,7 @@ function JKMusicSessionsLobby() {
|
|||
|
||||
const grantShowNotificationsPermission = () => {
|
||||
setShowNotificationsModal(false);
|
||||
|
||||
try {
|
||||
if (!window.Notification) {
|
||||
console.log('Your web browser does not support notifications.');
|
||||
|
|
@ -109,10 +111,13 @@ function JKMusicSessionsLobby() {
|
|||
Notification.requestPermission()
|
||||
.then(function(p) {
|
||||
if (p === 'granted') {
|
||||
updateUser(currentUser.id, { accept_desktop_notifications: true }).then(() => {
|
||||
console.log('User granted permission for desktop notifications');
|
||||
new Notification('Lobby Chat Notifications', {
|
||||
body: 'Notifications will appear here.',
|
||||
});
|
||||
localStorage.setItem('showLobbyChatNotifications', true);
|
||||
});
|
||||
} else {
|
||||
console.log('User blocked notifications.');
|
||||
}
|
||||
|
|
@ -183,17 +188,16 @@ function JKMusicSessionsLobby() {
|
|||
<Row className="swiper-container d-block d-md-none" data-testid="sessionsSwiper">
|
||||
<Nav tabs>
|
||||
<NavItem>
|
||||
<NavLink className="active" onClick={() => setActiveTab('1')}>
|
||||
<NavLink style={{ cursor: 'pointer'}} className={ activeTab === '1' ? 'active' : null } onClick={() => setActiveTab('1')}>
|
||||
Users
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink className="" onClick={() => setActiveTab('2')}>
|
||||
<NavLink style={{ cursor: 'pointer'}} className={ activeTab === '2' ? 'active' : null } onClick={() => setActiveTab('2')}>
|
||||
Chat
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
|
||||
<TabContent activeTab={activeTab}>
|
||||
<TabPane tabId="1">
|
||||
<Row>
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ const JKMessageModal = props => {
|
|||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onClick={toggle}>Cancel</Button>
|
||||
<Button onClick={toggle}>Close</Button>
|
||||
<Button color="primary" onClick={sendMessage} disabled={!newMessage}>
|
||||
Send Message
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||
import { useResponsive, useIsMobile } from '@farfetch/react-context-responsive';
|
||||
import JKInstrumentIcon from './JKInstrumentIcon';
|
||||
|
||||
const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) => {
|
||||
const JKPersonInstrumentsList = ({ instruments, showIcons, showAll, toggleMoreDetails }) => {
|
||||
const proficiencies = {
|
||||
'1': 'Beginner',
|
||||
'2': 'Intermediate',
|
||||
|
|
@ -26,9 +26,11 @@ const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) =>
|
|||
{instrumentsToShow &&
|
||||
instrumentsToShow.map(instrument => (
|
||||
<div key={instrument.instrument_id} className="text-nowrap mb-1">
|
||||
<span className='mr-1'>
|
||||
{showIcons && (
|
||||
<span className="mr-1">
|
||||
<JKInstrumentIcon instrumentId={instrument.instrument_id} instrumentName={instrument.description} />
|
||||
</span>
|
||||
)}
|
||||
<strong>{instrument.description}:</strong> {proficiencies[instrument.proficiency_level]}
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -44,11 +46,13 @@ const JKPersonInstrumentsList = ({ instruments, showAll, toggleMoreDetails }) =>
|
|||
JKPersonInstrumentsList.propTypes = {
|
||||
instruments: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
showAll: PropTypes.bool,
|
||||
showIcons: PropTypes.bool,
|
||||
toggleMoreDetails: PropTypes.func
|
||||
};
|
||||
|
||||
JKPersonInstrumentsList.defaltProps = {
|
||||
showAll: false
|
||||
showAll: false,
|
||||
showIcons: true
|
||||
};
|
||||
|
||||
export default JKPersonInstrumentsList;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import { useResponsive } from '@farfetch/react-context-responsive';
|
|||
import useOnScreen from '../../hooks/useOnScreen';
|
||||
import useKeepScrollPosition from '../../hooks/useKeepScrollPosition';
|
||||
|
||||
import { useLobbyChat } from './JKLobbyChatContext';
|
||||
|
||||
function JKLobbyChat() {
|
||||
const CHANNEL_LOBBY = 'lobby';
|
||||
const LIMIT = 10;
|
||||
|
|
@ -22,48 +24,50 @@ function JKLobbyChat() {
|
|||
const { greaterThan } = useResponsive();
|
||||
const scrolledToBottom = useRef(false);
|
||||
const { currentUser } = useAuth();
|
||||
const [fetching, setFetching] = useState(false);
|
||||
//const [fetching, setFetching] = useState(false);
|
||||
const [messagesArrived, setMessagesArrived] = useState(false);
|
||||
const [offset, setOffset] = useState(0);
|
||||
//const [offset, setOffset] = useState(0);
|
||||
|
||||
const chatMessages = useSelector(state => state.lobbyChat.records.messages);
|
||||
const next = useSelector(state => state.lobbyChat.records.next);
|
||||
const createStatus = useSelector(state => state.lobbyChat.create_status);
|
||||
|
||||
const [messages, setMessages] = useState([]);
|
||||
//const [messages, setMessages] = useState([]);
|
||||
const [lastMessageRef, setLastMessageRef] = useState(null);
|
||||
const isIntersecting = useOnScreen({ current: lastMessageRef });
|
||||
const { containerRef } = useKeepScrollPosition([messages]);
|
||||
//const { containerRef } = useKeepScrollPosition([messages]);
|
||||
|
||||
const fetchMessages = async (overrides = {}) => {
|
||||
const options = { start: offset * LIMIT, limit: LIMIT };
|
||||
const params = { ...options, ...overrides };
|
||||
try {
|
||||
setFetching(true);
|
||||
await dispatch(fetchLobbyChatMessages(params)).unwrap();
|
||||
} catch (error) {
|
||||
console.log('Error when fetching chat messages', error);
|
||||
} finally {
|
||||
setFetching(false);
|
||||
}
|
||||
};
|
||||
const { messages, setMessages, lobbyChatOffset, setLobbyChatOffset, fetchLobbyMessages, containerRef, goToBottom } = useLobbyChat();
|
||||
|
||||
// const fetchMessages = async (overrides = {}) => {
|
||||
// const options = { start: lobbyChatOffset * LIMIT, limit: LIMIT };
|
||||
// const params = { ...options, ...overrides };
|
||||
// try {
|
||||
// setLobbyChatFetching(true);
|
||||
// await dispatch(fetchLobbyChatMessages(params)).unwrap();
|
||||
// } catch (error) {
|
||||
// console.log('Error when fetching chat messages', error);
|
||||
// } finally {
|
||||
// setLobbyChatFetching(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
if (isIntersecting) {
|
||||
if (next) {
|
||||
setOffset(prev => prev + 1);
|
||||
setLobbyChatOffset(prev => prev + 1);
|
||||
}
|
||||
}
|
||||
}, [isIntersecting]);
|
||||
|
||||
useEffect(() => {
|
||||
if (offset !== 0) {
|
||||
fetchMessages();
|
||||
if (lobbyChatOffset !== 0) {
|
||||
fetchLobbyMessages();
|
||||
}
|
||||
}, [offset]);
|
||||
}, [lobbyChatOffset]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMessages();
|
||||
fetchLobbyMessages();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -91,9 +95,9 @@ function JKLobbyChat() {
|
|||
return ht == 0 || st == sh - ht;
|
||||
};
|
||||
|
||||
const goToBottom = () => {
|
||||
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
||||
};
|
||||
// const goToBottom = () => {
|
||||
// containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
||||
//};
|
||||
|
||||
const handleOnKeyPress = event => {
|
||||
if (event.key === 'Enter' || event.key === 'NumpadEnter') {
|
||||
|
|
@ -129,9 +133,8 @@ function JKLobbyChat() {
|
|||
|
||||
useEffect(() => {
|
||||
if (createStatus === 'succeeded') {
|
||||
// fetchMessages({ start: 0, limit: 1, lastOnly: true });
|
||||
setMessages([])
|
||||
fetchMessages({ start: 0, limit: offset === 0 ? LIMIT : offset * LIMIT})
|
||||
fetchLobbyMessages({ start: 0, limit: lobbyChatOffset === 0 ? LIMIT : lobbyChatOffset * LIMIT})
|
||||
messageTextBox.current.focus();
|
||||
}
|
||||
}, [createStatus]);
|
||||
|
|
@ -194,7 +197,7 @@ function JKLobbyChat() {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-2" style={{ height: '20%' }}>
|
||||
<div className="mt-2 mb-2" style={{ height: '20%' }}>
|
||||
<textarea
|
||||
style={{ width: '100%' }}
|
||||
value={newMessage}
|
||||
|
|
@ -214,3 +217,4 @@ function JKLobbyChat() {
|
|||
}
|
||||
|
||||
export default JKLobbyChat;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import React, { createContext, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { fetchLobbyChatMessages } from '../../store/features/lobbyChatMessagesSlice';
|
||||
import useKeepScrollPosition from '../../hooks/useKeepScrollPosition';
|
||||
// Create the context
|
||||
export const JKLobbyChatContext = createContext();
|
||||
|
||||
// Create a provider component
|
||||
export const JKLobbyChatProvider = ({ children }) => {
|
||||
const dispatch = useDispatch();
|
||||
const LIMIT = 10;
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [lobbyChatLimit, setLobbyChatLimit] = useState(LIMIT);
|
||||
const [lobbyChatOffset, setLobbyChatOffset] = useState(0);
|
||||
const [lobbyChatFetching, setLobbyChatFetching] = useState(false);
|
||||
const { containerRef } = useKeepScrollPosition([messages]);
|
||||
|
||||
const fetchLobbyMessages = async (overrides = {}) => {
|
||||
const options = { start: lobbyChatOffset * LIMIT, limit: LIMIT };
|
||||
const params = { ...options, ...overrides };
|
||||
try {
|
||||
setLobbyChatFetching(true);
|
||||
await dispatch(fetchLobbyChatMessages(params)).unwrap();
|
||||
} catch (error) {
|
||||
console.log('Error when fetching chat messages', error);
|
||||
} finally {
|
||||
setLobbyChatFetching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const goToBottom = () => {
|
||||
containerRef.current.scrollTop = containerRef.current.scrollHeight;
|
||||
};
|
||||
|
||||
return (
|
||||
<JKLobbyChatContext.Provider value={{ messages, setMessages, lobbyChatOffset, setLobbyChatOffset, fetchLobbyMessages, lobbyChatLimit, goToBottom, containerRef }}>
|
||||
{children}
|
||||
</JKLobbyChatContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useLobbyChat = () => React.useContext(JKLobbyChatContext);
|
||||
|
|
@ -5,6 +5,7 @@ import JKMessageButton from '../profile/JKMessageButton';
|
|||
import JKMoreDetailsButton from '../profile/JKMoreDetailsButton';
|
||||
import JKLatencyBadge from '../profile/JKLatencyBadge';
|
||||
import JKProfileInstrumentsList from '../profile/JKProfileInstrumentsList';
|
||||
import JKProfileGenres from '../profile/JKProfileGenres';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import JKProfileSidePanel from '../profile/JKProfileSidePanel';
|
||||
|
|
@ -66,14 +67,18 @@ function JKLobbyUser({ user, setSelectedUsers }) {
|
|||
<JKLatencyBadge latencyData={latencyData} />
|
||||
</div>
|
||||
<div>
|
||||
<strong>{t('person_attributes.instruments', { ns: 'people' })}</strong>
|
||||
{/* <JKProfileInstrumentsList instruments={user.instruments} toggleMoreDetails={toggleMoreDetails} /> */}
|
||||
<JKProfileInstrumentsList
|
||||
instruments={user.instruments}
|
||||
toggleMoreDetails={toggleMoreDetails}
|
||||
showIcons={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className="align-middle text-center">
|
||||
<div className="d-inline-flex flex-wrap" style={{ gap: '2px' }}>
|
||||
<JKConnectButton
|
||||
currentUser={currentUser}
|
||||
user={user}
|
||||
|
|
@ -89,6 +94,7 @@ function JKLobbyUser({ user, setSelectedUsers }) {
|
|||
<JKMoreDetailsButton toggleMoreDetails={toggleMoreDetails} cssClasses="btn btn-primary fs--1 px-2 py-1">
|
||||
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
|
||||
</JKMoreDetailsButton>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
|
|
@ -97,16 +103,20 @@ function JKLobbyUser({ user, setSelectedUsers }) {
|
|||
<strong>{t('person_attributes.latency_to_me', { ns: 'people' })}:</strong>{' '}
|
||||
<JKLatencyBadge latencyData={latencyData} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="mt-3">
|
||||
<h5>{t('person_attributes.instruments', { ns: 'people' })}</h5>
|
||||
{/* <JKProfileInstrumentsList instruments={instruments} toggleMoreDetails={toggleMoreDetails} /> */}
|
||||
<JKProfileInstrumentsList
|
||||
instruments={user.instruments}
|
||||
toggleMoreDetails={toggleMoreDetails}
|
||||
showIcons={false}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mt-3">
|
||||
<h5>{t('person_attributes.genres', { ns: 'people' })}</h5>
|
||||
{/* <JKProfileGenres genres={genres} toggleMoreDetails={toggleMoreDetails} /> */}
|
||||
<JKProfileGenres genres={user.genres} toggleMoreDetails={toggleMoreDetails} />
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div className="d-inline-flex flex-wrap" style={{ gap: '2px' }}>
|
||||
<JKConnectButton
|
||||
currentUser={currentUser}
|
||||
user={user}
|
||||
|
|
@ -128,6 +138,7 @@ function JKLobbyUser({ user, setSelectedUsers }) {
|
|||
<FontAwesomeIcon icon="user" transform="shrink-4 down-1" className="mr-1" />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ function JKLobbyUserList({ loadingStatus, onlineMusicians, selectedUsers, setSel
|
|||
onlineMusicians.map(musician => <JKLobbyUser key={musician.id} user={musician} setSelectedUsers={setSelectedUsers} />)
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={2}>No users currently online.</td>
|
||||
<td colSpan={2}>No musicians are currently available here.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import JKProfileAvatar from '../profile/JKProfileAvatar';
|
||||
import { isIterableArray } from '../../helpers/utils';
|
||||
import Loader from '../common/Loader';
|
||||
|
||||
// import Swiper core and required modules
|
||||
import SwiperCore, { Navigation, Pagination, Scrollbar, A11y } from 'swiper';
|
||||
|
|
@ -22,13 +24,19 @@ SwiperCore.use([Navigation, Pagination, Scrollbar, A11y]);
|
|||
|
||||
const JKLobbyUserSwiper = ({ onlineMusicians, setSelectedUsers, loadingStatus }) => {
|
||||
return (
|
||||
<>
|
||||
{loadingStatus === 'loading' && onlineMusicians.length === 0 ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<>
|
||||
{isIterableArray(onlineMusicians) ? (
|
||||
<>
|
||||
<Swiper
|
||||
spaceBetween={0}
|
||||
slidesPerView={1}
|
||||
//onSlideChange={() => console.log('slide change')}
|
||||
onSlideNextTransitionEnd={swiper => {
|
||||
if(swiper.isEnd){
|
||||
if (swiper.isEnd) {
|
||||
//goNextPage()
|
||||
}
|
||||
}}
|
||||
|
|
@ -42,12 +50,11 @@ const JKLobbyUserSwiper = ({ onlineMusicians, setSelectedUsers, loadingStatus })
|
|||
}}
|
||||
>
|
||||
{onlineMusicians.map((musician, index) => (
|
||||
|
||||
<SwiperSlide key={`session-lobby-swiper-item-${musician.id}`}>
|
||||
<Card className="swiper-person-card">
|
||||
<CardHeader className="text-center bg-200">
|
||||
<CardHeader className="bg-200">
|
||||
<div className="avatar avatar-xl d-inline-block me-2 mr-2">
|
||||
<JKProfileAvatar url={musician.photo_url} size="xl"/>
|
||||
<JKProfileAvatar url={musician.photo_url} size="xl" />
|
||||
</div>
|
||||
<h5 className="d-inline-block align-top mt-1">{musician.name}</h5>
|
||||
</CardHeader>
|
||||
|
|
@ -64,12 +71,18 @@ const JKLobbyUserSwiper = ({ onlineMusicians, setSelectedUsers, loadingStatus })
|
|||
<div className="swiper-button-next" />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div>No musicians are currently available here.</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
JKLobbyUserSwiper.propTypes = {
|
||||
onlineMusicians: PropTypes.arrayOf(PropTypes.instanceOf(Object)).isRequired,
|
||||
setSelectedUsers: PropTypes.func.isRequired,
|
||||
setSelectedUsers: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default JKLobbyUserSwiper;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,25 @@ export const getPeopleIndex = () => {
|
|||
})
|
||||
}
|
||||
|
||||
export const getLobbyUsers = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/lobby`)
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
export const updateUser = (id, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch(`/users/${id}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => resolve(response))
|
||||
.catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
export const getGenres = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
apiFetch('/genres')
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { BrowserQueryProvider } from '../context/BrowserQuery';
|
|||
|
||||
import { NativeAppProvider } from '../context/NativeAppContext';
|
||||
|
||||
import { JKLobbyChatProvider } from '../components/sessions/JKLobbyChatContext';
|
||||
|
||||
const DashboardLayout = ({ location }) => {
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
|
|
@ -16,7 +18,9 @@ const DashboardLayout = ({ location }) => {
|
|||
<UserAuth path={location.pathname}>
|
||||
<BrowserQueryProvider>
|
||||
<NativeAppProvider>
|
||||
<JKLobbyChatProvider>
|
||||
<DashboardMain />
|
||||
</JKLobbyChatProvider>
|
||||
</NativeAppProvider>
|
||||
</BrowserQueryProvider>
|
||||
</UserAuth>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,18 @@ const chatMessagesSlice = createSlice({
|
|||
};
|
||||
state.status = 'succeeded';
|
||||
})
|
||||
// .addCase(fetchLobbyChatMessages.fulfilled, (state, action) => {
|
||||
// const lastOnly = action.meta.arg.lastOnly;
|
||||
// const currentChatMessageIds = state.records.messages.map(m => m.id);
|
||||
// const newMessages = action.payload.chats.filter(m => !currentChatMessageIds.includes(m.id));
|
||||
// state.records = {
|
||||
// next: state.records.next === null && lastOnly? null : action.payload.next,
|
||||
// messages: state.records.messages.concat(newMessages).map(m => ({...m, status: 'delivered'})).sort((a, b) => {
|
||||
// return new Date(a.created_at) - new Date(b.created_at);
|
||||
// })
|
||||
// };
|
||||
// state.status = 'succeeded';
|
||||
// })
|
||||
.addCase(fetchLobbyChatMessages.rejected, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.status = 'failed';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import {createSlice, createAsyncThunk} from "@reduxjs/toolkit";
|
||||
import { getPeopleIndex } from "../../helpers/rest";
|
||||
import { getPeopleIndex, getLobbyUsers } from "../../helpers/rest";
|
||||
|
||||
const initialState = {
|
||||
musicians: [],
|
||||
|
|
@ -10,7 +10,8 @@ const initialState = {
|
|||
export const fetchOnlineMusicians = createAsyncThunk(
|
||||
'onlineMusician/fetchMusicians',
|
||||
async (options, thunkAPI) => {
|
||||
const response = await getPeopleIndex(options)
|
||||
//const response = await getPeopleIndex(options)
|
||||
const response = await getLobbyUsers(options)
|
||||
return response.json()
|
||||
}
|
||||
)
|
||||
|
|
@ -32,6 +33,7 @@ export const onlineMusiciansSlice = createSlice({
|
|||
state.status = 'loading'
|
||||
})
|
||||
.addCase(fetchOnlineMusicians.fulfilled, (state, action) => {
|
||||
console.log('fetchOnlineMusicians.fulfilled', action.payload)
|
||||
state.status = 'succeeded'
|
||||
state.musicians = action.payload
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
class AddAcceptDesktopNotificationsToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
execute("ALTER TABLE public.users ADD COLUMN accept_desktop_notifications BOOLEAN DEFAULT false;")
|
||||
end
|
||||
def self.down
|
||||
execute("ALTER TABLE public.users DROP COLUMN accept_desktop_notifications;")
|
||||
end
|
||||
end
|
||||
|
|
@ -187,7 +187,7 @@ module JamRuby
|
|||
elsif channel == 'global'
|
||||
@@mq_router.publish_to_active_clients(msg)
|
||||
elsif channel == 'lobby'
|
||||
@@mq_router.publish_to_all_clients(msg) #TODO: only publish to lobby users
|
||||
@@mq_router.publish_to_active_clients(msg) #TODO: only publish to lobby users
|
||||
elsif channel == 'lesson'
|
||||
@@mq_router.publish_to_user(target_user.id, msg, sender = {:client_id => client_id}) if target_user
|
||||
@@mq_router.publish_to_user(user.id, msg, sender = {:client_id => client_id}) if user
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ module JamRuby
|
|||
tm.message = sanitized_text
|
||||
tm.target_user_id = target_user_id
|
||||
tm.source_user_id = source_user_id
|
||||
tm.valid?
|
||||
Rails.logger.info "___ERROR #{tm.errors.inspect}" unless tm.errors.empty?
|
||||
tm.save
|
||||
|
||||
# send notification
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ module JamRuby
|
|||
JAM_REASON_JOIN = 'j'
|
||||
JAM_REASON_IMPORT = 'i'
|
||||
JAM_REASON_LOGIN = 'l'
|
||||
JAM_REASON_PRESENT = 'p'
|
||||
|
||||
# MOD KEYS
|
||||
MOD_GEAR = "gear"
|
||||
|
|
@ -308,6 +309,14 @@ module JamRuby
|
|||
scope :came_through_amazon, -> { joins(:posa_cards).where('posa_cards.lesson_package_type_id in (?)', LessonPackageType::AMAZON_PACKAGES + LessonPackageType::LESSON_PACKAGE_TYPES)}
|
||||
scope :not_deleted, ->{ where(deleted: false) }
|
||||
|
||||
def self.lobby(current_user, options = {})
|
||||
query = User.where("users.id <> ? AND users.last_jam_updated_at > ?", current_user.id, 15.minutes.ago)
|
||||
if live_music_sessions = ActiveMusicSession.count > 0
|
||||
query = query.where("users.id NOT IN (?)", live_music_sessions.pluck(:user_id))
|
||||
end
|
||||
query
|
||||
end
|
||||
|
||||
def after_save
|
||||
if school_interest && !school_interest_was
|
||||
if education_interest
|
||||
|
|
@ -1959,21 +1968,28 @@ module JamRuby
|
|||
def update_addr_loc(connection, reason)
|
||||
unless connection
|
||||
@@log.warn("no connection specified in update_addr_loc with reason #{reason}")
|
||||
update_last_active(reason)
|
||||
return
|
||||
end
|
||||
|
||||
if connection.locidispid.nil?
|
||||
@@log.warn("no locidispid for connection's ip_address: #{connection.ip_address}")
|
||||
update_last_active(reason)
|
||||
return
|
||||
end
|
||||
|
||||
# we don't use a websocket login to update the user's record unless there is no addr
|
||||
if reason == JAM_REASON_LOGIN && last_jam_addr
|
||||
update_last_active(reason)
|
||||
return
|
||||
end
|
||||
|
||||
self.last_jam_addr = connection.addr
|
||||
self.last_jam_locidispid = connection.locidispid
|
||||
update_last_active(reason)
|
||||
end
|
||||
|
||||
def update_last_active(reason)
|
||||
self.last_jam_updated_reason = reason
|
||||
self.last_jam_updated_at = Time.now
|
||||
unless self.save
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
context.WEB_SOCKET_SWF_LOCATION = "assets/flash/WebSocketMain.swf";
|
||||
|
||||
context.JK.JamServer = function (app, activeElementEvent) {
|
||||
console.log("_DEBUG Init JK scripts....", context.JK.JamServer);
|
||||
// uniquely identify the websocket connection
|
||||
var channelId = null;
|
||||
var clientType = null;
|
||||
|
|
@ -197,7 +198,7 @@
|
|||
|
||||
|
||||
function loggedIn(header, payload) {
|
||||
|
||||
console.log("___loggedIn", header, payload)
|
||||
// reason for setTimeout:
|
||||
// loggedIn causes an absolute ton of initialization to happen, and errors sometimes happen
|
||||
// but because loggedIn(header,payload) is a callback from a websocket, the browser doesn't show a stack trace...
|
||||
|
|
@ -247,6 +248,7 @@
|
|||
// where there is no device on startup for the current profile.
|
||||
// So, in that case, it's possible that a reconnect loop will attempt, but we *do not want*
|
||||
// it to go through unless we've passed through .OnLoggedIn
|
||||
|
||||
server.connected = true;
|
||||
server.reconnecting = false;
|
||||
server.connecting = false;
|
||||
|
|
@ -315,17 +317,18 @@
|
|||
}
|
||||
|
||||
function activityCheck() {
|
||||
var timeoutTime = 300000; // 5 * 1000 * 60 , 5 minutes
|
||||
//var timeoutTime = 300000; // 5 * 1000 * 60 , 5 minutes
|
||||
var timeoutTime = 2000; // 5 * 1000 * 60 , 5 minutes
|
||||
active = true;
|
||||
setActive(active)
|
||||
activityTimeout = setTimeout(markAway, timeoutTime);
|
||||
$(document).ready(function() {
|
||||
$('body').bind('mousedown keydown touchstart focus', function(event) {
|
||||
|
||||
if (activityTimeout) {
|
||||
clearTimeout(activityTimeout);
|
||||
activityTimeout = null;
|
||||
}
|
||||
|
||||
if (!active) {
|
||||
if(server && server.connected) {
|
||||
logger.debug("awake again!")
|
||||
|
|
@ -701,6 +704,7 @@
|
|||
server.socket.onopen = server.onOpen;
|
||||
server.socket.onmessage = server.onMessage;
|
||||
server.socket.onclose = server.onClose;
|
||||
server.socket.onerror = server.onError;
|
||||
|
||||
connectTimeout = setTimeout(function () {
|
||||
logger.debug("connection timeout fired")
|
||||
|
|
@ -710,7 +714,7 @@
|
|||
server.close(true);
|
||||
connectDeferred.reject();
|
||||
}
|
||||
}, 4000);
|
||||
}, 10000);
|
||||
|
||||
return connectDeferred;
|
||||
};
|
||||
|
|
@ -740,15 +744,16 @@
|
|||
server.onOpen = function () {
|
||||
logger.debug("server.onOpen");
|
||||
|
||||
// we should receive LOGIN_ACK very soon. we already set a timer elsewhere to give 4 seconds to receive it
|
||||
// we should receive LOGIN_ACK very soon. we already set a timer elsewhere to give 10 seconds to receive it
|
||||
};
|
||||
|
||||
server.onMessage = function (e) {
|
||||
console.log("server.onMessage", e)
|
||||
var message = JSON.parse(e.data),
|
||||
messageType = message.type.toLowerCase(),
|
||||
payload = message[messageType],
|
||||
callbacks = server.dispatchTable[message.type];
|
||||
|
||||
console.log("server.onMessage", message, messageType, payload, callbacks)
|
||||
if (message.type == context.JK.MessageType.PEER_MESSAGE) {
|
||||
console.log("server.onMessage:" + messageType);
|
||||
}
|
||||
|
|
@ -773,8 +778,8 @@
|
|||
};
|
||||
|
||||
// onClose is called if either client or server closes connection
|
||||
server.onClose = function () {
|
||||
logger.info("Socket to server closed.");
|
||||
server.onClose = function (e) {
|
||||
logger.info("Socket to server closed.", e.code, e.reason);
|
||||
|
||||
var disconnectedSocket = this;
|
||||
|
||||
|
|
@ -790,6 +795,10 @@
|
|||
closedCleanup(true);
|
||||
};
|
||||
|
||||
server.onError = function (e) {
|
||||
logger.error("Socket error.", e);
|
||||
};
|
||||
|
||||
server.send = function (message) {
|
||||
|
||||
var jsMessage = JSON.stringify(message);
|
||||
|
|
@ -943,8 +952,6 @@
|
|||
registerSocketClosed();
|
||||
activityCheck();
|
||||
|
||||
|
||||
console.log("Init JK scripts....", context.JK.JamServer);
|
||||
context.JK.JamServer.send("hello")
|
||||
|
||||
// $inSituBanner = $('.server-connection');
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ class ApiUsersController < ApiController
|
|||
respond_with @users, responder: ApiResponder, :status => 200
|
||||
end
|
||||
|
||||
def lobby
|
||||
@users = User.lobby(current_user)
|
||||
respond_with @users, responder: ApiResponder, :status => 200
|
||||
end
|
||||
|
||||
def calendar
|
||||
#@user=lookup_user
|
||||
#ics = CalendarManager.new.create_ics_feed(@user)
|
||||
|
|
@ -230,6 +235,7 @@ class ApiUsersController < ApiController
|
|||
@user.education_interest = !!params[:education_interest]
|
||||
@user.retailer_interest = !!params[:retailer_interest]
|
||||
@user.desired_package = LessonPackageType.find_by_package_type!(params[:desired_package]) if params.has_key?(:desired_package)
|
||||
@user.accept_desktop_notifications = params[:accept_desktop_notifications] if params.has_key?(:accept_desktop_notifications)
|
||||
if @user.save
|
||||
|
||||
test_drive_package_details = params[:test_drive_package]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
collection @users
|
||||
|
||||
# do not retrieve all child collections when showing a list of users
|
||||
attributes :id, :first_name, :last_name, :name, :photo_url, :biography
|
||||
|
||||
child :musician_instruments => :instruments do
|
||||
attributes :instrument_id, :description, :proficiency_level, :priority
|
||||
end
|
||||
|
||||
child :genres => :genres do
|
||||
attributes :genre_id, :description
|
||||
end
|
||||
|
|
@ -391,6 +391,7 @@ Rails.application.routes.draw do
|
|||
match '/me' => 'api_users#me', :via => :get
|
||||
|
||||
match '/users' => 'api_users#index', :via => :get
|
||||
match '/users/lobby' => 'api_users#lobby', :via => :get
|
||||
match '/users' => 'api_users#create', :via => :post
|
||||
match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail'
|
||||
match '/users/:id/authorizations' => 'api_users#authorizations', :via => :get
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ module JamWebsockets
|
|||
|
||||
# this thread runs forever while WSG runs, and should do anything easily gotten out of critical message handling path
|
||||
@background_thread = Thread.new {
|
||||
count = 0
|
||||
client_check_count = 0
|
||||
user_last_seen_count = 0
|
||||
|
||||
while true
|
||||
|
||||
|
|
@ -103,11 +104,17 @@ module JamWebsockets
|
|||
periodical_check_connections
|
||||
periodical_notification_seen
|
||||
|
||||
if count == 30
|
||||
if client_check_count == 30
|
||||
periodical_check_clients
|
||||
count = 0
|
||||
client_check_count = 0
|
||||
end
|
||||
count = count + 1
|
||||
client_check_count = client_check_count + 1
|
||||
|
||||
if user_last_seen_count == 120
|
||||
periodical_update_user_last_seen
|
||||
user_last_seen_count = 0
|
||||
end
|
||||
user_last_seen_count = user_last_seen_count + 1
|
||||
|
||||
rescue => e
|
||||
@log.error "unhandled error in thread #{e}"
|
||||
|
|
@ -1514,6 +1521,21 @@ module JamWebsockets
|
|||
|
||||
end
|
||||
|
||||
def periodical_update_user_last_seen
|
||||
active_users_ids = @client_lookup.map { |client_id, client_context| client_context.active ? client_context.user.id : nil }.compact.uniq
|
||||
|
||||
if active_users_ids.any?
|
||||
sql = %{
|
||||
update users set last_jam_updated_at = now(), last_jam_updated_reason=#{User::JAM_REASON_PRESENT} where users.id in (#{active_users_ids.join(',')});
|
||||
}
|
||||
@log.info("SQL #{sql}")
|
||||
|
||||
ConnectionManager.active_record_transaction do |connection_manager, conn|
|
||||
conn.exec(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def periodical_stats_dump
|
||||
|
||||
# assume 60 seconds per status dump
|
||||
|
|
|
|||
Loading…
Reference in New Issue