Draggable resizable video panels
This commit is contained in:
parent
5f2456103b
commit
368902e801
|
@ -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
28
pkg/banned_users.go
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -463,11 +463,12 @@
|
||||||
<!-- Video Feeds-->
|
<!-- Video Feeds-->
|
||||||
|
|
||||||
<!-- My video -->
|
<!-- My video -->
|
||||||
<video class="feed"
|
<div class="feed" v-show="webcam.active">
|
||||||
id="localVideo"
|
<video class="feed"
|
||||||
v-show="webcam.active"
|
id="localVideo"
|
||||||
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">
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user