diff --git a/app/javascript/chat/__tests__/__snapshots__/article.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/article.test.jsx.snap index a615af0d7..93f14bd56 100644 --- a/app/javascript/chat/__tests__/__snapshots__/article.test.jsx.snap +++ b/app/javascript/chat/__tests__/__snapshots__/article.test.jsx.snap @@ -79,6 +79,7 @@ exports[`
should load article 1`] = ` class="heart-reaction-button " data-category="like" onClick={[Function]} + type="button" > like reaction should load article 1`] = ` class="unicorn-reaction-button " data-category="unicorn" onClick={[Function]} + type="button" > unicorn reaction should load article 1`] = ` class="readinglist-reaction-button " data-category="readinglist" onClick={[Function]} + type="button" > readinglist reaction should load chat 1`] = ` diff --git a/app/javascript/chat/__tests__/__snapshots__/compose.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/compose.test.jsx.snap index c258418fa..1cb93ca6f 100644 --- a/app/javascript/chat/__tests__/__snapshots__/compose.test.jsx.snap +++ b/app/javascript/chat/__tests__/__snapshots__/compose.test.jsx.snap @@ -14,6 +14,7 @@ exports[` behavior with message should render and test snapshot 1`] = diff --git a/app/javascript/chat/__tests__/__snapshots__/githubRepo.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/githubRepo.test.jsx.snap index d3eb50c34..7077f3307 100644 --- a/app/javascript/chat/__tests__/__snapshots__/githubRepo.test.jsx.snap +++ b/app/javascript/chat/__tests__/__snapshots__/githubRepo.test.jsx.snap @@ -16,7 +16,13 @@ preact-render-spy (1 nodes) data-path="Camera" onClick={[Function value]} > - 📁 Camera + + 📁 + + Camera
@@ -26,7 +32,13 @@ preact-render-spy (1 nodes) data-path="Environment" onClick={[Function value]} > - 📁 Environment + + 📁 + + Environment
@@ -36,7 +48,13 @@ preact-render-spy (1 nodes) data-path="Interactables" onClick={[Function value]} > - 📁 Interactables + + 📁 + + Interactables
diff --git a/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap b/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap index 60219a67d..d92a9e227 100644 --- a/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap +++ b/app/javascript/chat/__tests__/__snapshots__/message.test.jsx.snap @@ -10,18 +10,21 @@ exports[` should render and test snapshot 1`] = ` asdf profile
@@ -27,19 +34,29 @@ preact-render-spy (1 nodes)
-

Channel Invitations 🤗

+

+ Channel Invitations + + 🤗 + +

name

some description
+ {/* eslint-disable-next-line react/no-danger */}
diff --git a/app/javascript/chat/channelDetails.jsx b/app/javascript/chat/channelDetails.jsx index 380daffd3..c15876cff 100644 --- a/app/javascript/chat/channelDetails.jsx +++ b/app/javascript/chat/channelDetails.jsx @@ -94,7 +94,8 @@ class ChannelDetails extends Component { userInList = (list, user) => { const keys = Object.keys(list); - for (const key of keys) { + for (let i = 0; i < keys.length; i += 1) { + const key = keys[i]; if (user.id === list[key].id) { return true; } @@ -144,16 +145,25 @@ class ChannelDetails extends Component { if (this.userInList(channel.channel_users, user)) { invite = ( - is already in {channel.channel_name} + is already in + {' '} + {channel.channel_name} ); } return (
- profile_image@ - {user.user.username} - {user.title} - {' '} + profile_image +@ + {user.user.username} + {' '} +- + {/* prettier-ignore */} + {' '} + {user.title} + + {' '} {invite}
); @@ -167,7 +177,12 @@ class ChannelDetails extends Component { rel="noopener noreferrer" data-content={`users/${user.id}`} > - @{user.username} - {user.name} + @ + {user.username} + {' '} +- + {' '} + {user.name}
)); @@ -188,7 +203,8 @@ class ChannelDetails extends Component {

Danger Zone

- You have left this channel{' '} + You have left this channel + {' '} 😢😢😢 diff --git a/app/javascript/chat/channels.jsx b/app/javascript/chat/channels.jsx index 6fd63bdf5..c9b88098f 100644 --- a/app/javascript/chat/channels.jsx +++ b/app/javascript/chat/channels.jsx @@ -1,7 +1,7 @@ import { h } from 'preact'; import PropTypes from 'prop-types'; +// eslint-disable-next-line import/no-unresolved import ConfigImage from 'images/three-dots.svg'; -import GroupImage from 'images/organization.svg'; const Channels = ({ activeChannelId, @@ -70,9 +70,11 @@ const Channels = ({
👋 - {' '} + + {' '} Welcome to - DEV Connect! You may message anyone you mutually follow. + DEV Connect +! You may message anyone you mutually follow.
); } @@ -111,12 +113,12 @@ const Channels = ({ Channels.propTypes = { activeChannelId: PropTypes.number.isRequired, - chatChannels: PropTypes.array.isRequired, + chatChannels: PropTypes.arrayOf(PropTypes.objectOf()).isRequired, handleSwitchChannel: PropTypes.func.isRequired, expanded: PropTypes.bool.isRequired, filterQuery: PropTypes.string.isRequired, channelsLoaded: PropTypes.bool.isRequired, - incomingVideoCallChannelIds: PropTypes.array.isRequired, + incomingVideoCallChannelIds: PropTypes.arrayOf(PropTypes.string).isRequired, }; export default Channels; diff --git a/app/javascript/chat/chat.jsx b/app/javascript/chat/chat.jsx index 230b304de..5727b6906 100644 --- a/app/javascript/chat/chat.jsx +++ b/app/javascript/chat/chat.jsx @@ -663,6 +663,7 @@ export default class Chat extends Component { }; handleFailure = err => { + // eslint-disable-next-line no-console console.error(err); }; diff --git a/app/javascript/chat/codeEditor.jsx b/app/javascript/chat/codeEditor.jsx index 3bb6646e9..30bba683f 100644 --- a/app/javascript/chat/codeEditor.jsx +++ b/app/javascript/chat/codeEditor.jsx @@ -2,7 +2,12 @@ import { h, Component } from 'preact'; import PropTypes from 'prop-types'; export default class CodeEditor extends Component { + static propTypes = { + activeChannelId: PropTypes.string.isRequired, + }; + componentDidMount() { + const { activeChannelId } = this.props; import('codemirror').then(CodeMirror => { const editor = document.getElementById('codeeditor'); const myCodeMirror = CodeMirror(editor, { @@ -13,11 +18,11 @@ export default class CodeEditor extends Component { myCodeMirror.setSize('100%', '100%'); // Initial trigger: const channel = window.pusher.channel( - `presence-channel-${this.props.activeChannelId}`, + `presence-channel-${activeChannelId}`, ); channel.trigger('client-livecode', { context: 'initializing-live-code-channel', - channel: `presence-channel-${this.props.activeChannelId}`, + channel: `presence-channel-${activeChannelId}`, }); // Coding trigger: myCodeMirror.on('keyup', cm => { diff --git a/app/javascript/chat/compose.jsx b/app/javascript/chat/compose.jsx index 2e95df20f..00adc7121 100644 --- a/app/javascript/chat/compose.jsx +++ b/app/javascript/chat/compose.jsx @@ -5,11 +5,12 @@ export default class Chat extends Component { static propTypes = { handleKeyDown: PropTypes.func.isRequired, handleSubmitOnClick: PropTypes.func.isRequired, - activeChannelId: PropTypes.number, + activeChannelId: PropTypes.number.isRequired, }; shouldComponentUpdate(nextProps) { - return this.props.activeChannelId != nextProps.activeChannelId; + const { activeChannelId } = this.props; + return activeChannelId !== nextProps.activeChannelId; } render() { @@ -25,6 +26,7 @@ export default class Chat extends Component { maxLength="1000" /> + ); +} + +VideoControlButton.propTypes = { + btnClassName: PropTypes.string.isRequired, + btnClickCallback: PropTypes.func.isRequired, + btnLabel: PropTypes.string.isRequired, +}; + export default class Video extends Component { + static propTypes = { + activeChannelId: PropTypes.string.isRequired, + onToggleSound: PropTypes.func.isRequired, + onToggleVideo: PropTypes.func.isRequired, + onExit: PropTypes.func.isRequired, + soundOn: PropTypes.bool.isRequired, + videoOn: PropTypes.bool.isRequired, + }; + constructor(props) { super(props); let leftPx = 40; @@ -19,36 +45,38 @@ export default class Video extends Component { topPx, pageX: null, pageY: null, - token: null, room: null, participants: [], }; } componentDidMount() { + const { activeChannelId } = this.props; getTwilioToken( - `private-video-channel-${this.props.activeChannelId}`, + `private-video-channel-${activeChannelId}`, this.setupCallChannel, ); } componentWillUnmount() { - this.state.room.disconnect(); + const { room } = this.state; + room.disconnect(); } setupCallChannel = response => { const component = this; + const { activeChannelId } = this.props; import('twilio-video').then(({ connect, createLocalVideoTrack }) => { connect( response.token, { - name: `private-video-channel-${this.props.activeChannelId}`, + name: `private-video-channel-${activeChannelId}`, audio: true, type: 'peer-to-peer', video: { width: 640 }, }, ).then( - function(room) { + function onConnectSuccess(room) { component.setState({ token: response.token, room }); createLocalVideoTrack().then(track => { const localMediaContainer = document.getElementById( @@ -62,26 +90,33 @@ export default class Video extends Component { roomParticipants.push(participant); }); component.setState({ participants: roomParticipants }); - room.on('participantConnected', function(participant) { + room.on('participantConnected', function onParticipantConnected( + participant, + ) { component.props.onParticipantChange(room.participants); component.triggerRemoteJoin(participant); room.participants.forEach(p => { roomParticipants.push(p); }); component.setState({ participants: roomParticipants }); - room.on('participantDisconnected', function() { - component.props.onParticipantChange(room.participants); - }); - participant.on('dominantSpeakerChanged', participant => { + room.on( + 'participantDisconnected', + function onParticipantDisconnected() { + component.props.onParticipantChange(room.participants); + }, + ); + participant.on('dominantSpeakerChanged', dominantSpeaker => { + // eslint-disable-next-line no-console console.log( 'The new dominant speaker in the Room is:', - participant, + dominantSpeaker, ); }); }); }, - function(error) { + function onConnectFailure(error) { document.getElementById('videoremotescreen').innerHTML = ''; + // eslint-disable-next-line no-console console.error(`Unable to connect to Room: ${error.message}`); }, ); @@ -118,25 +153,18 @@ export default class Video extends Component { }; handleDrag = e => { - if (!this.state.pageX) { + const { pageX, offsetDiffX, pageY, offsetDiffY } = this.state; + if (!pageX) { this.setState({ pageX: e.pageX, pageY: e.pageY, offsetDiffX: e.pageX - e.target.offsetLeft, offsetDiffY: e.pageY - e.target.offsetTop, }); - } else if (e.pageX != 0) { + } else if (e.pageX !== 0) { this.setState({ - leftPx: - this.state.pageX + - e.pageX - - this.state.pageX - - this.state.offsetDiffX, - topPx: - this.state.pageY + - e.pageY - - this.state.pageY - - this.state.offsetDiffY, + leftPx: pageX + e.pageX - pageX - offsetDiffX, + topPx: pageY + e.pageY - pageY - offsetDiffY, }); } else if (e.pageX === 0) { this.setState({ @@ -150,6 +178,7 @@ export default class Video extends Component { toggleSound = () => { const { room } = this.state; + const { onToggleSound } = this.props; if (room) { room.localParticipant.audioTracks.forEach(track => { if (track.isEnabled) { @@ -159,11 +188,12 @@ export default class Video extends Component { } }); } - this.props.onToggleSound(); + onToggleSound(); }; toggleVideo = () => { const { room } = this.state; + const { onToggleVideo } = this.props; if (room) { room.localParticipant.videoTracks.forEach(track => { if (track.isEnabled) { @@ -173,41 +203,41 @@ export default class Video extends Component { } }); } - this.props.onToggleVideo(); + onToggleVideo(); }; render() { + const { topPx, leftPx, participants } = this.state; + const { onExit, soundOn, videoOn } = this.props; return (
- - - + {VideoControlButton({ + btnClassName: 'chat__videocallexitbutton', + btnClickCallback: onExit, + btnLabel: '×', + })} + {VideoControlButton({ + btnClassName: 'chat__videocallcontrolbutton', + btnClickCallback: this.toggleSound, + btnLabel: soundOn ? 'Mute' : 'UnMute', + })} + {VideoControlButton({ + btnClassName: + 'chat__videocallcontrolbutton chat__videocallcontrolbutton--videoonoff', + btnClickCallback: this.toggleVideo, + btnLabel: videoOn ? 'Turn Off Video' : 'Turn On Video', + })}
); } diff --git a/app/javascript/chat/view.jsx b/app/javascript/chat/view.jsx index 12a88243d..e282a9a63 100644 --- a/app/javascript/chat/view.jsx +++ b/app/javascript/chat/view.jsx @@ -1,61 +1,82 @@ import { h, Component } from 'preact'; +import PropTypes from 'prop-types'; -const setupButton = ({className = '', onClickCallback, dataContent = '', btnLabel = ''}) => { +/** + * TODO: Instead of calling this function in render, use jsx (). + */ +function SetupButton({ className, onClickCallback, dataContent, btnLabel }) { return ( - ) + ); } +SetupButton.propTypes = { + className: PropTypes.string.isRequired, + onClickCallback: PropTypes.func.isRequired, + dataContent: PropTypes.string.isRequired, + btnLabel: PropTypes.string.isRequired, +}; + export default class View extends Component { - channel = (props) => { + static propTypes = { + onAcceptInvitation: PropTypes.func.isRequired, + onDeclineInvitation: PropTypes.func.isRequired, + onViewExit: PropTypes.func.isRequired, + channels: PropTypes.arrayOf(PropTypes.object).isRequired, + }; + + channel = props => { + const { onAcceptInvitation, onDeclineInvitation } = this.props; return (

{props.channel.channel_name}

{props.channel.description}
- { - setupButton({ - className: 'cta', - onClickCallback: this.props.onAcceptInvitation, - dataContent: props.channel.membership_id, - btnLabel: 'Accept' - }) - } - { - setupButton({ - className: 'cta', - onClickCallback: this.props.onDeclineInvitation, - dataContent: props.channel.membership_id, - btnLabel: 'Decline' - }) - } + {SetupButton({ + className: 'cta', + onClickCallback: onAcceptInvitation, + dataContent: props.channel.membership_id, + btnLabel: 'Accept', + })} + {SetupButton({ + className: 'cta', + onClickCallback: onDeclineInvitation, + dataContent: props.channel.membership_id, + btnLabel: 'Decline', + })}
); }; render() { - const channels = this.props.channels.map(channel => { - return + const { onViewExit, channels: channelsFromProps } = this.props; + const channels = channelsFromProps.map(channel => { + return ; }); return (
- { - setupButton({ - className: 'chatNonChatView_exitbutton', - onClickCallback: this.props.onViewExit, - dataContent: 'exit', - btnLabel: '×' - }) - } -

Channel Invitations 🤗

+ {SetupButton({ + className: 'chatNonChatView_exitbutton', + onClickCallback: onViewExit, + dataContent: 'exit', + btnLabel: '×', + })} +

+ Channel Invitations + {' '} + + 🤗 + +

{channels}