Pop-out draggable video support

ipad-testing
Noah 2023-04-19 22:00:31 -07:00
parent fb11295168
commit 58264515f9
4 changed files with 190 additions and 24 deletions

View File

@ -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;

View File

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

File diff suppressed because one or more lines are too long

View File

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