Remove experimental /call functionality from Connect ✂️✂️✂️ (#12979)
* Remove Twilio gem * remove VideoChatsController + views and specs * Remove /call related code * Update spec * More JS cleanup * Restore wrongly deleted files * Remove obsolete files * More cleanup
This commit is contained in:
parent
5bd87b93d6
commit
7c7a8dcd45
20 changed files with 8 additions and 724 deletions
1
Gemfile
1
Gemfile
|
|
@ -108,7 +108,6 @@ gem "store_attribute", "~> 0.8.1" # ActiveRecord extension which adds typecastin
|
|||
gem "storext", "~> 3.3" # Add type-casting and other features on top of ActiveRecord::Store.store_accessor
|
||||
gem "stripe", "~> 5.30" # Ruby library for the Stripe API
|
||||
gem "strong_migrations", "~> 0.7" # Catch unsafe migrations
|
||||
gem "twilio-ruby", "~> 5.48" # The official library for communicating with the Twilio REST API
|
||||
gem "twitter", "~> 7.0" # A Ruby interface to the Twitter API
|
||||
gem "uglifier", "~> 4.2" # Uglifier minifies JavaScript files
|
||||
gem "ulid", "~> 1.3" # Universally Unique Lexicographically Sortable Identifier implementation for Ruby
|
||||
|
|
|
|||
|
|
@ -808,10 +808,6 @@ GEM
|
|||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
timecop (0.9.4)
|
||||
twilio-ruby (5.48.0)
|
||||
faraday (>= 0.9, < 2.0)
|
||||
jwt (>= 1.5, <= 2.5)
|
||||
nokogiri (>= 1.6, < 2.0)
|
||||
twitter (7.0.0)
|
||||
addressable (~> 2.3)
|
||||
buftok (~> 0.2.0)
|
||||
|
|
@ -1028,7 +1024,6 @@ DEPENDENCIES
|
|||
strong_migrations (~> 0.7)
|
||||
test-prof (~> 1.0)
|
||||
timecop (~> 0.9)
|
||||
twilio-ruby (~> 5.48)
|
||||
twitter (~> 7.0)
|
||||
uglifier (~> 4.2)
|
||||
ulid (~> 1.3)
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
class TwilioTokensController < ApplicationController
|
||||
after_action :verify_authorized
|
||||
|
||||
def show
|
||||
video_channel = params[:id].split("private-video-channel-")[1] if params[:id].start_with?("private-video-channel-")
|
||||
unless video_channel
|
||||
skip_authorization
|
||||
render json: { status: "failure", token: @twilio_token }, status: :not_found
|
||||
return
|
||||
end
|
||||
@chat_channel = ChatChannel.find(video_channel.to_i)
|
||||
authorize @chat_channel # show pundit method for chat_channel_policy works here, should always check though
|
||||
@twilio_token = Twilio::GetJwtToken.call(current_user, params[:id])
|
||||
render json: { status: "success", token: @twilio_token }, status: :ok
|
||||
end
|
||||
end
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
class VideoChatsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
after_action :verify_authorized
|
||||
|
||||
def show
|
||||
@chat_channel = ChatChannel.find(params[:id]) || not_found
|
||||
authorize @chat_channel
|
||||
|
||||
account_sid = ApplicationConfig["TWILIO_ACCOUNT_SID"]
|
||||
api_key = ApplicationConfig["TWILIO_VIDEO_API_KEY"]
|
||||
api_secret = ApplicationConfig["TWILIO_VIDEO_API_SECRET"]
|
||||
@username = display_username
|
||||
@video_type = video_type
|
||||
token = Twilio::JWT::AccessToken.new(
|
||||
account_sid,
|
||||
api_key,
|
||||
api_secret,
|
||||
[],
|
||||
identity: @username,
|
||||
)
|
||||
|
||||
grant = Twilio::JWT::AccessToken::VideoGrant.new
|
||||
grant.room = params[:id]
|
||||
token.add_grant(grant)
|
||||
|
||||
@token = token.to_jwt
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def display_username
|
||||
return "@#{params[:username]}" if params[:username] && Rails.env.development? # simpler solo testing in dev
|
||||
|
||||
"@#{current_user.username}"
|
||||
end
|
||||
|
||||
def video_type
|
||||
if @chat_channel.channel_type == "direct" || @chat_channel.chat_channel_memberships.size < 5
|
||||
"peer-to-peer"
|
||||
else
|
||||
"group"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
import { h } from 'preact';
|
||||
import { axe } from 'jest-axe';
|
||||
import { render } from '@testing-library/preact';
|
||||
import { VideoContent } from '../videoContent';
|
||||
|
||||
describe('<VideoContent />', () => {
|
||||
it('should have no a11y violations', async () => {
|
||||
const { container } = render(
|
||||
<VideoContent
|
||||
videoPath="/some-video-path"
|
||||
fullscreen="false"
|
||||
onTriggerVideoContent={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const results = await axe(container);
|
||||
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it('should render in fullscreen', () => {
|
||||
const { queryByLabelText } = render(
|
||||
<VideoContent
|
||||
videoPath="/some-video-path"
|
||||
fullscreen
|
||||
onTriggerVideoContent={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByLabelText('Leave fullscreen')).toBeNull();
|
||||
expect(queryByLabelText('Fullscreen')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not render in fullscreen', () => {
|
||||
const { queryByLabelText } = render(
|
||||
<VideoContent
|
||||
videoPath="/some-video-path"
|
||||
fullscreen={false}
|
||||
onTriggerVideoContent={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByLabelText('Fullscreen')).toBeNull();
|
||||
expect(queryByLabelText('Leave fullscreen')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should trigger video content when clicked', () => {
|
||||
const onTriggerVideoContent = jest.fn();
|
||||
|
||||
const { getByTestId } = render(
|
||||
<VideoContent
|
||||
videoPath="/some-video-path"
|
||||
fullscreen
|
||||
onTriggerVideoContent={onTriggerVideoContent}
|
||||
/>,
|
||||
);
|
||||
|
||||
getByTestId('connect-video').click();
|
||||
|
||||
expect(onTriggerVideoContent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should load the given video', () => {
|
||||
const onTriggerVideoContent = jest.fn();
|
||||
|
||||
const { getByTitle } = render(
|
||||
<VideoContent
|
||||
videoPath="/some-video-path"
|
||||
fullscreen
|
||||
onTriggerVideoContent={onTriggerVideoContent}
|
||||
/>,
|
||||
);
|
||||
|
||||
const videoFrame = getByTitle('Video display');
|
||||
|
||||
expect(videoFrame.getAttribute('src')).toEqual('/some-video-path');
|
||||
});
|
||||
});
|
||||
|
|
@ -36,7 +36,6 @@ import { Compose } from './compose';
|
|||
import { Message } from './message';
|
||||
import { ActionMessage } from './actionMessage';
|
||||
import { Content } from './content';
|
||||
import { VideoContent } from './videoContent';
|
||||
import { DragAndDropZone } from '@utilities/dragAndDrop';
|
||||
import { dragAndUpload } from '@utilities/dragAndUpload';
|
||||
import { Button } from '@crayons';
|
||||
|
|
@ -83,7 +82,6 @@ export class Chat extends Component {
|
|||
notificationsPermission: null,
|
||||
activeContent: {},
|
||||
fullscreenContent: null,
|
||||
videoPath: null,
|
||||
expanded: window.innerWidth > NARROW_WIDTH_LIMIT,
|
||||
isMobileDevice: typeof window.orientation !== 'undefined',
|
||||
subscribedPusherChannels: [],
|
||||
|
|
@ -715,14 +713,6 @@ export class Chat extends Component {
|
|||
// should check if user has the privilege
|
||||
if (message.startsWith('/code')) {
|
||||
this.setActiveContentState(activeChannelId, { type_of: 'code_editor' });
|
||||
} else if (message.startsWith('/call')) {
|
||||
const messageObject = {
|
||||
activeChannelId,
|
||||
message: '/call',
|
||||
mentionedUsersId: this.getMentionedUsers(message),
|
||||
};
|
||||
this.setState({ videoPath: `/video_chats/${activeChannelId}` });
|
||||
sendMessage(messageObject, this.handleSuccess, this.handleFailure);
|
||||
} else if (message.startsWith('/play ')) {
|
||||
const messageObject = {
|
||||
activeChannelId,
|
||||
|
|
@ -1008,17 +998,6 @@ export class Chat extends Component {
|
|||
path: `/chat_channel_memberships/${activeChannel.id}/edit`,
|
||||
type_of: 'article',
|
||||
});
|
||||
} else if (content.startsWith('sidecar-content-plus-video')) {
|
||||
this.setActiveContentState(activeChannelId, {
|
||||
type_of: 'loading-post',
|
||||
});
|
||||
this.setActiveContent({
|
||||
path: target.href || target.parentElement.href,
|
||||
type_of: 'article',
|
||||
});
|
||||
this.setState({ videoPath: `/video_chats/${activeChannelId}` });
|
||||
} else if (content.startsWith('sidecar-video')) {
|
||||
this.setState({ videoPath: target.href || target.parentElement.href });
|
||||
} else if (
|
||||
content.startsWith('sidecar') ||
|
||||
content.startsWith('article')
|
||||
|
|
@ -1596,11 +1575,6 @@ export class Chat extends Component {
|
|||
fullscreen={state.fullscreenContent === 'sidecar'}
|
||||
closeReportAbuseForm={this.closeReportAbuseForm}
|
||||
/>
|
||||
<VideoContent
|
||||
videoPath={state.videoPath}
|
||||
onTriggerVideoContent={this.onTriggerVideoContent}
|
||||
fullscreen={state.fullscreenContent === 'video'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1626,23 +1600,6 @@ export class Chat extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
onTriggerVideoContent = (e) => {
|
||||
if (e.target.dataset.content === 'exit') {
|
||||
this.setState({
|
||||
videoPath: null,
|
||||
fullscreenContent: null,
|
||||
expanded: window.innerWidth > 600,
|
||||
});
|
||||
} else if (this.state.fullscreenContent === 'video') {
|
||||
this.setState({ fullscreenContent: null });
|
||||
} else {
|
||||
this.setState({
|
||||
fullscreenContent: 'video',
|
||||
expanded: window.innerWidth > WIDE_WIDTH_LIMIT,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleMention = (e) => {
|
||||
const { activeChannel } = this.state;
|
||||
const mention = e.keyCode === 64;
|
||||
|
|
@ -2010,12 +1967,10 @@ export class Chat extends Component {
|
|||
data-testid="chat"
|
||||
className={`chat chat--expanded
|
||||
chat--${
|
||||
state.videoPath ? 'video-visible' : 'video-not-visible'
|
||||
} chat--${
|
||||
state.activeContent[state.activeChannelId]
|
||||
? 'content-visible'
|
||||
: 'content-not-visible'
|
||||
} ${fullscreenMode}`}
|
||||
state.activeContent[state.activeChannelId]
|
||||
? 'content-visible'
|
||||
: 'content-not-visible'
|
||||
} ${fullscreenMode}`}
|
||||
data-no-instant
|
||||
aria-expanded={state.expanded}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const smartSvgIcon = (content, d) => (
|
|||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
style={{ marginLeft:'-12px', marginTop:'-4px' }}
|
||||
style={{ marginLeft: '-12px', marginTop: '-4px' }}
|
||||
>
|
||||
<path data-content={content} fill="none" d="M0 0h24v24H0z" />
|
||||
<path data-content={content} d={d} />
|
||||
|
|
@ -75,7 +75,7 @@ export class Content extends Component {
|
|||
type="button"
|
||||
className="activechatchannel__activecontentexitbutton activechatchannel__activecontentexitbutton--fullscreen crayons-btn crayons-btn--secondary"
|
||||
data-content="fullscreen"
|
||||
style={{ left: "-80px", marginLeft: "0px" }}
|
||||
style={{ left: '-80px', marginLeft: '0px' }}
|
||||
title="fullscreen"
|
||||
>
|
||||
{' '}
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
import { h } from 'preact';
|
||||
|
||||
const smartSvgIcon = (content, d) => (
|
||||
<svg
|
||||
data-content={content}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
|
||||
>
|
||||
<path data-content={content} fill="none" d="M0 0h24v24H0z" />
|
||||
<path data-content={content} d={d} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
/**
|
||||
* Displays video in Connect.
|
||||
*
|
||||
* @param {string} props.videoPath The URL of a video
|
||||
* @param {function} props.onTriggerVideoContent Fires when a video is clicked on
|
||||
* @param {boolean} props.fullscreen Whether or not to show the video in fullscreen
|
||||
*
|
||||
* @returns A video component
|
||||
*/
|
||||
export function VideoContent(props) {
|
||||
const { videoPath, onTriggerVideoContent, fullscreen } = props;
|
||||
|
||||
if (!videoPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="connect-video"
|
||||
role="presentation"
|
||||
className="activechatchannel__activecontent activechatchannel__activecontent--video"
|
||||
id="chat_activecontent_video"
|
||||
onClick={onTriggerVideoContent}
|
||||
>
|
||||
<button
|
||||
aria-label="Exit"
|
||||
className="activechatchannel__activecontentexitbutton crayons-btn crayons-btn--secondary"
|
||||
data-content="exit"
|
||||
>
|
||||
{smartSvgIcon(
|
||||
'exit',
|
||||
'M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z',
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
aria-label={fullscreen ? 'Fullscreen' : 'Leave fullscreen'}
|
||||
className="activechatchannel__activecontentexitbutton activechatchannel__activecontentexitbutton--fullscreen crayons-btn crayons-btn--secondary"
|
||||
data-content="fullscreen"
|
||||
style={{ left: '-80px', marginLeft:'0px' }}
|
||||
>
|
||||
{fullscreen
|
||||
? smartSvgIcon(
|
||||
'fullscreen',
|
||||
'M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z',
|
||||
)
|
||||
: smartSvgIcon(
|
||||
'fullscreen',
|
||||
'M20 3h2v6h-2V5h-4V3h4zM4 3h4v2H4v4H2V3h2zm16 16v-4h2v6h-6v-2h4zM4 19h4v2H2v-6h2v4z',
|
||||
)}
|
||||
</button>
|
||||
<iframe title="Video display" src={videoPath} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
const { createLocalVideoTrack, connect } = require('twilio-video');
|
||||
const root = document.getElementById('videochat');
|
||||
const remoteMedia = document.getElementById('remote-media');
|
||||
const muteButton = document.getElementById('mute-toggle');
|
||||
const videoToggleButton = document.getElementById('videohide-toggle');
|
||||
const localMediaContainer = document.getElementById('local-media');
|
||||
const roomType = document.getElementById('room-type').dataset.type; //Group
|
||||
|
||||
connect(root.dataset.token, {
|
||||
name: 'room-name',
|
||||
audio: true,
|
||||
type: roomType,
|
||||
video: { width: 800 },
|
||||
}).then((room) => {
|
||||
room.participants.forEach(participantConnected);
|
||||
room.on('participantConnected', participantConnected);
|
||||
|
||||
room.on('participantDisconnected', participantDisconnected);
|
||||
room.once('disconnected', (_error) =>
|
||||
room.participants.forEach(participantDisconnected),
|
||||
);
|
||||
muteButton.onclick = function (e) {
|
||||
e.preventDefault();
|
||||
room.localParticipant.audioTracks.forEach(function (trackPub) {
|
||||
if (muteButton.dataset.muted === 'true') {
|
||||
muteButton.dataset.muted = 'false';
|
||||
muteButton.classList.remove('crayons-btn--danger');
|
||||
muteButton.innerHTML = 'Mute';
|
||||
trackPub.track.enable();
|
||||
} else {
|
||||
muteButton.dataset.muted = 'true';
|
||||
muteButton.classList.add('crayons-btn--danger');
|
||||
muteButton.innerHTML = 'Unmute';
|
||||
trackPub.track.disable();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
videoToggleButton.onclick = function (e) {
|
||||
e.preventDefault();
|
||||
room.localParticipant.videoTracks.forEach(function (trackPub) {
|
||||
if (videoToggleButton.dataset.hidden === 'true') {
|
||||
videoToggleButton.dataset.hidden = 'false';
|
||||
videoToggleButton.classList.remove('crayons-btn--danger');
|
||||
videoToggleButton.innerHTML = 'Hide';
|
||||
localMediaContainer.classList.remove('video-hidden');
|
||||
trackPub.track.enable();
|
||||
} else {
|
||||
videoToggleButton.dataset.hidden = 'true';
|
||||
videoToggleButton.classList.add('crayons-btn--danger');
|
||||
videoToggleButton.innerHTML = 'Unhide';
|
||||
localMediaContainer.classList.add('video-hidden');
|
||||
trackPub.track.disable();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
createLocalVideoTrack().then((track) => {
|
||||
localMediaContainer.appendChild(track.attach());
|
||||
document.getElementById('video-controls').classList.add('showing');
|
||||
});
|
||||
|
||||
function participantConnected(participant) {
|
||||
const numExistingDivs = document.getElementsByClassName('individual-video')
|
||||
.length;
|
||||
const div = document.createElement('div');
|
||||
div.id = participant.sid;
|
||||
div.className = `individual-video${
|
||||
numExistingDivs > 3 ? ' one-of-many-videos' : ''
|
||||
}`;
|
||||
div.innerHTML = `<div class="participant-info">\
|
||||
<div class="participant-name">${participant.identity}</div>\
|
||||
<div class="disabled-audio-indicator">audio off</div>\
|
||||
<div class="disabled-video-indicator">video off</div>\
|
||||
</div>`;
|
||||
|
||||
participant.on('trackSubscribed', (track) => trackSubscribed(div, track));
|
||||
participant.on('trackUnsubscribed', trackUnsubscribed);
|
||||
|
||||
participant.on('trackDisabled', (track) => trackDisabled(div, track));
|
||||
participant.on('trackEnabled', (track) => trackEnabled(div, track));
|
||||
|
||||
participant.tracks.forEach((publication) => {
|
||||
if (publication.isSubscribed) {
|
||||
trackSubscribed(div, publication.track);
|
||||
}
|
||||
});
|
||||
remoteMedia.appendChild(div);
|
||||
}
|
||||
|
||||
function participantDisconnected(participant) {
|
||||
document.getElementById(participant.sid).remove();
|
||||
}
|
||||
|
||||
function trackSubscribed(div, track) {
|
||||
div.appendChild(track.attach());
|
||||
if (!track.isEnabled) {
|
||||
if (track.kind === 'video') {
|
||||
div.classList.add('disabled-video');
|
||||
} else {
|
||||
div.classList.add('disabled-audio');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function trackUnsubscribed(track) {
|
||||
track.detach().forEach((element) => element.remove());
|
||||
}
|
||||
|
||||
function trackDisabled(div, track) {
|
||||
if (track.kind === 'video') {
|
||||
div.classList.add('disabled-video');
|
||||
} else {
|
||||
div.classList.add('disabled-audio');
|
||||
}
|
||||
}
|
||||
|
||||
function trackEnabled(div, track) {
|
||||
if (track.kind === 'video') {
|
||||
div.classList.remove('disabled-video');
|
||||
} else {
|
||||
div.classList.remove('disabled-audio');
|
||||
}
|
||||
}
|
||||
|
||||
if (navigator.userAgent === 'DEV-Native-ios') {
|
||||
root.innerHTML =
|
||||
'<h2 class="platform-unavailable-message">This feature is not yet available on iPhone</h2>';
|
||||
}
|
||||
|
|
@ -158,14 +158,6 @@ class Message < ApplicationRecord
|
|||
# rubocop:disable Rails/OutputSafety
|
||||
def handle_slash_command(html)
|
||||
response = case html.to_s.strip
|
||||
when "<p>/call</p>"
|
||||
"<a href='/video_chats/#{chat_channel_id}'
|
||||
class='chatchannels__richlink chatchannels__richlink--base'
|
||||
target='_blank' rel='noopener' data-content='sidecar-video'>
|
||||
<h1 data-content='sidecar-video'>
|
||||
Let's video chat 😄
|
||||
</h1>
|
||||
</a>".html_safe
|
||||
when "<p>/play codenames</p>" # proof of concept
|
||||
"<a href='https://www.horsepaste.com/connect-channel-#{rand(1_000_000_000)}'
|
||||
class='chatchannels__richlink chatchannels__richlink--base'
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
module Twilio
|
||||
class GetJwtToken
|
||||
def self.call(user, room_name)
|
||||
token = Twilio::JWT::AccessToken.new(
|
||||
ApplicationConfig["TWILIO_ACCOUNT_SID"],
|
||||
ApplicationConfig["TWILIO_VIDEO_API_KEY"],
|
||||
ApplicationConfig["TWILIO_VIDEO_API_SECRET"],
|
||||
[],
|
||||
identity: user.id,
|
||||
)
|
||||
|
||||
grant = Twilio::JWT::AccessToken::VideoGrant.new
|
||||
grant.room = room_name
|
||||
token.add_grant(grant)
|
||||
|
||||
token.to_jwt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
<style>
|
||||
body {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.video-chat-wrapper {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
overflow: scroll;
|
||||
}
|
||||
.individual-video video {
|
||||
max-height: 100vh;
|
||||
background: #232324;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#remote-media {
|
||||
display:flex;
|
||||
flex-wrap: wrap;
|
||||
/* flex-direction:row; */
|
||||
}
|
||||
|
||||
.individual-video {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin-bottom: -6px;
|
||||
flex: 1 0 21%;
|
||||
text-align: center;
|
||||
min-width:20%;
|
||||
background: #232324;
|
||||
}
|
||||
.one-of-many-videos {
|
||||
min-width:33%;
|
||||
}
|
||||
/* .individual-video:last-child {
|
||||
margin-bottom: 100px;
|
||||
} */
|
||||
|
||||
#local-media video {
|
||||
width: 150px;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
z-index: 5;
|
||||
border-radius:3px;
|
||||
border: 1px solid #232324;
|
||||
}
|
||||
|
||||
#local-media.video-hidden {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.participant-info {
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
.participant-name {
|
||||
display: inline-block;
|
||||
color: white;
|
||||
background: #171717;
|
||||
padding: 4px 12px;
|
||||
border-radius: 100px;
|
||||
margin: 8px;
|
||||
}
|
||||
.video-controls {
|
||||
position: fixed;
|
||||
bottom: 15px;
|
||||
left: 5px;
|
||||
width: calc(100% - 175px);
|
||||
z-index: 10;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.showing {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-controls button {
|
||||
font-size: 15px;
|
||||
width: 100%;
|
||||
margin: 3px 0px;
|
||||
padding: 3px 0px;
|
||||
}
|
||||
|
||||
.disabled-video video {
|
||||
opacity: 0.1;
|
||||
}
|
||||
.disabled-audio-indicator,.disabled-video-indicator {
|
||||
background: red;
|
||||
color: white;
|
||||
display: none;
|
||||
position: absolute;
|
||||
border-radius: 100px;
|
||||
padding: 4px 10px;
|
||||
right: 5px;
|
||||
bottom: 12px;
|
||||
}
|
||||
.disabled-audio-indicator {
|
||||
right: auto;
|
||||
left: 5px;
|
||||
}
|
||||
.disabled-video .disabled-video-indicator{
|
||||
display: inline-block;
|
||||
}
|
||||
.disabled-audio .disabled-audio-indicator{
|
||||
display: inline-block;
|
||||
}
|
||||
.platform-unavailable-message {
|
||||
padding: 20px;
|
||||
color: white;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.individual-video {
|
||||
flex: 1 0 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 280px) {
|
||||
.video-controls {
|
||||
width: 92%;
|
||||
bottom: 5px;
|
||||
}
|
||||
#local-media video {
|
||||
width: 90%;
|
||||
bottom: 85px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
.video-controls {
|
||||
left: auto;
|
||||
right: 170px;
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 400px) {
|
||||
.video-controls {
|
||||
width: 90px;
|
||||
}
|
||||
#local-media video {
|
||||
width: 90px;
|
||||
bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="video-chat-wrapper" id="videochat" data-token="<%= @token %>" data-channel="<%= params[:id] %>" data-user="<%= @username %>">
|
||||
<div id="room-type" data-type="<%= @video_type %>"></div>
|
||||
<div id="remote-media"></div>
|
||||
<div id="local-media"></div>
|
||||
<div id="video-controls" class="video-controls">
|
||||
<button id="mute-toggle" data-muted="false" class="crayons-btn crayons-btn--secondary">Mute</button>
|
||||
<button id="videohide-toggle" data-hidden="false" class="crayons-btn crayons-btn--secondary">Hide</button>
|
||||
</div>
|
||||
<%# TODO Make paramsp[:id] map to a chat channel and ensure the users have permission %>
|
||||
<%# That part is pretty straightforward %>
|
||||
<%# Then close the loop on the JavaScript part of it. %>
|
||||
<%# Also should be *fairly* straightforward in terms of following a basic tutorial. %>
|
||||
<%# Then we drop a link in chat rather than trying to make video work with complicated state in chat. %>
|
||||
<%# The complicated part has always been chat state/channels etc. Doing in sidecar means more containerization of logic. %>
|
||||
<%= javascript_packs_with_chunks_tag "videoChat", defer: true %>
|
||||
|
|
@ -95,7 +95,8 @@ Rails.application.routes.draw do
|
|||
get "/badges/badge_achievements", to: redirect("/admin/badge_achievements")
|
||||
get "/badges/badge_achievements/award_badges", to: redirect("/admin/badge_achievements/award_badges")
|
||||
|
||||
# NOTE: @ridhwana These routes below will be deleted once we remove the admin_restructure feature flag, hence they've been regrouped them in this manner.
|
||||
# NOTE: @ridhwana These routes below will be deleted once we remove the
|
||||
# admin_restructure feature flag, hence they've been regrouped in this manner.
|
||||
resources :articles, only: %i[index show update]
|
||||
resources :badges, only: %i[index edit update new create]
|
||||
resources :badge_achievements, only: %i[index destroy]
|
||||
|
|
|
|||
|
|
@ -177,7 +177,6 @@
|
|||
"pusher-js": "^7.0.3",
|
||||
"rails-erb-loader": "^5.5.2",
|
||||
"stimulus": "^2.0.0",
|
||||
"twilio-video": "^2.13.0",
|
||||
"web-share-wrapper": "^0.2.1"
|
||||
},
|
||||
"resolutions": {
|
||||
|
|
|
|||
|
|
@ -68,13 +68,6 @@ RSpec.describe Message, type: :model do
|
|||
expect(message.message_html).to include("sidecar-user")
|
||||
end
|
||||
|
||||
it "creates rich call link" do
|
||||
message.message_markdown = "/call"
|
||||
message.validate!
|
||||
|
||||
expect(message.message_html).to include("sidecar-video")
|
||||
end
|
||||
|
||||
it "creates rich embeddable link" do
|
||||
message.message_markdown = "https://docs.google.com/ https://www.figma.com/file/"
|
||||
message.validate!
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "TwilioTokens", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:chat_channel) { create(:chat_channel) }
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
describe "GET /twilio_tokens/:id" do
|
||||
it "returns a token for member of a channel" do
|
||||
chat_channel.add_users [user]
|
||||
get "/twilio_tokens/private-video-channel-#{chat_channel.id}"
|
||||
expect(response.status).to eq(200)
|
||||
end
|
||||
|
||||
it "returns not found if unknown ID" do
|
||||
chat_channel.add_users [user]
|
||||
get "/twilio_tokens/sddds3423443efrwdfsd"
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it "returns not found if wrong ID prefix" do
|
||||
chat_channel.add_users [user]
|
||||
get "/twilio_tokens/sddds3423443efrwdfsd-#{chat_channel.id}"
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
# it "returns unauthorized if user not member of channel" do
|
||||
# expect { get "/twilio_tokens/private-video-channel-#{chat_channel.id}" }.
|
||||
# to raise_error(Pundit::NotAuthorizedError)
|
||||
# end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "VideoChats", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:second_user) { create(:user) }
|
||||
let(:third_user) { create(:user) }
|
||||
|
||||
describe "GET /video_chats/:id" do
|
||||
context "with user signed in" do
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
it "displays basic html for working" do
|
||||
channel = ChatChannels::CreateWithUsers.call(users: [user, second_user])
|
||||
get "/video_chats/#{channel.id}"
|
||||
expect(response.body).to include("<div class=\"video-chat-wrapper")
|
||||
end
|
||||
|
||||
it "disallows unauthorized user" do
|
||||
channel = ChatChannels::CreateWithUsers.call(users: [second_user, third_user])
|
||||
expect { get "/video_chats/#{channel.id}" }.to raise_error(Pundit::NotAuthorizedError)
|
||||
end
|
||||
end
|
||||
|
||||
context "without user signed in" do
|
||||
it "asks to sign in" do
|
||||
get "/video_chats/1"
|
||||
expect(response).to redirect_to("/enter")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe Twilio::GetJwtToken, type: :service do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it "returns a token" do
|
||||
expect(described_class.call(user, "hello")).to be_present
|
||||
end
|
||||
end
|
||||
BIN
vendor/cache/twilio-ruby-5.48.0.gem
vendored
BIN
vendor/cache/twilio-ruby-5.48.0.gem
vendored
Binary file not shown.
46
yarn.lock
46
yarn.lock
|
|
@ -3068,11 +3068,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.1.1.tgz#3348564048e7a2d7398c935d466c0414ebb6a669"
|
||||
integrity sha512-Z6DoceYb/1xSg5+e+ZlPZ9v0N16ZvZ+wYMraFue4HYrE4ttONKtsvruIRf6t9TBR0YvSOfi1hUU0fJfBLCDYow==
|
||||
|
||||
"@twilio/webrtc@4.3.2":
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@twilio/webrtc/-/webrtc-4.3.2.tgz#919520a870fdf8d87f915da47276710828e8fc5b"
|
||||
integrity sha512-3gIq7XDI3vBoikRBchCnocFmlRQt45KC7v0DKkat+TV6WBXYJx+Hub22PR18PAROkwl5dAmIziByjYWWVhCHgA==
|
||||
|
||||
"@types/anymatch@*":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
|
||||
|
|
@ -5297,13 +5292,6 @@ babylon@^6.18.0:
|
|||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
|
||||
integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==
|
||||
|
||||
backoff@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f"
|
||||
integrity sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=
|
||||
dependencies:
|
||||
precond "0.2"
|
||||
|
||||
backslash@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/backslash/-/backslash-0.2.0.tgz#6c3c1fce7e7e714ccfc10fd74f0f73410677375f"
|
||||
|
|
@ -15342,11 +15330,6 @@ preact@^10.5.13:
|
|||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.5.13.tgz#85f6c9197ecd736ce8e3bec044d08fd1330fa019"
|
||||
integrity sha512-q/vlKIGNwzTLu+jCcvywgGrt+H/1P/oIRSD6mV4ln3hmlC+Aa34C7yfPI4+5bzW8pONyVXYS7SvXosy2dKKtWQ==
|
||||
|
||||
precond@0.2:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac"
|
||||
integrity sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
|
|
@ -18793,16 +18776,6 @@ tweetnacl@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
|
||||
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
|
||||
|
||||
twilio-video@^2.13.0:
|
||||
version "2.13.0"
|
||||
resolved "https://registry.yarnpkg.com/twilio-video/-/twilio-video-2.13.0.tgz#cf3b5f2b20344724f4950d16357c60b07ba53ea9"
|
||||
integrity sha512-s+lU6a1eL3zmropDDQu0LaqmBEasrZ34Pr7vftUBsy23oYApEhby54XvPb2+aWkM0vvoOez32jLRjE2YgwQ38A==
|
||||
dependencies:
|
||||
"@twilio/webrtc" "4.3.2"
|
||||
backoff "^2.5.0"
|
||||
ws "^3.3.1"
|
||||
xmlhttprequest "^1.8.0"
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
|
|
@ -18880,11 +18853,6 @@ uglify-js@^3.1.4:
|
|||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.0.tgz#b943f129275c41d435eb54b643bbffee71dccf57"
|
||||
integrity sha512-8lBMSkFZuAK7gGF8LswsXmir8eX8d2AAMOnxSDWjKBx/fBR6MypQjs78m6ML9zQVp1/hD4TBdfeMZMC7nW1TAA==
|
||||
|
||||
ultron@~1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
|
||||
integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
|
||||
|
||||
unfetch@^4.0.1, unfetch@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"
|
||||
|
|
@ -19750,15 +19718,6 @@ write-file-atomic@^3.0.0:
|
|||
signal-exit "^3.0.2"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
|
||||
ws@^3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
|
||||
integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
safe-buffer "~5.1.0"
|
||||
ultron "~1.1.0"
|
||||
|
||||
ws@^5.2.0:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"
|
||||
|
|
@ -19793,11 +19752,6 @@ xmlchars@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xmlhttprequest@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
|
||||
integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
|
||||
|
||||
xregexp@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue