Fix eslint lints in chat's jsx files (#4302) [deploy]
This commit is contained in:
parent
85d8f52789
commit
76e099599b
20 changed files with 308 additions and 137 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ exports[`<Chat /> should load chat 1`] = `
|
|||
<button
|
||||
class="messagecomposer__submit"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
SEND
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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' };
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -663,6 +663,7 @@ export default class Chat extends Component {
|
|||
};
|
||||
|
||||
handleFailure = err => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue