Draggable resizable video panels

This commit is contained in:
Noah 2023-03-13 21:26:37 -07:00
parent 5f2456103b
commit 368902e801
6 changed files with 133 additions and 29 deletions

View File

@ -33,7 +33,11 @@ It is very much in the style of the old-school Flash based webcam chat rooms of
Some important features still lacking: Some important features still lacking:
* Operator controls (kick/ban users) * Operator commands
* [x] /kick users
* [x] /nsfw to mark someone's camera
* [ ] /ban users
* [ ] /op users (give temporary mod control)
# Configuration # Configuration

28
pkg/banned_users.go Normal file
View File

@ -0,0 +1,28 @@
package barertc
import "time"
/* Functions to handle banned users */
/*
BanList holds (in memory) knowledge of currently banned users.
All bans are reset if the chat server is rebooted. Otherwise each ban
comes with a duration - default is 24 hours by the operator can specify
a duration with a ban. If the server is not rebooted, bans will be lifted
after they expire.
Bans are against usernames and will also block a JWT token from
authenticating if they are currently banned.
*/
type BanList struct {
Active []Ban
}
// Ban is an entry on the ban list.
type Ban struct {
Username string
ExpiresAt time.Time
}
//

View File

@ -1,6 +1,10 @@
package barertc package barertc
import "strings" import (
"strconv"
"strings"
"time"
)
// ProcessCommand parses a chat message for "/commands" // ProcessCommand parses a chat message for "/commands"
func (s *Server) ProcessCommand(sub *Subscriber, msg Message) bool { func (s *Server) ProcessCommand(sub *Subscriber, msg Message) bool {
@ -18,21 +22,10 @@ func (s *Server) ProcessCommand(sub *Subscriber, msg Message) bool {
if sub.JWTClaims != nil && sub.JWTClaims.IsAdmin { if sub.JWTClaims != nil && sub.JWTClaims.IsAdmin {
switch words[0] { switch words[0] {
case "/kick": case "/kick":
if len(words) == 1 { s.KickCommand(words, sub)
sub.ChatServer("Usage: `/kick username` to remove the user from the chat room.") return true
} case "/ban":
username := words[1] s.BanCommand(words, sub)
other, err := s.GetSubscriber(username)
if err != nil {
sub.ChatServer("/kick: username not found: %s", username)
} else {
other.ChatServer("You have been kicked from the chat room by %s", sub.Username)
other.SendJSON(Message{
Action: ActionKick,
})
s.DeleteSubscriber(other)
sub.ChatServer("%s has been kicked from the room", username)
}
return true return true
case "/nsfw": case "/nsfw":
if len(words) == 1 { if len(words) == 1 {
@ -63,3 +56,62 @@ func (s *Server) ProcessCommand(sub *Subscriber, msg Message) bool {
// Not handled. // Not handled.
return false return false
} }
// KickCommand handles the `/kick` operator command.
func (s *Server) KickCommand(words []string, sub *Subscriber) {
if len(words) == 1 {
sub.ChatServer("Usage: `/kick username` to remove the user from the chat room.")
}
username := words[1]
other, err := s.GetSubscriber(username)
if err != nil {
sub.ChatServer("/kick: username not found: %s", username)
} else {
other.ChatServer("You have been kicked from the chat room by %s", sub.Username)
other.SendJSON(Message{
Action: ActionKick,
})
s.DeleteSubscriber(other)
sub.ChatServer("%s has been kicked from the room", username)
}
}
// BanCommand handles the `/ban` operator command.
func (s *Server) BanCommand(words []string, sub *Subscriber) {
if len(words) == 1 {
sub.ChatServer(
"Usage: `/ban username` to remove the user from the chat room for 24 hours (default).\n\n" +
"Set another duration (in hours, fractions supported) like: `/ban username 0.5` for a 30-minute ban.",
)
}
// Parse the command.
var (
username = words[1]
duration = 24 * time.Hour
)
if len(words) >= 3 {
if dur, err := strconv.ParseFloat(words[2], 64); err == nil {
if dur < 1 {
duration = time.Duration(dur*60) * time.Second
} else {
duration = time.Duration(dur) * time.Hour
}
}
}
// TODO: banning, for now it just kicks.
_ = duration
other, err := s.GetSubscriber(username)
if err != nil {
sub.ChatServer("/ban: username not found: %s", username)
} else {
other.ChatServer("You have been kicked from the chat room by %s", sub.Username)
other.SendJSON(Message{
Action: ActionKick,
})
s.DeleteSubscriber(other)
sub.ChatServer("%s has been kicked from the room", username)
}
}

View File

@ -147,18 +147,21 @@ body {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
display: flex; /* display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: left; align-items: left; */
} }
.video-feeds > .feed { .video-feeds > .feed {
position: relative; position: relative;
flex: 0 0 168px; /* flex: 0 0 168px; */
float: left;
width: 168px; width: 168px;
height: 112px; height: 112px;
background-color: black; background-color: black;
margin: 3px; margin: 3px;
overflow: hidden;
resize: both;
} }
.video-feeds.x1 > .feed { .video-feeds.x1 > .feed {
@ -193,7 +196,7 @@ body {
.video-feeds > .feed > .controls { .video-feeds > .feed > .controls {
position: absolute; position: absolute;
background: rgba(0, 0, 0, 0.75); background: rgba(0, 0, 0, 0.75);
right: 4px; left: 4px;
bottom: 4px; bottom: 4px;
} }

View File

@ -188,14 +188,20 @@ const app = Vue.createApp({
history.pushState(null, "", location.href.split("?")[0]); history.pushState(null, "", location.href.split("?")[0]);
// XX: always show login dialog to test if this helps iOS devices. // XX: always show login dialog to test if this helps iOS devices.
this.loginModal.visible = true; // this.loginModal.visible = true;
/*
if (!this.username) { if (!this.username) {
this.loginModal.visible = true; this.loginModal.visible = true;
} else { } else {
this.signIn(); this.signIn();
} }
*/ },
watch: {
"webcam.videoScale": () => {
document.querySelectorAll(".video-feeds > .feed").forEach(node => {
node.style.width = null;
node.style.height = null;
});
},
}, },
computed: { computed: {
chatHistory() { chatHistory() {

View File

@ -463,11 +463,12 @@
<!-- Video Feeds--> <!-- Video Feeds-->
<!-- My video --> <!-- My video -->
<div class="feed" v-show="webcam.active">
<video class="feed" <video class="feed"
id="localVideo" id="localVideo"
v-show="webcam.active"
autoplay muted> autoplay muted>
</video> </video>
</div>
<!-- Others' videos --> <!-- 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">
@ -509,7 +510,17 @@
<div class="feed"> <div class="feed">
hi hi
</div> </div>
<div class="feed">
hi
</div>
<div class="feed">
hi
</div>
<div class="feed">
hi
</div>
--> -->
</div> </div>
<div class="card-content" id="chatHistory"> <div class="card-content" id="chatHistory">