Pop-out draggable video support
This commit is contained in:
parent
fb11295168
commit
58264515f9
|
@ -64,11 +64,11 @@ body {
|
|||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
/* position: relative; */
|
||||
}
|
||||
|
||||
/* Auto-scroll checkbox in the corner */
|
||||
.chat-column >.autoscroll-field {
|
||||
.autoscroll-field {
|
||||
position: absolute;
|
||||
z-index: 39; /* just below modal shadow */
|
||||
right: 6px;
|
||||
|
@ -175,7 +175,7 @@ body {
|
|||
align-items: left; */
|
||||
}
|
||||
|
||||
.video-feeds > .feed {
|
||||
.feed {
|
||||
position: relative;
|
||||
/* flex: 0 0 168px; */
|
||||
float: left;
|
||||
|
@ -187,6 +187,17 @@ body {
|
|||
resize: both;
|
||||
}
|
||||
|
||||
/* A popped-out video feed window */
|
||||
div.feed.popped-out {
|
||||
position: absolute;
|
||||
border: 1px solid #FFF;
|
||||
cursor: move;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.video-feeds.x1 > .feed {
|
||||
flex: 0 0 252px;
|
||||
width: 252px;
|
||||
|
@ -211,25 +222,24 @@ body {
|
|||
height: 448px;
|
||||
}
|
||||
|
||||
.video-feeds > .feed > video {
|
||||
.feed > video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-feeds > .feed > .controls {
|
||||
.feed > .controls {
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
}
|
||||
|
||||
.video-feeds > .feed > .close {
|
||||
.feed > .close {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.video-feeds > .feed > .caption {
|
||||
.feed > .caption {
|
||||
position: absolute;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
color: #fff;
|
||||
|
|
|
@ -18,6 +18,9 @@ function setModalImage(url) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Popped-out video drag functions.
|
||||
|
||||
|
||||
|
||||
const app = Vue.createApp({
|
||||
delimiters: ['[[', ']]'],
|
||||
|
@ -120,6 +123,7 @@ const app = Vue.createApp({
|
|||
// Streams per username.
|
||||
streams: {},
|
||||
muted: {}, // muted bool per username
|
||||
poppedOut: {}, // popped-out video per username
|
||||
|
||||
// RTCPeerConnections per username.
|
||||
pc: {},
|
||||
|
@ -1183,6 +1187,111 @@ const app = Vue.createApp({
|
|||
}
|
||||
},
|
||||
|
||||
// Pop out a user's video.
|
||||
popoutVideo(username) {
|
||||
this.WebRTC.poppedOut[username] = !this.WebRTC.poppedOut[username];
|
||||
|
||||
// If not popped out, reset CSS positioning.
|
||||
window.requestAnimationFrame(this.makeDraggableVideos);
|
||||
},
|
||||
|
||||
// Outside of Vue, attach draggable video scripts to DOM.
|
||||
makeDraggableVideos() {
|
||||
let $panel = document.querySelector("#video-feeds");
|
||||
|
||||
interact('.popped-in').unset();
|
||||
|
||||
// Give popped out videos to the root of the DOM so they can
|
||||
// be dragged anywhere on the page.
|
||||
window.requestAnimationFrame(() => {
|
||||
document.querySelectorAll('.popped-out').forEach(node => {
|
||||
// $panel.removeChild(node);
|
||||
document.body.appendChild(node);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.popped-in').forEach(node => {
|
||||
// document.body.removeChild(node);
|
||||
$panel.appendChild(node);
|
||||
node.style.top = null;
|
||||
node.style.left = null;
|
||||
node.setAttribute('data-x', 0);
|
||||
node.setAttribute('data-y', 0);
|
||||
});
|
||||
});
|
||||
|
||||
interact('.popped-out').draggable({
|
||||
// enable inertial throwing
|
||||
inertia: true,
|
||||
// keep the element within the area of it's parent
|
||||
modifiers: [
|
||||
interact.modifiers.restrictRect({
|
||||
restriction: 'parent',
|
||||
endOnly: true
|
||||
})
|
||||
],
|
||||
|
||||
listeners: {
|
||||
// call this function on every dragmove event
|
||||
move(event) {
|
||||
let target = event.target;
|
||||
let x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx
|
||||
let y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy
|
||||
|
||||
target.style.top = `${y}px`;
|
||||
target.style.left = `${x}px`;
|
||||
|
||||
target.setAttribute('data-x', x);
|
||||
target.setAttribute('data-y', y);
|
||||
},
|
||||
|
||||
// call this function on every dragend event
|
||||
end (event) {
|
||||
console.log(
|
||||
'moved a distance of ' +
|
||||
(Math.sqrt(Math.pow(event.pageX - event.x0, 2) +
|
||||
Math.pow(event.pageY - event.y0, 2) | 0))
|
||||
.toFixed(2) + 'px')
|
||||
}
|
||||
}
|
||||
}).resizable({
|
||||
edges: { left: true, right: true, bottom: true, right: true },
|
||||
listeners: {
|
||||
move (event) {
|
||||
var target = event.target
|
||||
var x = (parseFloat(target.getAttribute('data-x')) || 0)
|
||||
var y = (parseFloat(target.getAttribute('data-y')) || 0)
|
||||
|
||||
// update the element's style
|
||||
target.style.width = event.rect.width + 'px'
|
||||
target.style.height = event.rect.height + 'px'
|
||||
|
||||
// translate when resizing from top or left edges
|
||||
x += event.deltaRect.left
|
||||
y += event.deltaRect.top
|
||||
|
||||
target.style.top = `${y}px`;
|
||||
target.style.left = `${x}px`;
|
||||
|
||||
target.setAttribute('data-x', x)
|
||||
target.setAttribute('data-y', y)
|
||||
}
|
||||
},
|
||||
modifiers: [
|
||||
// keep the edges inside the parent
|
||||
interact.modifiers.restrictEdges({
|
||||
outer: 'parent'
|
||||
}),
|
||||
|
||||
// minimum size
|
||||
interact.modifiers.restrictSize({
|
||||
min: { width: 100, height: 50 }
|
||||
})
|
||||
],
|
||||
|
||||
inertia: true
|
||||
})
|
||||
},
|
||||
|
||||
initHistory(channel) {
|
||||
if (this.channels[channel] == undefined) {
|
||||
this.channels[channel] = {
|
||||
|
|
3
web/static/js/interact.min.js
vendored
Normal file
3
web/static/js/interact.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -451,7 +451,7 @@
|
|||
<!-- Mute/Unmute my mic buttons (if streaming)-->
|
||||
<button type="button"
|
||||
v-if="webcam.active && !webcam.muted"
|
||||
class="button is-small is-danger is-outlined ml-1 px-1"
|
||||
class="button is-small is-success ml-1 px-1"
|
||||
@click="muteMe()">
|
||||
<i class="fa fa-microphone mr-2"></i>
|
||||
Mute
|
||||
|
@ -460,7 +460,7 @@
|
|||
v-if="webcam.active && webcam.muted"
|
||||
class="button is-small is-danger ml-1 px-1"
|
||||
@click="muteMe()">
|
||||
<i class="fa fa-microphone mr-2"></i>
|
||||
<i class="fa fa-microphone-slash mr-2"></i>
|
||||
Unmute
|
||||
</button>
|
||||
|
||||
|
@ -561,14 +561,6 @@
|
|||
|
||||
<!-- Middle Column: Chat Room/History -->
|
||||
<div class="chat-column">
|
||||
<div class="autoscroll-field tag">
|
||||
<label class="checkbox is-size-6" title="Automatically scroll when new chat messages come in.">
|
||||
<input type="checkbox"
|
||||
v-model="autoscroll"
|
||||
:value="true">
|
||||
Auto-scroll
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="card grid-card">
|
||||
<header class="card-header has-background-link">
|
||||
|
@ -615,19 +607,52 @@
|
|||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="video-feeds" :class="webcam.videoScale" v-show="webcam.active || Object.keys(WebRTC.streams).length > 0">
|
||||
<div id="video-feeds" class="video-feeds" :class="webcam.videoScale" v-show="webcam.active || Object.keys(WebRTC.streams).length > 0">
|
||||
<!-- Video Feeds-->
|
||||
|
||||
<!-- My video -->
|
||||
<div class="feed" v-show="webcam.active">
|
||||
<div class="feed" v-show="webcam.active"
|
||||
:class="{'popped-out': WebRTC.poppedOut[username],
|
||||
'popped-in': !WebRTC.poppedOut[username]}">
|
||||
<video class="feed"
|
||||
id="localVideo"
|
||||
autoplay muted>
|
||||
</video>
|
||||
|
||||
<div class="caption">
|
||||
[[username]]
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<!-- MY Mute button -->
|
||||
<button type="button"
|
||||
v-if="webcam.active && !webcam.muted"
|
||||
class="button is-small is-success is-outlined ml-1 px-2"
|
||||
@click="muteMe()">
|
||||
<i class="fa fa-microphone"></i>
|
||||
</button>
|
||||
<button type="button"
|
||||
v-if="webcam.active && webcam.muted"
|
||||
class="button is-small is-danger ml-1 px-2"
|
||||
@click="muteMe()">
|
||||
<i class="fa fa-microphone-slash"></i>
|
||||
</button>
|
||||
|
||||
<!-- Pop-out MY video -->
|
||||
<button type="button"
|
||||
class="button is-small is-light is-outlined p-2 ml-2"
|
||||
title="Pop out"
|
||||
@click="popoutVideo(username)">
|
||||
<i class="fa fa-up-right-from-square"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Others' videos -->
|
||||
<div class="feed" v-for="(stream, username) in WebRTC.streams" v-bind:key="username">
|
||||
<div class="feed" v-for="(stream, username) in WebRTC.streams"
|
||||
v-bind:key="username"
|
||||
:class="{'popped-out': WebRTC.poppedOut[username],
|
||||
'popped-in': !WebRTC.poppedOut[username]}">
|
||||
<video class="feed"
|
||||
:id="'videofeed-'+username"
|
||||
autoplay>
|
||||
|
@ -644,20 +669,29 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<!-- Mute button -->
|
||||
<button type="button"
|
||||
v-if="isMuted(username)"
|
||||
class="button is-small is-danger is-outlined p-1"
|
||||
class="button is-small is-danger p-2"
|
||||
title="Unmute this video"
|
||||
@click="muteVideo(username)">
|
||||
<i class="fa fa-volume-xmark"></i>
|
||||
</button>
|
||||
<button type="button"
|
||||
v-else
|
||||
class="button is-small is-danger is-outlined p-1"
|
||||
class="button is-small is-success is-outlined p-2"
|
||||
title="Mute this video"
|
||||
@click="muteVideo(username)">
|
||||
<i class="fa fa-volume-high"></i>
|
||||
</button>
|
||||
|
||||
<!-- Pop-out -->
|
||||
<button type="button"
|
||||
class="button is-small is-light is-outlined p-2 ml-2"
|
||||
title="Pop out"
|
||||
@click="popoutVideo(username)">
|
||||
<i class="fa fa-up-right-from-square"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -680,6 +714,15 @@
|
|||
</div>
|
||||
<div class="card-content" id="chatHistory" :class="fontSizeClass">
|
||||
|
||||
<div class="autoscroll-field tag">
|
||||
<label class="checkbox is-size-6" title="Automatically scroll when new chat messages come in.">
|
||||
<input type="checkbox"
|
||||
v-model="autoscroll"
|
||||
:value="true">
|
||||
Auto-scroll
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- No history? -->
|
||||
<div v-if="chatHistory.length === 0">
|
||||
<em v-if="isDM">
|
||||
|
@ -806,7 +849,7 @@
|
|||
<div class="column pl-1 is-narrow">
|
||||
<button type="button" class="button"
|
||||
@click="uploadFile()">
|
||||
<i class="fa fa-upload"></i>
|
||||
<i class="fa fa-image"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -979,6 +1022,7 @@ const UserJWTClaims = {{.JWTClaims.ToJSON}};
|
|||
</script>
|
||||
|
||||
<script src="/static/js/vue-3.2.45.js"></script>
|
||||
<script src="/static/js/interact.min.js"></script>
|
||||
<script src="/static/js/sounds.js?{{.CacheHash}}"></script>
|
||||
<script src="/static/js/BareRTC.js?{{.CacheHash}}"></script>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user