Fix eslint lints in chat's jsx files (#4302) [deploy]

This commit is contained in:
Tamas Fodor 2019-11-04 21:51:30 +01:00 committed by Mac Siri
parent 85d8f52789
commit 76e099599b
20 changed files with 308 additions and 137 deletions

View file

@ -79,6 +79,7 @@ exports[`<Article /> should load article 1`] = `
class="heart-reaction-button "
data-category="like"
onClick={[Function]}
type="button"
>
<img
alt="like reaction"
@ -90,6 +91,7 @@ exports[`<Article /> should load article 1`] = `
class="unicorn-reaction-button "
data-category="unicorn"
onClick={[Function]}
type="button"
>
<img
alt="unicorn reaction"
@ -101,6 +103,7 @@ exports[`<Article /> should load article 1`] = `
class="readinglist-reaction-button "
data-category="readinglist"
onClick={[Function]}
type="button"
>
<img
alt="readinglist reaction"

View file

@ -207,6 +207,7 @@ exports[`<Chat /> should load chat 1`] = `
<button
class="messagecomposer__submit"
onClick={[Function]}
type="button"
>
SEND
</button>

View file

@ -14,6 +14,7 @@ exports[`<Compose /> behavior with message should render and test snapshot 1`] =
<button
class="messagecomposer__submit"
onClick={[Function]}
type="button"
>
SEND
</button>

View file

@ -16,7 +16,13 @@ preact-render-spy (1 nodes)
data-path="Camera"
onClick={[Function value]}
>
📁 Camera
<span
role="img"
aria-label="folder-emoji"
>
📁
</span>
Camera
</a>
</div>
<div class="activecontent__githubrepofilerow">
@ -26,7 +32,13 @@ preact-render-spy (1 nodes)
data-path="Environment"
onClick={[Function value]}
>
📁 Environment
<span
role="img"
aria-label="folder-emoji"
>
📁
</span>
Environment
</a>
</div>
<div class="activecontent__githubrepofilerow">
@ -36,7 +48,13 @@ preact-render-spy (1 nodes)
data-path="Interactables"
onClick={[Function value]}
>
📁 Interactables
<span
role="img"
aria-label="folder-emoji"
>
📁
</span>
Interactables
</a>
</div>
<div class="activecontent__githubrepofilerow">

View file

@ -10,18 +10,21 @@ exports[`<Message /> should render and test snapshot 1`] = `
<a
data-content="users/undefined"
href="/asdf"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="asdf profile"
class="chatmessagebody__profileimage"
data-content="users/undefined"
role="presentation"
src=""
/>
</a>
</div>
<div
class="chatmessage__body"
role="presentation"
>
<span
class="chatmessagebody__username"
@ -35,6 +38,7 @@ exports[`<Message /> should render and test snapshot 1`] = `
class="chatmessagebody__username--link"
data-content="users/undefined"
href="/asdf"
rel="noopener noreferrer"
target="_blank"
>
asdf

View file

@ -24,18 +24,21 @@ exports[`<Video /> should render properly and test snapshot 1`] = `
<button
class="chat__videocallexitbutton"
onClick={[Function]}
type="button"
>
×
</button>
<button
class="chat__videocallcontrolbutton"
onClick={[Function]}
type="button"
>
UnMute
</button>
<button
class="chat__videocallcontrolbutton chat__videocallcontrolbutton--videoonoff"
onClick={[Function]}
type="button"
>
Turn On Video
</button>

View file

@ -11,11 +11,18 @@ exports[`<View /> should render and test snapshot (no channel) 1`] = `
class="chatNonChatView_exitbutton"
data-content="exit"
onClick={[Function]}
type="button"
>
×
</button>
<h1>
Channel Invitations 🤗
Channel Invitations
<span
aria-label="hugging-emoji"
role="img"
>
🤗
</span>
</h1>
</div>
</div>
@ -27,19 +34,29 @@ preact-render-spy (1 nodes)
<div class="chatNonChatView">
<div class="container">
<button
type="button"
class="chatNonChatView_exitbutton"
onClick={[Function onViewExitFake]}
data-content="exit"
>
×
</button>
<h1>Channel Invitations 🤗</h1>
<h1>
Channel Invitations
<span
role="img"
aria-label="hugging-emoji"
>
🤗
</span>
</h1>
<div class="chatNonChatView_contentblock">
<h2>name</h2>
<div>
<em>some description</em>
</div>
<button
type="button"
class="cta"
onClick={[Function handleInvitationAcceptFake]}
data-content="12345"
@ -47,6 +64,7 @@ preact-render-spy (1 nodes)
Accept
</button>
<button
type="button"
class="cta"
onClick={[Function handleInvitationDeclineFake]}
data-content="12345"

View file

@ -132,9 +132,7 @@ describe('<ChannelDetails />', () => {
const pendingusers = context.find('.channeldetails__pendingusers');
for (let i = 0; i < pendingusers.length; i += 1) {
expect(pendingusers.at(i).text()).toEqual(
`@${moddetails.pending_users_select_fields[i].username} - ${
moddetails.pending_users_select_fields[i].name
}`,
`@${moddetails.pending_users_select_fields[i].username} - ${moddetails.pending_users_select_fields[i].name}`,
);
expect(
pendingusers
@ -199,11 +197,13 @@ describe('<ChannelDetails />', () => {
let inviteAttrAns;
const included = (list, el) => {
const keys = Object.keys(list);
for (const key of keys) {
for (let i = 0; i < keys.length; i += 1) {
const key = keys[i];
if (list[key].id === el.id) {
return true;
}
}
return false;
};
for (let i = 0; i < searchedusersdivs.length; i += 1) {
if (
@ -224,9 +224,7 @@ describe('<ChannelDetails />', () => {
.childAt(0)
.text(),
).toEqual(
`@${searchedusers.searchedUsers[i].user.username} - ${
searchedusers.searchedUsers[i].title
}`,
`@${searchedusers.searchedUsers[i].user.username} - ${searchedusers.searchedUsers[i].title}`,
);
if (

View file

@ -16,7 +16,7 @@ global.window = doc.defaultView;
global.window.algoliasearch = algoliasearch;
// mock observer and user ID
window.IntersectionObserver = jest.fn(function() {
window.IntersectionObserver = jest.fn(function intersectionObserverMock() {
this.observe = jest.fn();
});
global.window.currentUser = { id: 'some_id' };

View file

@ -1,20 +1,30 @@
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
// eslint-disable-next-line import/no-unresolved
import heartImage from 'images/emoji/emoji-one-heart.png';
// eslint-disable-next-line import/no-unresolved
import unicornImage from 'images/emoji/emoji-one-unicorn.png';
// eslint-disable-next-line import/no-unresolved
import bookmarkImage from 'images/emoji/emoji-one-bookmark.png';
export default class Article extends Component {
static propTypes = {
resource: PropTypes.shape({
id: PropTypes.string,
}).isRequired,
};
constructor(props) {
super(props);
this.state = {
reactionCounts: [],
userReactions: [],
optimisticUserReaction: null,
};
}
componentDidMount() {
fetch(`/reactions?article_id=${this.props.resource.id}`, {
const { resource } = this.props;
fetch(`/reactions?article_id=${resource.id}`, {
Accept: 'application/json',
'Content-Type': 'application/json',
credentials: 'same-origin',
@ -29,11 +39,12 @@ export default class Article extends Component {
};
displayReactionsFailure = response => {
// eslint-disable-next-line no-console
console.log(response);
};
handleNewReactionResponse = response => {
let oldUserReactions = this.state.userReactions;
let { userReactions: oldUserReactions } = this.state;
const foundReactions = oldUserReactions.filter(obj => {
return obj.category === response.category;
});
@ -41,7 +52,7 @@ export default class Article extends Component {
oldUserReactions.push({ category: response.category });
} else {
oldUserReactions = oldUserReactions.filter(obj => {
return obj.category != response.category;
return obj.category !== response.category;
});
}
this.setState({
@ -51,14 +62,15 @@ export default class Article extends Component {
};
handleNewReactionFailure = response => {
// eslint-disable-next-line no-console
console.log(response);
};
handleReactionClick = e => {
e.preventDefault();
const { target } = e;
const { resource: article } = this.props;
this.setState({ optimisticUserReaction: target.dataset.category });
const article = this.props.resource;
fetch('/reactions', {
method: 'POST',
headers: {
@ -93,6 +105,7 @@ export default class Article extends Component {
return (
<button
type="button"
className={`${curType[0]} ${props.reactedClass}`}
onClick={this.handleReactionClick}
data-category={curType[1]}
@ -107,7 +120,7 @@ export default class Article extends Component {
};
render() {
const article = this.props.resource;
const { resource: article } = this.props;
let heartReactedClass = '';
let unicornReactedClass = '';
let bookmarkReactedClass = '';
@ -167,15 +180,21 @@ export default class Article extends Component {
src={article.user.profile_image_90}
alt={article.user.username}
/>
<span>{article.user.name} </span>
<span>
{article.user.name}
{' '}
</span>
<span className="published-at">
{' '}
| {article.readable_publish_date}
|
{' '}
{article.readable_publish_date}
</span>
</a>
</h3>
</div>
<div className="body">
{/* eslint-disable-next-line react/no-danger */}
<div dangerouslySetInnerHTML={{ __html: article.body_html }} />
</div>
</div>

View file

@ -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 = (
<span className="channel__member">
is already in <em>{channel.channel_name}</em>
is already in
{' '}
<em>{channel.channel_name}</em>
</span>
);
}
return (
<div className="channeldetails__searchedusers">
<a href={user.path} target="_blank" rel="noopener noreferrer">
<img src={user.user.profile_image_90} alt="profile_image" />@
{user.user.username} - {user.title}
</a>{' '}
<img src={user.user.profile_image_90} alt="profile_image" />
@
{user.user.username}
{' '}
-
{/* prettier-ignore */}
{' '}
{user.title}
</a>
{' '}
{invite}
</div>
);
@ -167,7 +177,12 @@ class ChannelDetails extends Component {
rel="noopener noreferrer"
data-content={`users/${user.id}`}
>
@{user.username} - {user.name}
@
{user.username}
{' '}
-
{' '}
{user.name}
</a>
</div>
));
@ -188,7 +203,8 @@ class ChannelDetails extends Component {
<div className="channeldetails__leftchannel">
<h2>Danger Zone</h2>
<h3>
You have left this channel{' '}
You have left this channel
{' '}
<span role="img" aria-label="emoji">
😢😢😢
</span>

View file

@ -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 = ({
<div className="chatchannels__channelslistheader">
<span role="img" aria-label="emoji">
👋
</span>{' '}
</span>
{' '}
Welcome to
<b> DEV Connect</b>! You may message anyone you mutually follow.
<b> DEV Connect</b>
! You may message anyone you mutually follow.
</div>
);
}
@ -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;

View file

@ -663,6 +663,7 @@ export default class Chat extends Component {
};
handleFailure = err => {
// eslint-disable-next-line no-console
console.error(err);
};

View file

@ -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 => {

View file

@ -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"
/>
<button
type="button"
className="messagecomposer__submit"
onClick={handleSubmitOnClick}
>

View file

@ -4,6 +4,13 @@ import marked from 'marked';
import { getJSONContents } from './actions';
export default class GithubRepo extends Component {
static propTypes = {
githubToken: PropTypes.string.isRequired,
resource: PropTypes.shape({
args: PropTypes.string,
}).isRequired,
};
constructor(props) {
super(props);
this.state = {
@ -18,25 +25,28 @@ export default class GithubRepo extends Component {
}
componentDidMount() {
if (this.state.token) {
const { token } = this.state;
const { resource } = this.props;
if (token) {
getJSONContents(
`https://api.github.com/repos/${this.props.resource.args}/contents?access_token=${this.state.token}`,
`https://api.github.com/repos/${resource.args}/contents?access_token=${token}`,
this.loadContent,
this.loadFailure,
);
getJSONContents(
`https://api.github.com/repos/${this.props.resource.args}/readme?access_token=${this.state.token}`,
`https://api.github.com/repos/${resource.args}/readme?access_token=${token}`,
this.loadContent,
this.loadFailure,
);
}
this.setState({ path: this.props.resource.args });
this.setState({ path: resource.args });
}
handleItemClick = e => {
const { token } = this.state;
e.preventDefault();
getJSONContents(
`${e.target.dataset.apiUrl}&access_token=${this.state.token}`,
`${e.target.dataset.apiUrl}&access_token=${token}`,
this.loadContent,
this.loadFailure,
);
@ -62,7 +72,6 @@ export default class GithubRepo extends Component {
this.setState({
files,
directories,
response,
});
} else if (response.path === 'README.md') {
this.setState({
@ -71,7 +80,6 @@ export default class GithubRepo extends Component {
} else if (response.content) {
this.setState({
content: window.atob(response.content),
response,
});
}
};
@ -81,7 +89,16 @@ export default class GithubRepo extends Component {
};
render() {
if (!this.state.token || this.state.token.length === 0) {
const {
token,
content,
path,
directories: directoriesFromState,
files: filesFromState,
readme: readmeFromState,
root,
} = this.state;
if (!token || token.length === 0) {
return (
<div className="activecontent__githubrepo">
<div className="activecontent__githubrepoheader">
@ -91,17 +108,15 @@ export default class GithubRepo extends Component {
</div>
);
}
if (this.state.content) {
if (content) {
return (
<div className="activecontent__githubrepo">
<div className="activecontent__githubrepoheader">
{this.state.path}
</div>
<pre>{this.state.content}</pre>
<div className="activecontent__githubrepoheader">{path}</div>
<pre>{content}</pre>
</div>
);
}
const directories = this.state.directories.map(item => (
const directories = directoriesFromState.map(item => (
<div className="activecontent__githubrepofilerow">
<a
href={item.html_url}
@ -109,11 +124,15 @@ export default class GithubRepo extends Component {
data-path={item.path}
onClick={this.handleItemClick}
>
📁 {item.name}
<span role="img" aria-label="folder-emoji">
📁
</span>
{' '}
{item.name}
</a>
</div>
));
const files = this.state.files.map(item => (
const files = filesFromState.map(item => (
<div className="activecontent__githubrepofilerow">
<a
href={item.html_url}
@ -126,22 +145,24 @@ export default class GithubRepo extends Component {
</div>
));
let readme = '';
if (this.state.readme) {
if (readmeFromState) {
readme = (
<div
className="activecontent__githubreporeadme"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: marked(this.state.readme),
__html: marked(readmeFromState),
}}
/>
);
}
if (this.state.root) {
if (root) {
return (
<div className="activecontent__githubrepo">
<div className="activecontent__githubrepoheader">
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a href="/Users/benhalpern/dev/dev.to_core/app" />
{this.state.path}
{path}
</div>
<div className="activecontent__githubrepofiles activecontent__githubrepofiles--root">
{directories}
@ -153,7 +174,7 @@ export default class GithubRepo extends Component {
}
return (
<div className="activecontent__githubrepo">
<div className="activecontent__githubrepoheader">{this.state.path}</div>
<div className="activecontent__githubrepoheader">{path}</div>
<div className="activecontent__githubrepofiles">
{directories}
{files}

View file

@ -22,6 +22,7 @@ const Message = ({
const messageArea = (
<span
className="chatmessagebody__message"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: message }}
/>
);
@ -32,10 +33,12 @@ const Message = ({
<a
href={`/${user}`}
target="_blank"
rel="noopener noreferrer"
data-content={`users/${userID}`}
onClick={onContentTrigger}
>
<img
role="presentation"
className="chatmessagebody__profileimage"
src={profileImageUrl}
alt={`${user} profile`}
@ -44,12 +47,17 @@ const Message = ({
/>
</a>
</div>
<div className="chatmessage__body" onClick={onContentTrigger}>
<div
role="presentation"
className="chatmessage__body"
onClick={onContentTrigger}
>
<span className="chatmessagebody__username" style={spanStyle}>
<a
className="chatmessagebody__username--link"
href={`/${user}`}
target="_blank"
rel="noopener noreferrer"
data-content={`users/${userID}`}
onClick={onContentTrigger}
>
@ -77,7 +85,7 @@ Message.propTypes = {
type: PropTypes.string,
timestamp: PropTypes.string,
profileImageUrl: PropTypes.string,
onContentTrigger: PropTypes.func,
onContentTrigger: PropTypes.func.isRequired,
};
Message.defaultProps = {

View file

@ -6,7 +6,7 @@ const ErrorMessage = ({ message }) => {
return (
<div className="chatmessage">
<span className="chatmessage__body" style={errorStyle}>
{'Sorry '}
Sorry
<span className="chatmessagebody__currentuser">
{`@${window.currentUser.username}`}
</span>

View file

@ -2,7 +2,33 @@ import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import { getTwilioToken } from './actions';
/**
* TODO: Instead of calling this function in render, use jsx (<VideoControlButton />).
*/
function VideoControlButton({ btnClassName, btnClickCallback, btnLabel }) {
return (
<button type="button" className={btnClassName} onClick={btnClickCallback}>
{btnLabel}
</button>
);
}
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 (
<div
className="chat__videocall"
id="chat__videocall"
draggable="true"
onDrag={this.handleDrag}
style={{ left: `${this.state.leftPx}px`, top: `${this.state.topPx}px` }}
style={{ left: `${leftPx}px`, top: `${topPx}px` }}
>
<div
id="videoremotescreen"
className={`chat__remotevideoscreen-${this.state.participants.length}`}
className={`chat__remotevideoscreen-${participants.length}`}
/>
<div className="chat__localvideoscren" id="videolocalscreen" />
<button
className="chat__videocallexitbutton"
onClick={this.props.onExit}
>
×
</button>
<button
className="chat__videocallcontrolbutton"
onClick={this.toggleSound}
>
{this.props.soundOn ? 'Mute' : 'UnMute'}
</button>
<button
className="chat__videocallcontrolbutton chat__videocallcontrolbutton--videoonoff"
onClick={this.toggleVideo}
>
{this.props.videoOn ? 'Turn Off Video' : 'Turn On Video'}
</button>
{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',
})}
</div>
);
}

View file

@ -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 (<SetupButton />).
*/
function SetupButton({ className, onClickCallback, dataContent, btnLabel }) {
return (
<button
type="button"
className={className}
onClick={onClickCallback}
data-content={dataContent}
>
{btnLabel}
</button>
)
);
}
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 (
<div className="chatNonChatView_contentblock">
<h2>{props.channel.channel_name}</h2>
<div>
<em>{props.channel.description}</em>
</div>
{
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',
})}
</div>
);
};
render() {
const channels = this.props.channels.map(channel => {
return <this.channel channel={channel} />
const { onViewExit, channels: channelsFromProps } = this.props;
const channels = channelsFromProps.map(channel => {
return <this.channel channel={channel} />;
});
return (
<div className="chatNonChatView">
<div className="container">
{
setupButton({
className: 'chatNonChatView_exitbutton',
onClickCallback: this.props.onViewExit,
dataContent: 'exit',
btnLabel: '×'
})
}
<h1>Channel Invitations 🤗</h1>
{SetupButton({
className: 'chatNonChatView_exitbutton',
onClickCallback: onViewExit,
dataContent: 'exit',
btnLabel: '×',
})}
<h1>
Channel Invitations
{' '}
<span role="img" aria-label="hugging-emoji">
🤗
</span>
</h1>
{channels}
</div>
</div>