Use StimulusJS for vomit confirmations in Internal (#7255)

This PR implements the async vomit-reaction functionality using StimulusJS. Prior to this, the JS lived in `_abuse-reports.html.erb`.

Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
This commit is contained in:
Arit Amana 2020-04-15 15:26:10 -04:00 committed by GitHub
parent fd9fdebc0b
commit 1d1a8456a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 89 deletions

View file

@ -88,7 +88,6 @@
input {
outline: 0;
}
}
.chat__channels--expanded {
@ -172,7 +171,6 @@
}
}
.chat_chatconfig {
position: absolute;
bottom: 0px;
@ -367,7 +365,7 @@
@media screen and (max-width: 426px) {
width: calc(100% - 16px);
min-width: calc(100% - 16px);
}
}
iframe {
position: absolute;
top: 0;
@ -396,7 +394,8 @@
max-width: 52%;
}
.chat--content-visible .activechatchannel__activecontent.activechatchannel__activecontent--video {
.chat--content-visible
.activechatchannel__activecontent.activechatchannel__activecontent--video {
width: 180px;
min-width: 180px;
}
@ -446,7 +445,6 @@
}
}
.activechatchannel__activecontentexitbutton {
font-size: 30px;
position: absolute;
@ -600,13 +598,11 @@
border: 1px solid var(--base-30);
}
.chatchanneltab--new {
border: 1px solid var(--accent-brand);
color: var(--accent-brand-darker);
}
.chatchanneltab--video {
background: lighten($green, 10%);
animation: small-pulser 0.5s linear infinite;
@ -718,7 +714,7 @@
display: flex;
max-width: 100%;
@media not all and (pointer: coarse) {
&:hover {
&:hover {
@include themeable(
background,
theme-container-background-hover,

View file

@ -1,3 +1,4 @@
/* eslint-disable consistent-return,no-unused-vars,react/destructuring-assignment,react/no-access-state-in-setstate,react/button-has-type */
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import ConfigImage from '../../assets/images/three-dots.svg';
@ -79,16 +80,11 @@ export default class Chat extends Component {
channelUsers: [],
showMemberlist: false,
memberFilterQuery: null,
rerenderIfUnchangedCheck: null
rerenderIfUnchangedCheck: null,
};
getAllMessages(chatOptions.activeChannelId, 0, this.receiveAllMessages);
}
shouldComponentUpdate(nextProps, nextState) {
if(this.state.rerenderIfUnchangedCheck !== nextState.rerenderIfUnchangedCheck) {
return false;
}
}
componentDidMount() {
const {
chatChannels,
@ -144,6 +140,14 @@ export default class Chat extends Component {
getChannelInvites(this.handleChannelInvites, null);
}
shouldComponentUpdate(nextProps, nextState) {
if (
this.state.rerenderIfUnchangedCheck !== nextState.rerenderIfUnchangedCheck
) {
return false;
}
}
componentDidUpdate() {
const { scrolled, currentMessageLocation } = this.state;
const messageList = document.getElementById('messagelist');
@ -344,7 +348,10 @@ export default class Chat extends Component {
if (entry.isIntersecting && this.state.scrolled === true) {
this.setState({ scrolled: false, showAlert: false });
} else if (this.state.scrolled === false) {
this.setState({ scrolled: true, rerenderIfUnchangedCheck: Math.random() });
this.setState({
scrolled: true,
rerenderIfUnchangedCheck: Math.random(),
});
}
});
};
@ -601,7 +608,7 @@ export default class Chat extends Component {
message: '/call',
mentionedUsersId: this.getMentionedUsers(message),
};
this.setState({ videoPath: `/video_chats/${ activeChannelId}` });
this.setState({ videoPath: `/video_chats/${activeChannelId}` });
sendMessage(messageObject, this.handleSuccess, this.handleFailure);
} else if (message.startsWith('/new')) {
this.setActiveContentState(activeChannelId, {
@ -816,10 +823,11 @@ export default class Chat extends Component {
});
} else if (target.dataset.content === 'exit') {
this.setActiveContentState(activeChannelId, null);
this.setState({fullscreenContent: null});
this.setState({ fullscreenContent: null });
} else if (target.dataset.content === 'fullscreen') {
const mode = this.state.fullscreenContent === 'sidecar' ? null : 'sidecar'
this.setState({fullscreenContent: mode})
const mode =
this.state.fullscreenContent === 'sidecar' ? null : 'sidecar';
this.setState({ fullscreenContent: mode });
}
}
document.getElementById('messageform').focus();
@ -1018,12 +1026,12 @@ export default class Chat extends Component {
toggleSearchShowing = () => {
if (!this.state.searchShowing) {
setTimeout(function(){
document.getElementById("chatchannelsearchbar").focus()
},100)
setTimeout(function () {
document.getElementById('chatchannelsearchbar').focus();
}, 100);
}
this.setState({searchShowing: !this.state.searchShowing})
}
this.setState({ searchShowing: !this.state.searchShowing });
};
renderChatChannels = () => {
const { state } = this;
@ -1074,10 +1082,32 @@ export default class Chat extends Component {
>
{'<'}
</button>
{state.searchShowing ? <input placeholder="Search Channels" onKeyUp={this.debouncedChannelFilter} id="chatchannelsearchbar" className="crayons-textfield" /> : ''}
{state.searchShowing ? (
<input
placeholder="Search Channels"
onKeyUp={this.debouncedChannelFilter}
id="chatchannelsearchbar"
className="crayons-textfield"
/>
) : (
''
)}
{invitesButton}
<div className="chat__channeltypefilter">
<button className="chat__channelssearchtoggle" onClick={this.toggleSearchShowing}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="17" height="17"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z"/></svg></button>
<button
className="chat__channelssearchtoggle"
onClick={this.toggleSearchShowing}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="17"
height="17"
>
<path fill="none" d="M0 0h24v24H0z" />
<path d="M18.031 16.617l4.283 4.282-1.415 1.415-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9 9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617zm-2.006-.742A6.977 6.977 0 0 0 18 11c0-3.868-3.133-7-7-7-3.868 0-7 3.132-7 7 0 3.867 3.132 7 7 7a6.977 6.977 0 0 0 4.875-1.975l.15-.15z" />
</svg>
</button>
{this.renderChannelFilterButton(
'all',
'all',
@ -1145,7 +1175,7 @@ export default class Chat extends Component {
}
const jumpbackButton = document.getElementById('jumpback_button');
if (this.scroller) {
const scrolledRatio =
(this.scroller.scrollTop + this.scroller.clientHeight) /
@ -1264,7 +1294,7 @@ export default class Chat extends Component {
);
};
onTriggerVideoContent = e => {
onTriggerVideoContent = (e) => {
if (e.target.dataset.content === 'exit') {
this.setState({ videoPath: null, fullscreenContent: null });
} else if (this.state.fullscreenContent === 'video') {
@ -1578,9 +1608,9 @@ export default class Chat extends Component {
}
let fullscreenMode = '';
if (state.fullscreenContent === 'sidecar') {
fullscreenMode = 'chat--content-visible-full'
fullscreenMode = 'chat--content-visible-full';
} else if (state.fullscreenContent === 'video') {
fullscreenMode = 'chat--video-visible-full'
fullscreenMode = 'chat--video-visible-full';
}
return (
<div

View file

@ -0,0 +1,70 @@
/* eslint-disable no-alert */
import { Controller } from 'stimulus';
export default class ReactionController extends Controller {
static targets = ['invalid', 'confirmed'];
// eslint-disable-next-line class-methods-use-this
/* eslint no-alert: "error" */
updateReaction(id, status) {
fetch(`/internal/reactions/${id}`, {
method: 'PATCH',
headers: {
Accept: 'application/json',
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']")
.content,
'Content-Type': 'application/json',
},
body: JSON.stringify({
id,
status,
}),
credentials: 'same-origin',
}).then((response) =>
response
.json()
.then((json) => {
if (json.outcome === 'Success') {
this.element.remove();
document.getElementById(`js__reaction__div__hr__${id}`).remove();
} else {
alert(json.error);
}
})
.catch((error) => {
alert(error);
}),
);
}
updateReactionInvalid() {
this.updateReaction(this.reactionId, this.invalidStatus);
}
reactableUserCheck() {
if (this.reactableType === 'user') {
// eslint-disable-next-line no-restricted-globals
if (confirm('You are confirming a User vomit reaction; are you sure?')) {
this.updateReaction(this.reactionId, this.confirmedStatus);
}
} else {
this.updateReaction(this.reactionId, this.confirmedStatus);
}
}
get reactionId() {
return parseInt(this.data.get('id'), 10);
}
get confirmedStatus() {
return this.confirmedTarget.dataset.status;
}
get reactableType() {
return this.confirmedTarget.dataset.reactable;
}
get invalidStatus() {
return this.invalidTarget.dataset.altstatus;
}
}

View file

@ -27,7 +27,7 @@
<div class="card-body" style="overflow: scroll; max-height: 500px;">
<% @vomits.each do |reaction| %>
<% next if (reaction.reactable_type == "Article" && !reaction.reactable.published) || (reaction.reactable_type == "User" && reaction.reactable&.banished?) %>
<div class="d-flex justify-content-between" id="js__reaction__div__<%= reaction.id %>">
<div class="d-flex justify-content-between" data-controller="reaction" data-reaction-id="<%= reaction.id %>">
<span>
🤢 <a href="<%= reaction.user.path %>">@<%= reaction.user.username %></a>
</span>
@ -45,28 +45,31 @@
<% if params[:status] == "Open" || params[:status].blank? %>
<% if reaction.reactable_type == "User" %>
<button
class="btn btn-success btn-sm js-reaction-confirm"
class="btn btn-success btn-sm"
type="button"
data-reactable="user"
data-id="<%= reaction.id %>"
data-status="confirmed">
data-status="confirmed"
data-target="reaction.confirmed"
data-action="reaction#reactableUserCheck">
CONFIRM
</button>
<% else %>
<button
class="btn btn-success btn-sm js-reaction-confirm"
class="btn btn-success btn-sm"
type="button"
data-reactable="not-user"
data-id="<%= reaction.id %>"
data-status="confirmed">
data-reactable="non-user"
data-status="confirmed"
data-target="reaction.confirmed"
data-action="reaction#reactableUserCheck">
CONFIRM
</button>
<% end %>
<button
class="btn btn-danger btn-sm js-reaction-confirm"
class="btn btn-danger btn-sm"
type="button"
data-id="<%= reaction.id %>"
data-status="invalid">
data-altstatus="invalid"
data-target="reaction.invalid"
data-action="reaction#updateReactionInvalid">
INVALID
</button>
<% end %>
@ -78,49 +81,3 @@
</div>
</div>
</div>
<script>
function removeReactionDiv(id){
document.getElementById(`js__reaction__div__${id}`).remove();
document.getElementById(`js__reaction__div__hr__${id}`).remove();
}
function updateReactionStatus(id, status){
fetch(`/internal/reactions/${id}`, {
method: 'PATCH',
headers: {
Accept: 'application/json',
'X-CSRF-Token': document.querySelector("meta[name='csrf-token']").content,
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: id,
status: status,
}),
credentials: 'same-origin'
})
.then(response => response.json()
.then(json => {
if (json.outcome === 'Success') {
removeReactionDiv(id)
} else {
alert(json.error)
}
})
.catch(error => {
alert(error);
}))
}
document.querySelectorAll('.js-reaction-confirm').forEach(item => {
item.addEventListener('click', function () {
if (item.dataset.reactable === 'user') {
if (confirm("You are confirming a reaction for a User; are you sure?")){
updateReactionStatus(item.dataset.id, item.dataset.status);
}
} else {
updateReactionStatus(item.dataset.id, item.dataset.status);
}
});
});
</script>

View file

@ -24,7 +24,6 @@
margin-bottom: 100px;
}
#local-media video {
width: 150px;
position: fixed;
@ -32,7 +31,7 @@
right: 10px;
z-index: 5;
}
#local-media.video-hidden {
opacity: 0.1;
}