From d8cb1c7c11b961d075d5112f6da5183d18cc1455 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 7 Sep 2023 19:24:26 -0700 Subject: [PATCH] Refactor more Vue components Spin out components for: * MessageBox: draw a chat message in the chat history panel as well as reused in the Report Modal. * WhoListRow: provides a consistent UX for the Who List and Watching tab. On the Watching tab, the video button is replaced with the boot from video. Other changes: * Move VideoFlag into its own separate ES module. * Emoji available reactions are moved into MessageBox. * On WhoListRow: usernames are clickable to also open their profile page. * On WhoListRow: the Watching tab is now sortable and follows the user's sort selection like the Online tab does. --- src/App.vue | 410 ++++++++++----------------------- src/assets/main.css | 33 --- src/components/MessageBox.vue | 293 +++++++++++++++++++++++ src/components/ReportModal.vue | 50 ++-- src/components/WhoListRow.vue | 204 ++++++++++++++++ src/lib/VideoFlag.js | 12 + 6 files changed, 651 insertions(+), 351 deletions(-) create mode 100644 src/components/MessageBox.vue create mode 100644 src/components/WhoListRow.vue create mode 100644 src/lib/VideoFlag.js diff --git a/src/App.vue b/src/App.vue index 09f3a78..502ea0b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,8 +4,11 @@ import TheWelcome from './components/TheWelcome.vue' import LoginModal from './components/LoginModal.vue'; import ExplicitOpenModal from './components/ExplicitOpenModal.vue'; import ReportModal from './components/ReportModal.vue'; +import MessageBox from './components/MessageBox.vue'; +import WhoListRow from './components/WhoListRow.vue'; import LocalStorage from './lib/LocalStorage'; +import VideoFlag from './lib/VideoFlag'; import { SoundEffects, DefaultSounds } from './lib/sounds'; import { isAppleWebkit } from './lib/browsers'; @@ -33,6 +36,8 @@ export default { LoginModal, ExplicitOpenModal, ReportModal, + MessageBox, + WhoListRow, }, data() { return { @@ -77,14 +82,6 @@ export default { audioContext: null, audioTracks: {}, }, - reactions: [ - ['❤ī¸', '👍', '😂', '😉', 'đŸ˜ĸ', '😡', 'đŸĨ°'], - ['😘', '👎', '☚ī¸', '😭', '🤔', '🙄', '🤩'], - ['👋', 'đŸ”Ĩ', '😈', '🍑', '🍆', 'đŸ’Ļ', '🍌'], - ['😋', '⭐', '😇', '😴', '😱', '👀', '🎃'], - ['🤮', 'đŸĨŗ', '🙏', 'đŸ¤Ļ', '💩', 'đŸ¤¯', 'đŸ’¯'], - ['😏', '🙈', '🙉', '🙊', '☀ī¸', '🌈', '🎂'], - ], // Cached blocklist for the current user sent by your website. CachedBlocklist: CachedBlocklist, @@ -175,15 +172,7 @@ export default { }, // Video flag constants (sync with values in messages.go) - VideoFlag: { - Active: 1 << 0, - NSFW: 1 << 1, - Muted: 1 << 2, - IsTalking: 1 << 3, - MutualRequired: 1 << 4, - MutualOpen: 1 << 5, - VipOnly: 1 << 6, - }, + VideoFlag: VideoFlag, // WebRTC sessions with other users. WebRTC: { @@ -571,6 +560,63 @@ export default { result = result.reverse(); } + // Default ordering from ChatServer = a-z + return result; + }, + sortedWatchingList() { + let result = []; + for (let username of Object.keys(this.webcam.watching)) { + let user = this.getUser(username); + result.push(user); + } + + switch (this.whoSort) { + case "broadcasting": + result.sort((a, b) => { + return (b.video & this.VideoFlag.Active) - (a.video & this.VideoFlag.Active); + }); + break; + case "nsfw": + result.sort((a, b) => { + let left = (a.video & (this.VideoFlag.Active | this.VideoFlag.NSFW)), + right = (b.video & (this.VideoFlag.Active | this.VideoFlag.NSFW)); + return right - left; + }); + break; + case "status": + result.sort((a, b) => { + if (a.status === b.status) return 0; + return b.status < a.status ? -1 : 1; + }); + break; + case "op": + result.sort((a, b) => { + return b.op - a.op; + }); + break; + case "emoji": + result.sort((a, b) => { + if (a.emoji === b.emoji) return 0; + return a.emoji < b.emoji ? -1 : 1; + }) + break; + case "login": + result.sort((a, b) => { + return b.loginAt - a.loginAt; + }); + break; + case "gender": + result.sort((a, b) => { + if (a.gender === b.gender) return 0; + let left = a.gender || 'z', + right = b.gender || 'z'; + return left < right ? -1 : 1; + }) + break; + case "z-a": + result = result.reverse(); + } + // Default ordering from ChatServer = a-z return result; }, @@ -817,7 +863,6 @@ export default { // WhoList updates. onWho(msg) { this.whoList = msg.whoList; - this.whoMap = {}; if (this.whoList == undefined) { this.whoList = []; @@ -1460,6 +1505,16 @@ export default { isUsernameOnline(username) { return this.whoMap[username] != undefined; }, + getUser(username) { + // Return the full User object from the Who List, or a dummy placeholder if not online. + if (this.whoMap[username] != undefined) { + return this.whoMap[username]; + } + + return { + username: username, + }; + }, avatarForUsername(username) { if (this.whoMap[username] != undefined && this.whoMap[username].avatar) { return this.avatarURL(this.whoMap[username]); @@ -2050,6 +2105,9 @@ export default { this.closeVideo(username, "answerer"); } + // Remove them from our list. + delete(this.webcam.watching[username]); + this.ChatClient( `You have booted ${username} off your camera. They will no longer be able ` + `to connect to your camera, or even see that your camera is active at all -- ` + @@ -3428,169 +3486,25 @@ export default { -
-
- -
-
-
- - - - {{ nicknameForUsername(msg.username) }} - -
-
- {{ prettyDate(msg.at) - }} -
-
- - -
-
- - - @{{ msg.username }} - - @{{ msg.username }} - - internal -
-
-
-
- - - @{{ msg.username }} - - @{{ msg.username }} - - internal -
- -
- - - - - - - - - - - -
-
-
-
- - -
- -
- -
- - - -
- - -
- {{ msg.message }} -
- - -
- - {{ emoji }} {{ users.length }} - -
-
- -
+ @@ -3726,116 +3640,40 @@ export default { diff --git a/src/assets/main.css b/src/assets/main.css index e8667cd..08fbe90 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -1,35 +1,2 @@ @import './base.css'; -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - - font-weight: normal; -} - -a, -.green { - text-decoration: none; - color: hsla(160, 100%, 37%, 1); - transition: 0.4s; -} - -@media (hover: hover) { - a:hover { - background-color: hsla(160, 100%, 37%, 0.2); - } -} - -@media (min-width: 1024px) { - body { - display: flex; - place-items: center; - } - - #app { - display: grid; - grid-template-columns: 1fr 1fr; - padding: 0 2rem; - } -} diff --git a/src/components/MessageBox.vue b/src/components/MessageBox.vue new file mode 100644 index 0000000..2086f5a --- /dev/null +++ b/src/components/MessageBox.vue @@ -0,0 +1,293 @@ + + + + + diff --git a/src/components/ReportModal.vue b/src/components/ReportModal.vue index 4c50f3b..04ce5db 100644 --- a/src/components/ReportModal.vue +++ b/src/components/ReportModal.vue @@ -1,4 +1,6 @@ + + + + diff --git a/src/lib/VideoFlag.js b/src/lib/VideoFlag.js new file mode 100644 index 0000000..ac93fa7 --- /dev/null +++ b/src/lib/VideoFlag.js @@ -0,0 +1,12 @@ +// Video flag constants (sync with values in messages.go) +const VideoFlag = { + Active: 1 << 0, + NSFW: 1 << 1, + Muted: 1 << 2, + IsTalking: 1 << 3, + MutualRequired: 1 << 4, + MutualOpen: 1 << 5, + VipOnly: 1 << 6, +}; + +export default VideoFlag;