Chat Setting Menu + Various Tweaks
* In place of the Help and Settings buttons, add a hamburger menu dropdown and place the links under there. * Also in the dropdown is Close All Cameras and Mute All Cameras (if you have any cams open; the links are hidden if not) * Also in the dropdown add a Logout button that just links to a new /logout route in order to unload the page and align with some users' expectations (not knowing closing out of the chat page was enough to log out of the room before) * Bring back "(offline)" indicators when a user is no longer in the room.
This commit is contained in:
parent
52dd53240e
commit
cbfbcd768f
13
pkg/pages.go
13
pkg/pages.go
|
@ -125,3 +125,16 @@ func AboutPage() http.HandlerFunc {
|
||||||
tmpl.ExecuteTemplate(w, "index", values)
|
tmpl.ExecuteTemplate(w, "index", values)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogoutPage returns the HTML template for the logout page.
|
||||||
|
func LogoutPage() http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Load the template, TODO: once on server startup.
|
||||||
|
tmpl := template.New("index")
|
||||||
|
tmpl, err := tmpl.ParseFiles("web/templates/logout.html")
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
tmpl.ExecuteTemplate(w, "index", nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ func (s *Server) Setup() error {
|
||||||
|
|
||||||
mux.Handle("/", IndexPage())
|
mux.Handle("/", IndexPage())
|
||||||
mux.Handle("/about", AboutPage())
|
mux.Handle("/about", AboutPage())
|
||||||
|
mux.Handle("/logout", LogoutPage())
|
||||||
mux.Handle("/ws", s.WebSocket())
|
mux.Handle("/ws", s.WebSocket())
|
||||||
mux.Handle("/api/statistics", s.Statistics())
|
mux.Handle("/api/statistics", s.Statistics())
|
||||||
mux.Handle("/api/blocklist", s.BlockList())
|
mux.Handle("/api/blocklist", s.BlockList())
|
||||||
|
|
99
src/App.vue
99
src/App.vue
|
@ -116,6 +116,7 @@ export default {
|
||||||
whoTab: 'online',
|
whoTab: 'online',
|
||||||
whoSort: 'a-z',
|
whoSort: 'a-z',
|
||||||
whoMap: {}, // map username to wholist entry
|
whoMap: {}, // map username to wholist entry
|
||||||
|
whoOnline: {}, // map users actually online right now
|
||||||
muted: {}, // muted usernames for client side state
|
muted: {}, // muted usernames for client side state
|
||||||
|
|
||||||
// Misc. user preferences (TODO: move all of them here)
|
// Misc. user preferences (TODO: move all of them here)
|
||||||
|
@ -512,6 +513,10 @@ export default {
|
||||||
if (this.webcam.vipOnly && this.isVIP) status |= this.VideoFlag.VipOnly;
|
if (this.webcam.vipOnly && this.isVIP) status |= this.VideoFlag.VipOnly;
|
||||||
return status;
|
return status;
|
||||||
},
|
},
|
||||||
|
numVideosOpen() {
|
||||||
|
// Return the count of other peoples videos we have open.
|
||||||
|
return Object.keys(this.WebRTC.streams).length;
|
||||||
|
},
|
||||||
sortedWhoList() {
|
sortedWhoList() {
|
||||||
let result = [...this.whoList];
|
let result = [...this.whoList];
|
||||||
|
|
||||||
|
@ -871,6 +876,7 @@ export default {
|
||||||
// WhoList updates.
|
// WhoList updates.
|
||||||
onWho(msg) {
|
onWho(msg) {
|
||||||
this.whoList = msg.whoList;
|
this.whoList = msg.whoList;
|
||||||
|
this.whoOnline = {};
|
||||||
|
|
||||||
if (this.whoList == undefined) {
|
if (this.whoList == undefined) {
|
||||||
this.whoList = [];
|
this.whoList = [];
|
||||||
|
@ -880,6 +886,7 @@ export default {
|
||||||
// off camera, close our side of the connection.
|
// off camera, close our side of the connection.
|
||||||
for (let row of this.whoList) {
|
for (let row of this.whoList) {
|
||||||
this.whoMap[row.username] = row;
|
this.whoMap[row.username] = row;
|
||||||
|
this.whoOnline[row.username] = true;
|
||||||
if (this.WebRTC.streams[row.username] != undefined &&
|
if (this.WebRTC.streams[row.username] != undefined &&
|
||||||
!(row.video & this.VideoFlag.Active)) {
|
!(row.video & this.VideoFlag.Active)) {
|
||||||
this.closeVideo(row.username, "offerer");
|
this.closeVideo(row.username, "offerer");
|
||||||
|
@ -1510,9 +1517,6 @@ export default {
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
isUsernameOnline(username) {
|
|
||||||
return this.whoMap[username] != undefined;
|
|
||||||
},
|
|
||||||
getUser(username) {
|
getUser(username) {
|
||||||
// Return the full User object from the Who List, or a dummy placeholder if not online.
|
// Return the full User object from the Who List, or a dummy placeholder if not online.
|
||||||
if (this.whoMap[username] != undefined) {
|
if (this.whoMap[username] != undefined) {
|
||||||
|
@ -1523,6 +1527,10 @@ export default {
|
||||||
username: username,
|
username: username,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
isUserOffline(username) {
|
||||||
|
// Return if the username is not presently online in the chat.
|
||||||
|
return this.whoOnline[username] !== true;
|
||||||
|
},
|
||||||
avatarForUsername(username) {
|
avatarForUsername(username) {
|
||||||
if (this.whoMap[username] != undefined && this.whoMap[username].avatar) {
|
if (this.whoMap[username] != undefined && this.whoMap[username].avatar) {
|
||||||
return this.avatarURL(this.whoMap[username]);
|
return this.avatarURL(this.whoMap[username]);
|
||||||
|
@ -1999,6 +2007,32 @@ export default {
|
||||||
// Inform backend we have closed it.
|
// Inform backend we have closed it.
|
||||||
this.sendWatch(username, false);
|
this.sendWatch(username, false);
|
||||||
},
|
},
|
||||||
|
closeOpenVideos() {
|
||||||
|
// Close all videos open of other users.
|
||||||
|
for (let username of Object.keys(this.WebRTC.streams)) {
|
||||||
|
this.closeVideo(username, "offerer");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muteAllVideos() {
|
||||||
|
// Mute the mic of all open videos.
|
||||||
|
let count = 0;
|
||||||
|
for (let username of Object.keys(this.WebRTC.streams)) {
|
||||||
|
if (this.WebRTC.muted[username]) continue;
|
||||||
|
|
||||||
|
// Find the <video> tag to mute it.
|
||||||
|
this.WebRTC.muted[username] = true;
|
||||||
|
let $ref = document.getElementById(`videofeed-${username}`);
|
||||||
|
if ($ref) {
|
||||||
|
$ref.muted = this.WebRTC.muted[username];
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
this.ChatClient(`You have muted the audio on ${count} video${count === 1 ? '' : 's'}.`);
|
||||||
|
}
|
||||||
|
},
|
||||||
unMutualVideo() {
|
unMutualVideo() {
|
||||||
// If we had our camera on to watch a video of someone who wants mutual cameras,
|
// If we had our camera on to watch a video of someone who wants mutual cameras,
|
||||||
// and then we turn ours off: we should unfollow the ones with mutual video.
|
// and then we turn ours off: we should unfollow the ones with mutual video.
|
||||||
|
@ -3218,14 +3252,45 @@ export default {
|
||||||
<i class="fa fa-fire mr-1" :class="{ 'has-text-danger': !webcam.nsfw }"></i> Explicit
|
<i class="fa fa-fire mr-1" :class="{ 'has-text-danger': !webcam.nsfw }"></i> Explicit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow pl-1">
|
<div class="column dropdown is-right is-narrow pl-1"
|
||||||
<a href="/about" target="_blank" class="button is-small is-link px-2">
|
onclick="this.classList.toggle('is-active')">
|
||||||
<i class="fa fa-info-circle"></i>
|
<div class="dropdown-trigger">
|
||||||
</a>
|
<button type="button" class="button is-small is-link px-2"
|
||||||
<button type="button" class="button is-small is-light ml-1 px-2" @click="showSettings()"
|
aria-haspopup="true"
|
||||||
title="Chat Settings">
|
aria-controls="chat-settings-menu">
|
||||||
<i class="fa fa-gear"></i>
|
<span>
|
||||||
</button>
|
<i class="fa fa-bars"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dropdown-menu mr-3" id="chat-settings-menu" role="menu">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="#" class="dropdown-item" @click.prevent="showSettings()">
|
||||||
|
<i class="fa fa-gear mr-1"></i> Chat Settings
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="#" class="dropdown-item" v-if="numVideosOpen > 0"
|
||||||
|
@click.prevent="closeOpenVideos()">
|
||||||
|
<i class="fa fa-video-slash mr-1"></i> Close all cameras
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="#" class="dropdown-item" v-if="numVideosOpen > 0"
|
||||||
|
@click.prevent="muteAllVideos()">
|
||||||
|
<i class="fa fa-microphone-slash mr-1"></i> Mute all cameras
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
|
||||||
|
<a href="/about" target="_blank" class="dropdown-item">
|
||||||
|
<i class="fa fa-info-circle mr-1"></i> About
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/logout" class="dropdown-item">
|
||||||
|
<i class="fa fa-arrow-right-from-bracket mr-1"></i> Log out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -3279,9 +3344,12 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
{{ c.name }}
|
<del v-if="isUserOffline(c.name)">
|
||||||
|
{{ c.name }}
|
||||||
|
</del>
|
||||||
|
<span v-else>{{ c.name }}</span>
|
||||||
|
|
||||||
<span v-if="hasUnread(c.channel)" class="tag is-danger">
|
<span v-if="hasUnread(c.channel)" class="tag is-danger ml-1">
|
||||||
{{ hasUnread(c.channel) }}
|
{{ hasUnread(c.channel) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3486,8 +3554,8 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<strong>{{ nicknameForUsername(msg.username) }}</strong>
|
<strong>{{ nicknameForUsername(msg.username) }}</strong>
|
||||||
<small v-if="isUsernameOnline(msg.username)"
|
<span v-if="isUserOffline(msg.username)" class="ml-1">(offline)</span>
|
||||||
class="ml-1">(@{{ msg.username }})</small>
|
<small v-else class="ml-1">(@{{ msg.username }})</small>
|
||||||
{{ msg.message }}
|
{{ msg.message }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3500,6 +3568,7 @@ export default {
|
||||||
:message="msg"
|
:message="msg"
|
||||||
:position="i"
|
:position="i"
|
||||||
:user="getUser(msg.username)"
|
:user="getUser(msg.username)"
|
||||||
|
:is-offline="isUserOffline(msg.username)"
|
||||||
:username="username"
|
:username="username"
|
||||||
:website-url="config.website"
|
:website-url="config.website"
|
||||||
:is-dnd="isUsernameDND(msg.username)"
|
:is-dnd="isUsernameDND(msg.username)"
|
||||||
|
|
|
@ -3,6 +3,7 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
message: Object, // chat Message object
|
message: Object, // chat Message object
|
||||||
user: Object, // User object of the Message author
|
user: Object, // User object of the Message author
|
||||||
|
isOffline: Boolean, // user is not currently online
|
||||||
username: String, // current username logged in
|
username: String, // current username logged in
|
||||||
websiteUrl: String, // Base URL to website (for profile/avatar URLs)
|
websiteUrl: String, // Base URL to website (for profile/avatar URLs)
|
||||||
isDnd: Boolean, // user is not accepting DMs
|
isDnd: Boolean, // user is not accepting DMs
|
||||||
|
@ -151,6 +152,9 @@ export default {
|
||||||
|
|
||||||
<!-- User nickname/display name -->
|
<!-- User nickname/display name -->
|
||||||
{{ nickname }}
|
{{ nickname }}
|
||||||
|
|
||||||
|
<!-- Offline now? -->
|
||||||
|
<span v-if="isOffline">(offline)</span>
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="column has-text-right pb-0">
|
<div class="column has-text-right pb-0">
|
||||||
|
|
30
web/templates/logout.html
Normal file
30
web/templates/logout.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{{define "index"}}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/bulma.min.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/static/css/bulma-prefers-dark.css">
|
||||||
|
<title>Logged Out</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container is-fullhd">
|
||||||
|
<div class="content my-5">
|
||||||
|
<h1>Logged Out</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You are now logged out of the chat room. You may now close this page.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
setTimeout(() => {
|
||||||
|
window.close();
|
||||||
|
}, 5000);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{end}}
|
Loading…
Reference in New Issue
Block a user