diff --git a/defaultFlair.json b/defaultFlair.json index 6f6e608..65c2801 100644 --- a/defaultFlair.json +++ b/defaultFlair.json @@ -1,10 +1,17 @@ [ { - "name": "glitter", + "name": "gold", + "displayName": "Gold", "rank": "gold" }, { - "name": "negativeGlitter", - "ranke": "gold" + "name": "black-gold", + "displayName": "Black Gold", + "rank": "gold" + }, + { + "name": "lightning", + "displayName": "Lightning", + "rank": "gold" } -] \ No newline at end of file +] diff --git a/src/app/channel/activeChannel.js b/src/app/channel/activeChannel.js index 949e438..7c78111 100644 --- a/src/app/channel/activeChannel.js +++ b/src/app/channel/activeChannel.js @@ -14,6 +14,10 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ +//local imports +const flairModel = require('../../schemas/flairSchema'); +const permissionModel = require('../../schemas/permissionSchema'); + module.exports = class{ constructor(server, name){ this.server = server; @@ -21,7 +25,7 @@ module.exports = class{ this.userList = new Map(); } - handleConnection(userDB, socket){ + async handleConnection(userDB, socket){ //get current user object from the userlist var userObj = this.userList.get(userDB.user); @@ -45,6 +49,9 @@ module.exports = class{ //if everything looks good, admit the connection to the channel socket.join(socket.chan); + //Make sure the client receives important client-info before userlist + await this.sendClientMetadata(userDB, socket); + //Send out the userlist this.broadcastUserList(socket.chan); } @@ -71,13 +78,59 @@ module.exports = class{ this.broadcastUserList(socket.chan); } + async sendClientMetadata(userObj, socket){ + const flairListDB = await flairModel.find({}); + var flairList = []; + + const user = { + id: userObj.id, + user: userObj.user, + rank: userObj.rank, + flair: userObj.flair + } + + flairListDB.forEach((flair)=>{ + if(permissionModel.rankToNum(flair.rank) <= permissionModel.rankToNum(userObj.rank)){ + flairList.push({ + name: flair.name, + displayName: flair.displayName + }); + } + }); + + socket.emit("clientMetadata", {user, flairList}); + } + broadcastUserList(){ var userList = []; this.userList.forEach((userObj, user) => { - userList.push(user); + userList.push({ + user: user, + flair: userObj.flair + }); }); - this.server.io.in(this.name).emit("user-list", userList); + this.server.io.in(this.name).emit("userList", userList); + } + + updateFlair(user, flair){ + const userObj = this.userList.get(user); + + userObj.flair = flair; + + this.userList.set(user, userObj); + + //Quick hack to make this compatible with sendClientMetadata. make sure we do this AFTER setting the object in the userList map... + userObj.name = user; + + //Crawl through user's sockets (lol) + userObj.sockets.forEach((sockid) => { + //Send metadata to each one + const socket = this.server.io.sockets.sockets.get(sockid); + this.sendClientMetadata(userObj, socket); + }); + + this.broadcastUserList(); } } \ No newline at end of file diff --git a/src/app/channel/channelManager.js b/src/app/channel/channelManager.js index 7b0006e..0fb2797 100644 --- a/src/app/channel/channelManager.js +++ b/src/app/channel/channelManager.js @@ -116,4 +116,19 @@ module.exports = class{ const channel = this.activeChannels.get(socket.chan); return channel.userList.get(socket.user.user); } + + getConnectedChannels(socket){ + var chanList = []; + + this.activeChannels.forEach((channel) => { + const foundUser = channel.userList.get(socket.user.user); + + //If we found a user and this channel hasn't been added to the list + if(foundUser){ + chanList.push(channel); + } + }); + + return chanList; + } } \ No newline at end of file diff --git a/src/app/channel/chatHandler.js b/src/app/channel/chatHandler.js index 48f6ffd..cc266d3 100644 --- a/src/app/channel/chatHandler.js +++ b/src/app/channel/chatHandler.js @@ -20,7 +20,6 @@ const validator = require('validator');//No express here, so regular validator i //local imports const loggerUtils = require('../../utils/loggerUtils'); const userModel = require('../../schemas/userSchema'); -const channelManager = require('./channelManager'); module.exports = class{ constructor(server){ @@ -54,13 +53,20 @@ module.exports = class{ } async setFlair(socket, data){ - const userDB = await userModel.findOne({user: socket.user.user}); + var userDB = await userModel.findOne({user: socket.user.user}); + const chanList = this.server.getConnectedChannels(socket) if(userDB){ try{ //We can take this data raw since our schema checks it against existing flairs, and mongoose sanatizes queries userDB.flair = data.flair; - await userDB.save(); + userDB = await userDB.save(); + + chanList.forEach((channel) => { + channel.updateFlair(userDB.user, userDB.flair); + }); + + }catch(err){ return loggerUtils.socketExceptionHandler(socket, err); } diff --git a/src/schemas/flairSchema.js b/src/schemas/flairSchema.js index ec43073..f8981ed 100644 --- a/src/schemas/flairSchema.js +++ b/src/schemas/flairSchema.js @@ -26,6 +26,10 @@ const flairSchema = new mongoose.Schema({ type: mongoose.SchemaTypes.String, required: true }, + displayName:{ + type: mongoose.SchemaTypes.String, + required: true + }, rank: { type: mongoose.SchemaTypes.String, enum: permissionModel.rankEnum, diff --git a/src/schemas/userSchema.js b/src/schemas/userSchema.js index 4dc56f6..ebd5b7e 100644 --- a/src/schemas/userSchema.js +++ b/src/schemas/userSchema.js @@ -105,6 +105,10 @@ userSchema.pre('save', async function (next){ //Throw a shit fit. Do not pass go. Do not collect $200. throw new Error("Invalid flair!"); } + + if(permissionModel.rankToNum(this.rank) < permissionModel.rankToNum(foundFlair.rank)){ + throw new Error(`User '${this.user}' does not have a high enough rank for flair '${foundFlair.displayName}'!`); + } } } diff --git a/src/views/partial/styles.ejs b/src/views/partial/styles.ejs index afd5a9c..86461ab 100644 --- a/src/views/partial/styles.ejs +++ b/src/views/partial/styles.ejs @@ -15,4 +15,5 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see .--> + \ No newline at end of file diff --git a/www/css/channel.css b/www/css/channel.css index d9bb45a..35c0eab 100644 --- a/www/css/channel.css +++ b/www/css/channel.css @@ -202,6 +202,7 @@ input#chat-panel-prompt{ .user-entry{ margin: 0.2em; font-size: 1em; + width: fit-content; } #media-panel-aspect-lock-icon{ diff --git a/www/css/flair.css b/www/css/flair.css new file mode 100644 index 0000000..be1dcdd --- /dev/null +++ b/www/css/flair.css @@ -0,0 +1,16 @@ +.flair-gold, .flair-black-gold, .flair-lightning{ + background-image: url('/img/flair/gold.gif'); + border-radius: 0.3em; + box-shadow: 0.1em 0.1em 3px #fff4b6 inset, -0.1em 0.1em 3px #fff4b6 inset, 0.1em -0.1em 3px #fff4b6 inset, -0.1em -0.1em 3px #fff4b6 inset; + color: rgb(121, 106, 23); + text-shadow: 1px 1px 1px #fff4b6, -1px 1px 1px #fff4b6, 1px -1px 1px #fff4b6, -1px -1px 1px #fff4b6; + padding: 0.1em 0.4em; +} + +.flair-black-gold{ + filter: invert(1) grayscale(0.9); +} + +.flair-lightning{ + filter: invert(1) grayscale(0.5); +} \ No newline at end of file diff --git a/www/img/flair/gold.gif b/www/img/flair/gold.gif new file mode 100644 index 0000000..28eaa51 Binary files /dev/null and b/www/img/flair/gold.gif differ diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index 5622942..9169c2e 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -43,9 +43,19 @@ class channel{ document.title = `${this.channelName} - Connected` }); - this.socket.on("error", (data) => { - console.log(data); - }); + this.socket.on("clientMetadata", this.handleClientInfo.bind(this)); + + this.socket.on("error", console.log); + } + + handleClientInfo(data){ + this.user = { + id: data.user.id, + name: data.user.name, + rank: data.user.rank + } + + this.chatBox.handleClientInfo(data); } } diff --git a/www/js/channel/chat.js b/www/js/channel/chat.js index 19cdda2..fc2ee66 100644 --- a/www/js/channel/chat.js +++ b/www/js/channel/chat.js @@ -27,7 +27,8 @@ class chatBox{ //Element Nodes this.chatPanel = document.querySelector("#chat-panel-div"); - this.highLevel = document.querySelector("#chat-panel-high-level-select"); + this.highSelect = document.querySelector("#chat-panel-high-level-select"); + this.flairSelect = document.querySelector("#chat-panel-flair-select"); this.chatBuffer = document.querySelector("#chat-panel-buffer-div"); this.chatPrompt = document.querySelector("#chat-panel-prompt"); this.settingsIcon = document.querySelector("#chat-panel-settings-icon"); @@ -57,6 +58,7 @@ class chatBox{ this.aspectLockIcon.addEventListener("click", this.lockAspect.bind(this)); this.showChatIcon.addEventListener("click", ()=>{this.toggleUI()}); this.hideChatIcon.addEventListener("click", ()=>{this.toggleUI()}); + this.flairSelect.addEventListener("change", this.setFlair.bind(this)); //Clickdragger/Resize this.clickDragger.handle.addEventListener("mousedown", this.unlockAspect.bind(this)); @@ -69,6 +71,92 @@ class chatBox{ }); } + displayChat(chat){ + //Create chat-entry span + var chatEntry = document.createElement('span'); + chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${chat.user}`); + + //Create high-level label + var highLevel = document.createElement('p'); + highLevel.classList.add("chat-panel-buffer","chat-entry-high-level"); + highLevel.innerHTML = `${chat.high}`; + chatEntry.appendChild(highLevel); + + //Create username label + var userLabel = document.createElement('p'); + userLabel.classList.add("chat-panel-buffer","chat-entry-username"); + + if(chat.flair != ""){ + var flair = `flair-${chat.flair}`; + }else{ + var flair = this.client.userList.colorMap.get(chat.user); + } + + //Create color span + var colorSpan = document.createElement('span'); + colorSpan.classList.add("chat-entry-flair-span", flair); + colorSpan.innerHTML = `${chat.user}`; + userLabel.innerHTML = `${colorSpan.outerHTML}: `; + + chatEntry.appendChild(userLabel); + + //Create chat body + var chatBody = document.createElement('p'); + chatBody.classList.add("chat-panel-buffer","chat-entry-body"); + chatBody.innerHTML = chat.msg; + chatEntry.appendChild(chatBody); + + this.chatBuffer.appendChild(chatEntry); + } + + async send(event){ + if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){ + this.client.socket.emit("chatMessage",{msg: this.chatPrompt.value, high: this.highSelect.value}); + this.chatPrompt.value = ""; + } + } + + handleClientInfo(data){ + this.updateFlairSelect(data.flairList, data.user.flair); + } + + setFlair(event){ + const flair = event.target.value; + + this.client.socket.emit("setFlair", {flair}); + } + + + + updateFlairSelect(flairList, flair){ + //clear current flair select + this.flairSelect.innerHTML = ""; + + //Inject flair object for standard flair like the hack we are + flairList.push({ + name: "", + displayName: "Classic" + }); + + //For each flair in flairlist + flairList.forEach((flair) => { + //Create an option + var flairOption = document.createElement('option'); + //Set the name and innerHTML + flairOption.value = flair.name; + flairOption.innerHTML = flair.displayName; + + //Append it to the select + this.flairSelect.appendChild(flairOption); + }); + + //Set the selected flair in the UI + this.flairSelect.value = flair; + //Re-style the UI, do this in two seperate steps in-case we're running for the first time and have nothing to replace. + this.flairSelect.className = this.flairSelect.className.replace(/flair-\S*/, ""); + this.flairSelect.classList.add(`flair-${flair}`); + } + lockAspect(event){ //prevent the user from breaking shit :P if(this.chatPanel.style.display != "none"){ @@ -112,44 +200,4 @@ class chatBox{ this.client.player.hideVideoIcon.style.display = "none"; } } - - displayChat(chat){ - //Create chat-entry span - var chatEntry = document.createElement('span'); - chatEntry.classList.add("chat-panel-buffer","chat-entry",`chat-entry-${chat.user}`); - - //Create high-level label - var highLevel = document.createElement('p'); - highLevel.classList.add("chat-panel-buffer","chat-entry-high-level"); - highLevel.innerHTML = `${chat.high}`; - chatEntry.appendChild(highLevel); - - //Create username label - var userLabel = document.createElement('p'); - userLabel.classList.add("chat-panel-buffer","chat-entry-username"); - - //Create color span - var colorSpan = document.createElement('span'); - colorSpan.classList.add(this.client.userList.colorMap.get(chat.user)); - colorSpan.innerHTML = `${chat.user}`; - userLabel.innerHTML = `${colorSpan.outerHTML}: `; - - chatEntry.appendChild(userLabel); - - //Create chat body - var chatBody = document.createElement('p'); - chatBody.classList.add("chat-panel-buffer","chat-entry-body"); - chatBody.innerHTML = chat.msg; - chatEntry.appendChild(chatBody); - - this.chatBuffer.appendChild(chatEntry); - } - - async send(event){ - if((!event || !event.key || event.key == "Enter") && this.chatPrompt.value){ - this.client.socket.emit("chatMessage",{msg: this.chatPrompt.value, high: this.highLevel.value}); - this.chatPrompt.value = ""; - } - } - } \ No newline at end of file diff --git a/www/js/channel/userlist.js b/www/js/channel/userlist.js index 5730047..9bce9f7 100644 --- a/www/js/channel/userlist.js +++ b/www/js/channel/userlist.js @@ -53,7 +53,7 @@ class userList{ } defineListeners(){ - this.client.socket.on('user-list', (data) => { + this.client.socket.on('userList', (data) => { this.updateList(data); }); } @@ -72,12 +72,12 @@ class userList{ var color = this.userColors[Math.floor(Math.random()*this.userColors.length)] //if this user was in the previous colormap - if(this.colorMap.get(user) != null){ + if(this.colorMap.get(user.user) != null){ //Override with previous color - color = this.colorMap.get(user); + color = this.colorMap.get(user.user); } - newMap.set(user, color); + newMap.set(user.user, color); this.renderUser(user, color); }); @@ -87,8 +87,14 @@ class userList{ renderUser(user, color){ var userEntry = document.createElement('p'); - userEntry.innerText = user; - userEntry.id = `user-entry-${user}`; + userEntry.innerText = user.user; + userEntry.id = `user-entry-${user.user}`; + + //Override color with flair + if(user.flair != ""){ + color = `flair-${user.flair}`; + } + userEntry.classList.add("chat-panel-users","user-entry",color); this.userList.appendChild(userEntry);