From b43aa731ca1029323df8398bb836b1982fe1acac Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Wed, 2 Apr 2025 01:52:02 +0000 Subject: [PATCH] improve livechat --- .../app/src/components/LiveChat/index.jsx | 558 +++++++++--------- .../app/src/components/LiveChat/index.less | 213 +++---- 2 files changed, 399 insertions(+), 372 deletions(-) diff --git a/packages/app/src/components/LiveChat/index.jsx b/packages/app/src/components/LiveChat/index.jsx index 17fec96a..f53dc31f 100755 --- a/packages/app/src/components/LiveChat/index.jsx +++ b/packages/app/src/components/LiveChat/index.jsx @@ -8,327 +8,353 @@ import SessionModel from "@models/session" import "./index.less" const Line = (props) => { - const { user, content } = props + const { user, content } = props - return
-
-

{user.fullName ?? user.username}

-
-
- {content} -
-
+ return ( +
+
+

{user.fullName ?? user.username}

+
+
+ {content} +
+
+ ) } export default class LiveChat extends React.Component { - state = { - joining: true, - roomInfo: null, + state = { + joining: true, + roomInfo: null, - timeline: [], - temporalTimeline: [], - maxTemporalLines: this.props.maxTemporalLines ?? 10, + timeline: [], + temporalTimeline: [], + maxTemporalLines: this.props.maxTemporalLines ?? 10, - lastSentMessage: null, - writtedMessage: "", - } + lastSentMessage: null, + writtedMessage: "", + } - debouncedIntervalTimelinePurge = null + debouncedIntervalTimelinePurge = null - timelineRef = React.createRef() + timelineRef = React.createRef() - socket = app.cores.api.client().sockets.chats + socket = app.cores.api.client().ws.sockets.get("chats") - roomEvents = { - "room:message": (message) => { - if (message.content === this.state.lastSentMessage) { - console.timeEnd("[CHATROOM] SUBMIT:MESSAGE") - } + roomEvents = { + "room:message": (message) => { + if (message.content === this.state.lastSentMessage) { + console.timeEnd("[CHATROOM] SUBMIT:MESSAGE") + } - this.pushToTimeline(message) - }, - "room:joined": (info) => { - console.log("[CHATROOM] Room joined", info) + this.pushToTimeline(message) + }, + "room:joined": (info) => { + console.log("[CHATROOM] Room joined", info) - this.setState({ - joining: false, - roomInfo: info, - }) - }, - "room:leave": (info) => { - console.log("[CHATROOM] Room left", info) + this.setState({ + joining: false, + roomInfo: info, + }) + }, + "room:leave": (info) => { + console.log("[CHATROOM] Room left", info) - this.setState({ - joining: true, - roomInfo: null, - }) - } - } + this.setState({ + joining: true, + roomInfo: null, + }) + }, + } - joinSocketRoom = async () => { - if (!SessionModel.token) { - return this.setState({ - noAuthed: true, - }) - } - - console.log(`[CHATROOM] Joining socket room [${this.props.id}]...`) + joinSocketRoom = async () => { + if (!SessionModel.token) { + return this.setState({ + noAuthed: true, + }) + } - for (const [eventName, eventHandler] of Object.entries(this.roomEvents)) { - this.socket.on(eventName, eventHandler) - } + console.log(`[CHATROOM] Joining socket room [${this.props.id}]...`) - await this.setState({ - joining: true, - }) + for (const [eventName, eventHandler] of Object.entries( + this.roomEvents, + )) { + this.socket.on(eventName, eventHandler) + } - this.socket.emit( - "join:room", - { - room: this.props.id, - }, - (error, info) => { - if (error) { - this.setState({ connectionEnd: true }) + await this.setState({ + joining: true, + }) - return console.error("Error joining room", error) - } - } - ) - } + this.socket.emit( + "join:room", + { + room: this.props.id, + }, + (error, info) => { + if (error) { + this.setState({ connectionEnd: true }) - leaveSocketRoom = () => { - if (this.state.connectionEnd) { - return false - } - - console.log(`[CHATROOM] Leaving socket room [${this.props.id}]...`) + return console.error("Error joining room", error) + } + }, + ) + } - for (const [eventName, eventHandler] of Object.entries(this.roomEvents)) { - this.socket.off(eventName, eventHandler) - } + leaveSocketRoom = () => { + if (this.state.connectionEnd) { + return false + } - this.socket.emit("leave:room") - } + console.log(`[CHATROOM] Leaving socket room [${this.props.id}]...`) - submitMessage = (message) => { - console.time("[CHATROOM] SUBMIT:MESSAGE") + for (const [eventName, eventHandler] of Object.entries( + this.roomEvents, + )) { + this.socket.off(eventName, eventHandler) + } - this.socket.emit("room:send:message", { - message - }) + this.socket.emit("leave:room") + } - // remove writted message - this.setState({ - lastSentMessage: message, - writtedMessage: "" - }) - } + submitMessage = (message) => { + console.time("[CHATROOM] SUBMIT:MESSAGE") - pushToTimeline = (message) => { - const { timeline } = this.state + this.socket.emit("room:send:message", { + message, + }) - if (typeof message.key === "undefined") { - message.key = this.state.timeline.length - } + // remove writted message + this.setState({ + lastSentMessage: message, + writtedMessage: "", + }) + } - this.setState({ - timeline: [...timeline, message] - }) + pushToTimeline = (message) => { + const { timeline } = this.state - if (this.props.floatingMode) { - if (this.state.temporalTimeline.length >= this.state.maxTemporalLines) { - this.setState({ - temporalTimeline: this.state.temporalTimeline.slice(1) - }) - } + if (typeof message.key === "undefined") { + message.key = this.state.timeline.length + } - // calculate duration based on message length (Minimum 3 second, maximum 10 seconds) - const calculatedDuration = Math.min(Math.max(message.content.length * 0.1, 3), 10) * 1000 + this.setState({ + timeline: [...timeline, message], + }) - const temporalLine = { - expireTime: Date.now() + calculatedDuration, - duration: calculatedDuration, - messageKey: message.key - } + if (this.props.floatingMode) { + if ( + this.state.temporalTimeline.length >= + this.state.maxTemporalLines + ) { + this.setState({ + temporalTimeline: this.state.temporalTimeline.slice(1), + }) + } - this.setState({ - temporalTimeline: [...this.state.temporalTimeline, temporalLine] - }) + // calculate duration based on message length (Minimum 3 second, maximum 10 seconds) + const calculatedDuration = + Math.min(Math.max(message.content.length * 0.1, 3), 10) * 1000 - if (this.debouncedIntervalTimelinePurge) { - clearInterval(this.debouncedIntervalTimelinePurge) - } + const temporalLine = { + expireTime: Date.now() + calculatedDuration, + duration: calculatedDuration, + messageKey: message.key, + } - this.debouncedIntervalTimelinePurge = setInterval(this.purgeLastTemporalLine, 3000) - } + this.setState({ + temporalTimeline: [ + ...this.state.temporalTimeline, + temporalLine, + ], + }) - this.scrollTimelineToBottom() - } + if (this.debouncedIntervalTimelinePurge) { + clearInterval(this.debouncedIntervalTimelinePurge) + } - purgeLastTemporalLine = () => { - if (!this.props.floatingMode) { - return false - } + this.debouncedIntervalTimelinePurge = setInterval( + this.purgeLastTemporalLine, + 3000, + ) + } - const { temporalTimeline } = this.state + this.scrollTimelineToBottom() + } - if (temporalTimeline.length === 0) { - clearInterval(this.debouncedIntervalTimelinePurge) - return false - } + purgeLastTemporalLine = () => { + if (!this.props.floatingMode) { + return false + } - const lastTemporalLine = temporalTimeline[0] + const { temporalTimeline } = this.state - if (lastTemporalLine.expireTime < Date.now()) { - this.setState({ - temporalTimeline: temporalTimeline.slice(1) - }) - } - } + if (temporalTimeline.length === 0) { + clearInterval(this.debouncedIntervalTimelinePurge) + return false + } - handleInputChange = (e) => { - if (e.target.value[0] === " " || e.target.value[0] === "\n") { - e.target.value = e.target.value.slice(1) - } + const lastTemporalLine = temporalTimeline[0] - this.setState({ - writtedMessage: e.target.value - }) - } + if (lastTemporalLine.expireTime < Date.now()) { + this.setState({ + temporalTimeline: temporalTimeline.slice(1), + }) + } + } - handleOnEnter = (e) => { - e.preventDefault() - e.stopPropagation() + handleInputChange = (e) => { + if (e.target.value[0] === " " || e.target.value[0] === "\n") { + e.target.value = e.target.value.slice(1) + } - if (e.target.value.length === 0) { - return - } + this.setState({ + writtedMessage: e.target.value, + }) + } - this.submitMessage(e.target.value) - } + handleOnEnter = (e) => { + e.preventDefault() + e.stopPropagation() - scrollTimelineToBottom = () => { - const scrollingElement = document.getElementById("liveChat_timeline") + if (e.target.value.length === 0) { + return + } - if (scrollingElement) { - scrollingElement.scrollTo({ - top: scrollingElement.scrollHeight, - behavior: "smooth" - }) - } - } + this.submitMessage(e.target.value) + } - componentDidMount = async () => { - this.joinSocketRoom() + scrollTimelineToBottom = () => { + const scrollingElement = document.getElementById("liveChat_timeline") - app.ctx = { - submit: this.submitMessage - } - } + if (scrollingElement) { + scrollingElement.scrollTo({ + top: scrollingElement.scrollHeight, + behavior: "smooth", + }) + } + } - componentWillUnmount() { - this.leaveSocketRoom() - - if (this.debouncedIntervalTimelinePurge) { - clearInterval(this.debouncedIntervalTimelinePurge) - } + componentDidMount = async () => { + this.joinSocketRoom() - delete app.ctx - } + app.ctx = { + submit: this.submitMessage, + } + } - render() { - if (this.state.connectionEnd) { - return
- -
- } + componentWillUnmount() { + this.leaveSocketRoom() - if (this.state.connecting) { - return
- -
- } + if (this.debouncedIntervalTimelinePurge) { + clearInterval(this.debouncedIntervalTimelinePurge) + } - if (this.props.floatingMode) { - return
- - { - this.state.temporalTimeline.map((line, index) => { - return - - - }) - } - -
- } + delete app.ctx + } - if (this.state.noAuthed) { - return
- -
- } + render() { + if (this.state.connectionEnd) { + return ( +
+ +
+ ) + } - return
- { - !this.props.compact && this.state.timeline.length === 0 && - } + if (this.state.connecting) { + return ( +
+ +
+ ) + } - { - this.props.compact && this.state.timeline.length === 0 &&

- Welcome to the room -

- } + if (this.props.floatingMode) { + return ( +
+ + {this.state.temporalTimeline.map((line, index) => { + return ( + + + + ) + })} + +
+ ) + } - { - this.state.timeline.length !== 0 &&
- { - this.state.timeline.map((line, index) => { - return - }) - } -
- } + if (this.state.noAuthed) { + return ( +
+ +
+ ) + } -
- -
-
- } -} \ No newline at end of file + return ( +
+ {!this.props.compact && this.state.timeline.length === 0 && ( + + )} + + {this.props.compact && this.state.timeline.length === 0 && ( +

Welcome to the room

+ )} + + {this.state.timeline.length !== 0 && ( +
+ {this.state.timeline.map((line, index) => { + return + })} +
+ )} + +
+ +
+
+ ) + } +} diff --git a/packages/app/src/components/LiveChat/index.less b/packages/app/src/components/LiveChat/index.less index b42da1b7..4e548769 100755 --- a/packages/app/src/components/LiveChat/index.less +++ b/packages/app/src/components/LiveChat/index.less @@ -1,153 +1,154 @@ .liveChat { - position: relative; + position: relative; - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - width: 100%; - height: 100%; + width: 100%; + height: 100%; - overflow: hidden; + overflow: hidden; - transition: all 250ms ease-in-out; + transition: all 250ms ease-in-out; - &.empty { - justify-content: center; - align-items: center; + &.empty { + justify-content: center; + align-items: center; - &.compact { - height: 5vh !important; - } - } + &.compact { + height: 5vh !important; + } + } - &.compact { - height: 15vh; + &.compact { + height: 15vh; - justify-content: flex-end; + justify-content: flex-end; - .liveChat_timeline { - position: relative; + .liveChat_timeline { + position: relative; - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - padding: 0; - margin: 0; + padding: 0; + margin: 0; - margin-bottom: 10px; + gap: 5px; - gap: 5px; + .textRoom_line { + padding: 0; + margin: 0; - .textRoom_line { - padding: 0; - margin: 0; + h4 { + font-size: 0.8rem; + } + } + } - h4 { - font-size: 0.8rem; - } - } - } + .liveChat_textInput { + position: relative; - .liveChat_textInput { - position: relative; + bottom: 0; + left: 0; - bottom: 0; - left: 0; + margin-bottom: 0; + } + } - margin-bottom: 0; - } - } + &.floating { + .textRoom_line { + display: relative; - &.floating { - .textRoom_line { - display: relative; + background-color: rgba(var(--layoutBackgroundColor), 0.7); + backdrop-filter: blur(20px); + } - background-color: rgba(var(--layoutBackgroundColor), 0.7); - backdrop-filter: blur(20px); - } + .liveChat_timeline { + display: flex; - .liveChat_timeline { - display: flex; + flex-direction: column; + padding-top: 0; - flex-direction: column; - padding-top: 0; + overflow-x: hidden; + overflow-y: hidden; - overflow-x: hidden; - overflow-y: hidden; + //justify-content: flex-end; - //justify-content: flex-end; + margin-bottom: 0; - margin-bottom: 0; + transition: all 250ms ease-in-out; + } + } - transition: all 250ms ease-in-out; - } - } + .liveChat_timeline { + position: relative; - .liveChat_timeline { - position: relative; - height: 100%; + display: flex; + flex-direction: column; - overflow-y: scroll; + gap: 10px; - margin-bottom: 70px; - } + height: 100%; + overflow-y: scroll; - .liveChat_textInput { - position: absolute; - bottom: 0; - left: 0; + margin-bottom: 70px; + } - margin-bottom: 20px; + .liveChat_textInput { + position: absolute; + bottom: 0; + left: 0; - width: 100%; - height: fit-content; + margin-bottom: 20px; - color: var(--text-color); + width: 100%; + height: fit-content; - .ant-input-textarea-show-count::after { - color: var(--text-color); - } - } + color: var(--text-color); + + .ant-input-textarea-show-count::after { + color: var(--text-color); + } + } } .textRoom_line { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - padding: 5px 10px; - margin-bottom: 10px; + padding: 5px 7px; + border-radius: 12px; - border-radius: 12px; + background-color: rgba(var(--bg_color_1), 0.7); - background-color: var(--background-color-accent); + h1, + h2, + h3, + h4, + span { + margin: 0; + user-select: text; + } - h1, - h2, - h3, - h4, - span { - margin: 0; - user-select: text; - } + .textRoom_line_user { + font-weight: bold; + font-size: 0.9rem; - .textRoom_line_user { - font-weight: bold; - font-size: 1rem; - color: var(--background-color-contrast); + color: var(--text-color); + overflow: hidden; - overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } - text-overflow: ellipsis; - white-space: nowrap; - } + .textRoom_line_content { + font-size: 0.8rem; + color: var(--text-color); - .textRoom_line_content { - font-size: 0.8rem; - color: var(--text-color); + padding: 0 10px; - padding: 0 10px; - - word-break: break-all; - white-space: pre-wrap; - } -} \ No newline at end of file + word-break: break-all; + white-space: pre-wrap; + } +}