diff --git a/assets/HypertextPages/chat.nytl.html b/assets/HypertextPages/chat.nytl.html index 61b8e9b..fb5a2e1 100644 --- a/assets/HypertextPages/chat.nytl.html +++ b/assets/HypertextPages/chat.nytl.html @@ -45,8 +45,16 @@
-
-
+
+
+
+
+
+ Loading backward... +
+
+ Loading forward... +
diff --git a/assets/css/chat.css b/assets/css/chat.css index 3e9990c..448e434 100644 --- a/assets/css/chat.css +++ b/assets/css/chat.css @@ -6,46 +6,66 @@ body, html { position: relative; flex: 1; background-color: #f1f1f1; - color: black; overflow: hidden; } -.message-box-mine { +.message-supercontainer{ position: absolute; - right: 5px; + width: 100%; + left: 0; + background-color: rgba(150, 0, 100, 50); + /*display: flex;*/ + /*flex-direction: row;*/ + /*justify-content: center;*/ +} + +.message-box{ + /*display: inline-block;*/ + padding: 5px; +} + +.message-box-mine { + margin-right: 5px; + margin-left: auto; max-width: 400px; border: 2px solid #82a173; padding: 5px; background-color: #cdff9b; color: black; + /*justify-self: flex-end;*/ } .message-box-alien { - position: absolute; - left: 5px; + margin-left: 5px; + margin-right: auto; max-width: 400px; border: 2px solid dimgrey; padding: 5px; background-color: white; color: black; + /*justify-self: flex-start;*/ } /* Only non-system messages can be deleted. Deleted messages do not have delete button This class should be used with (and, ofcourse, after) class message-box-my/message-box-alien */ .message-box-deleted { - font-weight: bold; border: 2px solid #cb0005; background-color: #ffc1bc; } +.message-box-deleted .message-box-msg{ + font-weight: bold; +} + .message-box-system { - position: absolute; - left: 15px; + margin-left: auto; + margin-right: auto; max-width: 500px; padding: 4px; background-color: #2d2d2d; color: white; font-weight: bold; + justify-self: center; } /* in #chat-widget .message-box */ @@ -80,6 +100,7 @@ body, html { width: 20px; padding: 2px; cursor: pointer; + display: inline; } .message-box-msg{ @@ -110,4 +131,12 @@ body, html { margin-right: auto; max-height: 20%; word-wrap: break-word; +} + +.loading-spinner{ + margin-left: auto; + margin-right: auto; + background-color: rgba(0, 0, 0, 0); + width: 25px; + display: block; } \ No newline at end of file diff --git a/assets/css/debug.css b/assets/css/debug.css index cb5f5b1..017070f 100644 --- a/assets/css/debug.css +++ b/assets/css/debug.css @@ -2,11 +2,7 @@ width: 100%; position: absolute; left: 0; - background-color: rgba(255, 50, 50, 160); - height: 4px; + opacity: 0.3; + height: 3px; z-index: 2; -} - -.chat-debug-rect-top{ - background-color: rgba(148, 0, 211, 160); } \ No newline at end of file diff --git a/assets/gif/loading.gif b/assets/gif/loading.gif new file mode 100644 index 0000000..05e1f57 Binary files /dev/null and b/assets/gif/loading.gif differ diff --git a/assets/js/chat.js b/assets/js/chat.js index 003b223..929764e 100644 --- a/assets/js/chat.js +++ b/assets/js/chat.js @@ -3,6 +3,8 @@ let LocalHistoryId = 0; let members = new Map(); let loadedMessages = new Map(); // messageSt objects +/* +container: EL, box: EL, offset: number (msgPres) */ let visibleMessages = new Map(); // HTMLElement objects let anchoredMsg = -1; @@ -41,15 +43,37 @@ function genSentBaseGMN(){ return Sent; } +function getChatWgSz(){ + let chatWg = document.getElementById("chat-widget"); + return [chatWg.offsetWidth, chatWg.offsetHeight]; +} + +function elSetOffsetInChat(el, offset){ + el.style.bottom = String(offset) + "px"; +} + +function isMissingPrimaryMsgHeap(){ + return lastMsgId >= 0 && anchoredMsg < 0; +} + +function isMissingTopMsgHeap(){ + let [W, H] = getChatWgSz(); + return anchoredMsg >= 0 && (highestPoint < H + softZoneSz && visibleMsgSegStart > 0); +} + +function isMissingBottomMsgHeap(){ + return anchoredMsg >= 0 && (lowestPoint > - softZoneSz && visibleMsgSegEnd < lastMsgId); +} + function updateOffsetOfVisibleMsg(msgId, offset){ - visibleMessages.get(msgId).style.bottom = String(offset) + "px"; + visibleMessages.get(msgId).container.style.bottom = String(offset) + "px"; } function updateOffsetsUpToTop(){ let offset = offsetOfAnchor; for (let curMsg = anchoredMsg; curMsg >= visibleMsgSegStart; curMsg--){ updateOffsetOfVisibleMsg(curMsg, offset); - let height = visibleMessages.get(curMsg).offsetHeight; + let height = visibleMessages.get(curMsg).container.offsetHeight; offset += height + 5; } return offset; @@ -58,7 +82,7 @@ function updateOffsetsUpToTop(){ function updateOffsetsDown(){ let offset = offsetOfAnchor; for (let curMsg = anchoredMsg + 1; curMsg <= visibleMsgSegEnd; curMsg++){ - let height = visibleMessages.get(curMsg).offsetHeight; + let height = visibleMessages.get(curMsg).container.offsetHeight; offset -= (height + 5); updateOffsetOfVisibleMsg(curMsg, offset); } @@ -72,28 +96,47 @@ function updateOffsetsSane(){ lowestPoint = updateOffsetsDown(); } +function heightOfPreloadGhost(){ + let [W, H] = getChatWgSz(); + return Math.min(H * 0.9, Math.max(H * 0.69, 30)); +} + function updateOffsets(){ - if (anchoredMsg < 0) - return; - updateOffsetsSane() - let winTop = document.getElementById("chat-widget").offsetHeight; - if (lowestPoint > chatPadding || (highestPoint - lowestPoint) <= winTop){ - bumpedAtTheBottom = true; - anchoredMsg = visibleMsgSegEnd; - offsetOfAnchor = chatPadding; - updateOffsetsSane(); - } else if (highestPoint < winTop - chatPadding) { - console.log("Advancing by " + (winTop - chatPadding - highestPoint)) - offsetOfAnchor += (winTop - chatPadding - highestPoint); + let spinnerTop = document.getElementById("top-loading"); + let spinnerBottom = document.getElementById("bottom-loading"); + let SbH = spinnerBottom.offsetHeight; + if (anchoredMsg < 0){ + hideHTMLElement(spinnerBottom); + elSetOffsetInChat(spinnerTop, chatPadding); + setElementVisibility(spinnerTop, isMissingPrimaryMsgHeap()); + } else { updateOffsetsSane(); + let [W, H] = getChatWgSz(); + let lowestLowestPoint = isMissingBottomMsgHeap() ? lowestPoint - heightOfPreloadGhost(): lowestPoint; + let highestHighestPoint = isMissingTopMsgHeap() ? highestPoint + heightOfPreloadGhost() : highestPoint; + if (lowestLowestPoint > chatPadding || (highestHighestPoint - lowestLowestPoint) <= H - chatPadding * 2) { + bumpedAtTheBottom = true; + offsetOfAnchor += (-lowestLowestPoint + chatPadding); + updateOffsetsSane(); + } else if (highestHighestPoint < H - chatPadding) { + offsetOfAnchor += (-highestHighestPoint + (H - chatPadding)); + updateOffsetsSane(); + } + /* Messages weere updated (and only them). They were talking with ghosts. + Now we are trying to show spinners of ghosts */ + elSetOffsetInChat(spinnerTop, highestPoint); + setElementVisibility(spinnerTop, isMissingTopMsgHeap()); + elSetOffsetInChat(spinnerBottom, lowestPoint - SbH); + setElementVisibility(spinnerBottom, isMissingBottomMsgHeap()); } } function shouldShowDeleteMesgBtn(messageSt){ - return !messageSt.isSystem && messageSt.exists && ( + return !messageSt.isSystem && messageSt.exists && (messageSt.myRoleHere !== userChatRoleReadOnly) &&( messageSt.myRoleHere === userChatRoleAdmin || messageSt.senderUserId === userinfo.uid); } +// todo: fix messageboxes function getMsgTypeClassSenderBased(messageSt){ if (messageSt.isSystem) return "message-box-system" @@ -106,12 +149,13 @@ function getMsgFullTypeClassName(messageSt){ return getMsgTypeClassSenderBased(messageSt) + (messageSt.exists ? "" : " message-box-deleted"); } -/* Two things can be updated: messages existance and delete button visibility */ -function updateMessageBox(id, box, messageSt){ - box.querySelector(".message-box-button-delete").style.display = shouldShowDeleteMesgBtn(messageSt) ? "block" : "none"; +/* Two things can be updated: messages existance and delete button visibility +* Supercontainer.container is persistent, Supercontainer.box can change it's class */ +function updateMessageSupercontainer(supercontainer, messageSt){ + let box = supercontainer.box; + setElementVisibility(box.querySelector(".message-box-button-delete"), shouldShowDeleteMesgBtn(messageSt), "inline"); box.className = getMsgFullTypeClassName(messageSt); - // Notice, that no check of previous state is performed. Double loading is a rare event, - // and I can afford to be slow + // Notice, that no check of previous state is performed. Double loading is a rare event, I can afford to be slow if (!messageSt.exists) box.querySelector(".message-box-msg").innerText = msgErased; } @@ -136,8 +180,12 @@ function decodeSystemMessage(text){ return "... Bad log ..."; } -function convertMessageStToBox(messageSt){ +function convertMessageStToSupercontainer(messageSt){ + let container = document.createElement("div"); + container.className = "message-supercontainer"; + let box = document.createElement("div"); + container.appendChild(box); box.className = getMsgFullTypeClassName(messageSt); let ID = messageSt.id; @@ -178,6 +226,7 @@ function convertMessageStToBox(messageSt){ storeHiddenMsgIdForDeletionWin = ID; activatePopupWindowById("msg-deletion-win"); }; + setElementVisibility(inTopPartButtonDelete, shouldShowDeleteMesgBtn(messageSt), "inline"); let inTopPartButtonGetLink = document.createElement("img"); topPart.appendChild(inTopPartButtonGetLink); @@ -202,14 +251,15 @@ function convertMessageStToBox(messageSt){ msgPart.innerText = messageSt.text; } else msgPart.innerText = msgErased; - return box; + + return {'container': container, 'box': box, 'offset': 0}; } function makeVisible(msgId){ - let box = convertMessageStToBox(loadedMessages.get(msgId)); + let supercontainer = convertMessageStToSupercontainer(loadedMessages.get(msgId)); const chatWin = document.getElementById("chat-widget"); - chatWin.appendChild(box); - visibleMessages.set(msgId, box); + chatWin.appendChild(supercontainer.container); + visibleMessages.set(msgId, supercontainer); } function opaNewMessageSt(messageSt){ @@ -217,7 +267,7 @@ function opaNewMessageSt(messageSt){ if (loadedMessages.has(msgId)){ loadedMessages.set(msgId, messageSt); if (visibleMessages.has(msgId)){ - updateMessageBox(msgId, visibleMessages.get(msgId), messageSt); + updateMessageSupercontainer(visibleMessages.get(msgId), messageSt); } } else { loadedMessages.set(msgId, messageSt); @@ -249,15 +299,16 @@ function canISendMessages(){ } function updateLocalStateFromChatUpdRespBlind(chatUpdResp){ + console.log(anchoredMsg, offsetOfAnchor, chatUpdResp); LocalHistoryId = chatUpdResp.HistoryId; for (let memberSt of chatUpdResp.members){ let id = memberSt.userId; if (id === userinfo.uid && myRoleHere !== memberSt.roleHere) { myRoleHere = memberSt.roleHere; - for (let [msgId, box] of visibleMessages){ - updateMessageBox(msgId, loadedMessages.get(msgId), box); + for (let [msgId, sc] of visibleMessages){ + updateMessageSupercontainer(sc, loadedMessages.get(msgId)); } - document.getElementById("message-input").style.display = (canISendMessages() ? "block" : "none"); + setElementVisibility(document.getElementById("message-input"), canISendMessages()); } } for (let memberSt of chatUpdResp.members){ @@ -284,31 +335,16 @@ async function requestMessageNeighbours(fromMsg, direction){ } function needToLoadWhitespace(){ - let winTop = document.getElementById("chat-widget").offsetHeight; - if (anchoredMsg === -1){ - if (lastMsgId >= 0) - console.log("NEEDED 1", anchoredMsg, lastMsgId); - return lastMsgId >= 0; - } else if (highestPoint < winTop + softZoneSz && visibleMsgSegStart > 0){ - console.log("NEEDED 2", visibleMsgSegStart); - return true; - } else if (lowestPoint > 0 - softZoneSz && visibleMsgSegEnd < lastMsgId){ - console.log("NEEDED 3"); - return true; - } - return false; + return isMissingPrimaryMsgHeap() || isMissingTopMsgHeap() || isMissingBottomMsgHeap(); } -async function tryLoadWhitespace(){ - let winTop = document.getElementById("chat-widget").offsetHeight; - console.log(anchoredMsg, lastMsgId); - if (anchoredMsg === -1){ - if (lastMsgId !== -1){ - await requestMessageNeighbours(-1, "backward"); - } - } else if (highestPoint < winTop + softZoneSz && visibleMsgSegStart > 0){ +async function tryLoadWhitespaceSingle(){ + console.log('tryLoadWhitespaceSingle'); + if (isMissingPrimaryMsgHeap()){ + await requestMessageNeighbours(-1, "backward"); + } else if (isMissingTopMsgHeap()){ await requestMessageNeighbours(visibleMsgSegStart, "backward"); - } else if (lowestPoint > 0 - softZoneSz && visibleMsgSegEnd < lastMsgId){ + } else if (isMissingBottomMsgHeap()){ await requestMessageNeighbours(visibleMsgSegEnd, "forward"); } } @@ -317,10 +353,8 @@ async function loadWhitespaceMultitry(){ if (needToLoadWhitespace()){ cancelMainloopTimeout(); do { - console.trace(); - console.log("Normalnie ludi ne spyat"); try { - await tryLoadWhitespace(); + await tryLoadWhitespaceSingle(); await sleep(100); } catch (e) { console.error(e); @@ -368,7 +402,7 @@ async function UPDATE(){ let Recv = await apiRequest("chatPollEvents", genSentBase()); await updateLocalStateFromRecv(Recv); } -// __guestMainloopPollerAction = UPDATE(); +__guestMainloopPollerAction = UPDATE; window.onload = function (){ console.log("Page was loaded"); @@ -397,8 +431,10 @@ window.onload = function (){ let chatWg = document.getElementById("chat-widget"); let chatWgDebugLinesFnc = function (){ let H = chatWg.offsetHeight; - document.getElementById("debug-line-lowest").style.bottom = String(-softZoneSz) + "px"; - document.getElementById("debug-line-highest").style.bottom = String(H + softZoneSz) + "px"; + elSetOffsetInChat(document.getElementById("debug-line-lowest"), -softZoneSz); + elSetOffsetInChat(document.getElementById("debug-line-highest"), H + softZoneSz); + elSetOffsetInChat(document.getElementById("debug-line-top-padding"), H - chatPadding); + elSetOffsetInChat(document.getElementById("debug-line-bottom-padding"), chatPadding) }; window.addEventListener("resize", chatWgDebugLinesFnc); chatWgDebugLinesFnc(); diff --git a/assets/js/common.js b/assets/js/common.js index ea33eed..3b48ded 100644 --- a/assets/js/common.js +++ b/assets/js/common.js @@ -59,3 +59,15 @@ function roleToColor(role) { // todo: replace it with translation const msgErased = "[ ERASED ]"; + +function hideHTMLElement(el){ + el.style.display = "none"; +} + +function showHTMLElement(el){ + el.style.display = "block"; +} + +function setElementVisibility(el, isVisible, howVisible = "block"){ + el.style.display = isVisible ? howVisible : "none"; +} \ No newline at end of file diff --git a/src/web_chat/iu9_ca_web_chat_lib/run.cpp b/src/web_chat/iu9_ca_web_chat_lib/run.cpp index ad05db5..2fe2b29 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/run.cpp +++ b/src/web_chat/iu9_ca_web_chat_lib/run.cpp @@ -41,6 +41,7 @@ namespace iu9cawebchat { samI.update({ een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} }, een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} }, + een9::StaticAssetManagerRule{assets_dir + "/gif", "/assets/gif", {{".gif", "image/gif"}} }, een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", { {".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"} } },