diff --git a/src/chatview.cpp b/src/chatview.cpp index b7ee941edd5724c8f1c8cfee557bb1d7dc52521d..3d873e1ff6c1b5d92741aee721d897557f99c882 100644 --- a/src/chatview.cpp +++ b/src/chatview.cpp @@ -60,6 +60,7 @@ struct _ChatViewPrivate AccountInfoPointer const * accountInfo_; QMetaObject::Connection new_interaction_connection; + QMetaObject::Connection interaction_removed; QMetaObject::Connection update_interaction_connection; QMetaObject::Connection update_add_to_conversations; @@ -90,6 +91,7 @@ chat_view_dispose(GObject *object) QObject::disconnect(priv->new_interaction_connection); QObject::disconnect(priv->update_interaction_connection); + QObject::disconnect(priv->interaction_removed); QObject::disconnect(priv->update_add_to_conversations); /* Destroying the box will also destroy its children, and we wouldn't @@ -269,6 +271,14 @@ webkit_chat_container_script_dialog(GtkWidget* webview, gchar *interaction, Chat } } else if (order.find("ADD_TO_CONVERSATIONS") == 0) { button_add_to_conversations_clicked(self); + } else if (order.find("DELETE_INTERACTION:") == 0) { + try { + auto interactionId = std::stoull(order.substr(std::string("DELETE_INTERACTION:").size())); + if (!priv->conversation_) return; + (*priv->accountInfo_)->conversationModel->clearInteractionFromConversation(priv->conversation_->uid, interactionId); + } catch (...) { + g_warning("delete interaction failed: can't find %s", order.substr(std::string("DELETE_INTERACTION:").size()).c_str()); + } } } @@ -341,6 +351,16 @@ update_interaction(ChatView* self, uint64_t interactionId, const lrc::api::inter ); } +static void +remove_interaction(ChatView* self, uint64_t interactionId) +{ + ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self); + webkit_chat_container_remove_interaction( + WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), + interactionId + ); +} + static void load_participants_images(ChatView *self) { @@ -415,7 +435,7 @@ webkit_chat_container_ready(ChatView* self) &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::newInteraction, [self, priv](const std::string& uid, uint64_t interactionId, lrc::api::interaction::Info interaction) { if (!priv->conversation_) return; - if(uid == priv->conversation_->uid) { + if (uid == priv->conversation_->uid) { print_interaction_to_buffer(self, interactionId, interaction); } }); @@ -424,11 +444,20 @@ webkit_chat_container_ready(ChatView* self) &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::interactionStatusUpdated, [self, priv](const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) { if (!priv->conversation_) return; - if(uid == priv->conversation_->uid) { + if (uid == priv->conversation_->uid) { update_interaction(self, msgId, msg); } }); + priv->interaction_removed = QObject::connect( + &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::interactionRemoved, + [self, priv](const std::string& convUid, uint64_t interactionId) { + if (!priv->conversation_) return; + if (convUid == priv->conversation_->uid) { + remove_interaction(self, interactionId); + } + }); + if (!priv->conversation_) return; auto contactUri = priv->conversation_->participants.front(); try { diff --git a/src/webkitchatcontainer.cpp b/src/webkitchatcontainer.cpp index 35b51c48ed409d577dca7c9e64accdd9fe0f5d51..0f950080d6e76da3c1bdea86e21fee55a3de251a 100644 --- a/src/webkitchatcontainer.cpp +++ b/src/webkitchatcontainer.cpp @@ -632,6 +632,23 @@ webkit_chat_container_update_interaction(WebKitChatContainer *view, g_free(function_call); } +void +webkit_chat_container_remove_interaction(WebKitChatContainer *view, uint64_t interactionId) +{ + WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view); + + gchar* function_call = g_strdup_printf("removeInteraction(%i);", interactionId); + webkit_web_view_run_javascript( + WEBKIT_WEB_VIEW(priv->webview_chat), + function_call, + NULL, + NULL, + NULL + ); + g_free(function_call); +} + + void webkit_chat_container_print_new_interaction(WebKitChatContainer *view, lrc::api::ConversationModel& conversation_model, diff --git a/src/webkitchatcontainer.h b/src/webkitchatcontainer.h index b906ff622fe91d7a3a92233ebc8a6242c7ed3384..1f22d286c245b5322fbc12f1f3b12b8930853bee 100644 --- a/src/webkitchatcontainer.h +++ b/src/webkitchatcontainer.h @@ -48,6 +48,7 @@ void webkit_chat_container_clear (WebKitChatContainer *view void webkit_chat_container_clear_sender_images (WebKitChatContainer *view); void webkit_chat_container_print_new_interaction(WebKitChatContainer *view, lrc::api::ConversationModel& conversation_model, uint64_t msgId, const lrc::api::interaction::Info& interaction); void webkit_chat_container_update_interaction (WebKitChatContainer *view, lrc::api::ConversationModel& conversation_model, uint64_t msgId, const lrc::api::interaction::Info& interaction); +void webkit_chat_container_remove_interaction (WebKitChatContainer *view, uint64_t interactionId); void webkit_chat_container_print_history (WebKitChatContainer *view, lrc::api::ConversationModel& conversation_model, const std::map<uint64_t, lrc::api::interaction::Info> interactions); void webkit_chat_container_set_sender_image (WebKitChatContainer *view, const std::string& sender, const std::string& senderImage); gboolean webkit_chat_container_is_ready (WebKitChatContainer *view); diff --git a/web/chatview.css b/web/chatview.css index 85083f610a97f279da017de23390bb8d85ff6a16..2c41798ddbe3e695350907fc48f3e4fe4513a543 100644 --- a/web/chatview.css +++ b/web/chatview.css @@ -391,13 +391,71 @@ a:hover { -webkit-user-select: auto; } +.menu_interaction +{ + margin: 5px; + padding: 10px; + padding-top: 0; + opacity: 0; + height: 20px; + transition:visibility 0.3s linear,opacity 0.3s linear; +} + +.message_type_contact .menu_interaction +{ + display: none; + visibility: hidden; +} + +.message_type_call .menu_interaction +{ + margin: auto; + padding: 0; + vertical-align: center; +} + +.message_type_call .menu_interaction .dropdown +{ + margin-top: -17px; +} + +.message:hover .menu_interaction +{ + display: block; + opacity: 1; +} + +.dropdown { + display: none; + z-index: 1; + position: absolute; + background-color: #fff; + padding-top: 3px; + padding-bottom: 3px; +} + +.dropdown div +{ + color: #111; + padding: 10px; +} + +.dropdown div:hover +{ + background-color: #ddd; +} + +#showmenu:checked ~ .dropdown{ + display:block; +} + .message_in { - padding-left: 25%; + padding-left: 25%; } .message_out { - padding-right: 25%; - flex-direction: row-reverse; + padding-right: 25%; + flex-direction: row-reverse; } .message_in + .message_in .sender_image { @@ -530,52 +588,52 @@ a:hover { } .timestamp { - display: flex; - justify-content: flex-start; - color: #333; - font-size: 10px; - padding: 5px; + display: flex; + justify-content: flex-start; + color: #333; + font-size: 10px; + padding: 5px; } .timestamp_out { - flex-direction: row-reverse; + flex-direction: row-reverse; } /* Buttons */ .flat-button { - flex: 1; - padding: 0; + flex: 1; + padding: 0; } .left_buttons { - display: flex; - align-self: center; - max-width: 90px; - padding-left: 1em; + display: flex; + align-self: center; + max-width: 90px; + padding-left: 1em; } /* Status */ .status_circle { - fill: #A0A0A0; - -webkit-animation: circle-dance; - -webkit-animation-duration: 0.8s; - -webkit-animation-iteration-count: infinite; - -webkit-animation-direction: alternate; - -webkit-animation-timing-function: ease-in-out; + fill: #A0A0A0; + -webkit-animation: circle-dance; + -webkit-animation-duration: 0.8s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-direction: alternate; + -webkit-animation-timing-function: ease-in-out; } .anim-first { - -webkit-animation-delay: 0.7s; + -webkit-animation-delay: 0.7s; } .anim-second { - -webkit-animation-delay: 0.9s; + -webkit-animation-delay: 0.9s; } .anim-third { - -webkit-animation-delay: 1.1s; + -webkit-animation-delay: 1.1s; } @-webkit-keyframes circle-dance { @@ -590,20 +648,20 @@ a:hover { } .status-x { - stroke-dasharray: 12; - stroke-dashoffset: 12; - -webkit-animation: dash-x; - -webkit-animation-duration: 0.2s; - -webkit-animation-fill-mode: forwards; - -webkit-animation-timing-function: ease-in-out; + stroke-dasharray: 12; + stroke-dashoffset: 12; + -webkit-animation: dash-x; + -webkit-animation-duration: 0.2s; + -webkit-animation-fill-mode: forwards; + -webkit-animation-timing-function: ease-in-out; } .x-first { - -webkit-animation-delay: 0.7s; + -webkit-animation-delay: 0.7s; } .x-second { - -webkit-animation-delay: 0.9s; + -webkit-animation-delay: 0.9s; } @-webkit-keyframes dash-x{ @@ -618,13 +676,13 @@ a:hover { /* Contact + Call interactions */ .message_type_contact .message_wrapper, .message_type_call .message_wrapper { - width: auto; - margin-left: 30%; - margin-right: 30%; - display: flex; - flex-wrap: wrap; - background-color: #f2f2f2; - padding: 0; + width: auto; + margin-left: 30%; + margin-right: 30%; + display: flex; + flex-wrap: wrap; + background-color: #f2f2f2; + padding: 0; } .message_type_contact .message_wrapper:before, @@ -639,94 +697,94 @@ a:hover { .message_type_contact .text, .message_type_call .text { - align-self: center; - font-size: 1.2em; - padding: 1em; + align-self: center; + font-size: 1.2em; + padding: 1em; } /* file interactions */ .message_type_data_transfer .message_wrapper { - padding: 0; - width: 30%; - display: flex; - flex-wrap: wrap; + padding: 0; + width: 30%; + display: flex; + flex-wrap: wrap; } .accept, .refuse { - border-radius: 50%; - cursor: pointer; + border-radius: 50%; + cursor: pointer; } .accept svg, .refuse svg { - padding: 8px; - width: 24px; - height: 24px; + padding: 8px; + width: 24px; + height: 24px; } .accept { - fill: #219d55; + fill: #219d55; } .accept:hover { - fill: white; - background: #219d55; + fill: white; + background: #219d55; } .refuse { - fill: #dc2719; + fill: #dc2719; } .refuse:hover { - fill: white; - background: #dc2719; + fill: white; + background: #dc2719; } .message_type_data_transfer .text { - padding: 1em; - text-align: left; - align-self: left; - max-width: calc(100% - 180px); + padding: 1em; + text-align: left; + align-self: left; + max-width: calc(100% - 180px); } .message_type_data_transfer .filename { - cursor: pointer; - font-size: 1.1em; - overflow: hidden; + cursor: pointer; + font-size: 1.1em; + overflow: hidden; } .message_type_data_transfer .informations { - color: #555; - font-size: 0.8em; + color: #555; + font-size: 0.8em; } .message_progress_bar { - width: 100%; - height: 1em; - position: relative; - overflow: hidden; - background-color: #eee; - border-radius: 0; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset; + width: 100%; + height: 1em; + position: relative; + overflow: hidden; + background-color: #eee; + border-radius: 0; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset; } .message_progress_bar > span { - display: inline; - height: 100%; - background-color: #01a2b8; - position: absolute; - overflow: hidden; + display: inline; + height: 100%; + background-color: #01a2b8; + position: absolute; + overflow: hidden; } /* text interactions */ .message_type_text .message_wrapper { - max-width: 40%; + max-width: 40%; } .message_type_text .message_text { - padding: 0px; + padding: 0px; } .message_text { @@ -737,110 +795,124 @@ a:hover { } pre { - font : inherit; - font-family : inherit; - font-size : inherit; - font-style : inherit; - font-variant : inherit; - font-weight : inherit; - margin: 0; - padding: 0; + font : inherit; + font-family : inherit; + font-size : inherit; + font-style : inherit; + font-variant : inherit; + font-weight : inherit; + margin: 0; + padding: 0; } /* Media interactions */ .media_wrapper img { - max-width: 800px; - max-height: 700px; - margin: 5px 0 0 0; - border-radius: 10px; + max-width: 800px; + max-height: 700px; + margin: 5px 0 0 0; + border-radius: 10px; } .playVideo { - background-color: rgba(0, 0, 0, 0.6); - height: 50px; - width: 50px; - border-radius: 5px; - float: right; - position: absolute; - top: calc(50% - 25px); - left: calc(50% - 25px); - z-index: 3; + background-color: rgba(0, 0, 0, 0.6); + height: 50px; + width: 50px; + border-radius: 5px; + float: right; + position: absolute; + top: calc(50% - 25px); + left: calc(50% - 25px); + z-index: 3; } .containerVideo { - width: 100%; - position: relative; + width: 100%; + position: relative; } .playVideo svg { - height: 40px; - width: 40px; - margin: 5px; + height: 40px; + width: 40px; + margin: 5px; } /* Text interaction */ .failure, .sending { - margin: 10px 10px; - color: #A0A0A0; - opacity: 0; - -webkit-animation-name: fade-in; - -webkit-animation-duration: 0.2s; - -webkit-animation-timing-function: ease-in-out; - -webkit-animation-delay: 0.4s; - -webkit-animation-fill-mode: forwards; - transition: opacity 0.5s ease-in-out; + margin: 10px 10px; + color: #A0A0A0; + opacity: 0; + -webkit-animation-name: fade-in; + -webkit-animation-duration: 0.2s; + -webkit-animation-timing-function: ease-in-out; + -webkit-animation-delay: 0.4s; + -webkit-animation-fill-mode: forwards; + transition: opacity 0.5s ease-in-out; } /* Classic screens */ @media screen and (max-width: 1920px), screen and (max-height: 1080px) { .message_in { - padding-left: 15%; + padding-left: 15%; } .message_out { - padding-right: 15%; + padding-right: 15%; } .message_type_text .message_wrapper { - max-width: 60%; + max-width: 60%; } /* File interactions */ .message_type_data_transfer .message_wrapper { - width: 40%; + width: 40%; } /* Media interactions */ .media_wrapper img { - max-width: 450px; - max-height: 450px; + max-width: 450px; + max-height: 450px; + } + + .menu_interaction + { + margin: 5px; + padding: 2px; + height: 10px; + font-size: 0.7em; + transition:visibility 0.3s linear,opacity 0.3s linear; } } /* lower resolutions */ @media screen and (max-width: 1000px), screen and (max-height: 480px) { .message_in { - padding-left: 0; + padding-left: 0; } .message_out { - padding-right: 0; + padding-right: 0; + } + + .message_type_contact, + .message_type_call { + max-width: 100%; } .message_type_text .message_wrapper { - max-width: 90%; + max-width: 90%; } /* File interactions */ .message_type_data_transfer .message_wrapper { - width: 70%; + width: 70%; } /* Media interactions */ .media_wrapper img { - max-width: 200px; - max-height: 200px; + max-width: 200px; + max-height: 200px; } } @@ -853,24 +925,24 @@ pre { /* File interactions */ .message_type_data_transfer .left_buttons { - max-width: 100%; + max-width: 100%; } .message_type_data_transfer .text { - max-width: 100%; - padding-left: 0; + max-width: 100%; + padding-left: 0; } } .message_type_contact .message_wrapper:hover .timestamp_action, .message_type_call .message_wrapper:hover .timestamp_action { - opacity: 1; + opacity: 1; } .timestamp_action { - display: flex; - align-items: center; - justify-content: center; - opacity:0; - transition:visibility 0.3s linear,opacity 0.3s linear; + margin: auto; + padding: 0; + vertical-align: center; + opacity:0; + transition:visibility 0.3s linear,opacity 0.3s linear; } diff --git a/web/chatview.html b/web/chatview.html index fec9979c5a14c173c94fc7802b37877fd52d5163..46af2c11b7c1fc59a2d84532f9453cab6ec1880a 100644 --- a/web/chatview.html +++ b/web/chatview.html @@ -879,7 +879,7 @@ function updateTextInteraction(message_div, delivery_status) { sending.setAttribute("class", "sending") sending.innerHTML = "<svg overflow=\"hidden\" viewBox=\"0 -2 16 14\" height=\"16px\" width=\"16px\"><circle class=\"status_circle anim-first\" cx=\"4\" cy=\"12\" r=\"1\"/><circle class=\"status_circle anim-second\" cx=\"8\" cy=\"12\" r=\"1\"/><circle class=\"status_circle anim-third\" cx=\"12\" cy=\"12\" r=\"1\"/></svg>" // add sending animation to message; - message_div.appendChild(sending) + message_div.insertBefore(sending, message_div.querySelector(".menu_interaction")) } message_div.querySelector(".message_text").style = "color: #888" break @@ -893,7 +893,7 @@ function updateTextInteraction(message_div, delivery_status) { failure_div.setAttribute("class", "failure") failure_div.innerHTML = "<svg overflow=\"visible\" viewBox=\"0 -2 16 14\" height=\"16px\" width=\"16px\"><path class=\"status-x x-first\" stroke=\"#AA0000\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" fill=\"none\" d=\"M4,4 L12,12\"/><path class=\"status-x x-second\" stroke=\"#AA0000\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" fill=\"none\" d=\"M12,4 L4,12\"/></svg>" // add failure animation to message - message_div.appendChild(failure_div) + message_div.insertBefore(failure_div, message_div.querySelector(".menu_interaction")) } if (sending) sending.style.display = "none" break @@ -979,6 +979,28 @@ function updateContactInteraction(message_div, message_object) { left_buttons.appendChild(status_button) } +/** + * Remove an interaction from the conversation + * @param interaction_id + */ +/* exported removeInteraction */ +function removeInteraction(interaction_id) { + var interaction = document.querySelector(`#message_${interaction_id}`) + var child = interaction + var messages = document.querySelector("#messages") + var i = 0 + while( (child = child.previousSibling) != null ) + i++ + if (interaction) interaction.parentNode.removeChild(interaction) + if (i < messages.children.length) { + var timestampAfter = messages.children[i].classList.contains("timestamp") + var timestampBefore = messages.children[i].classList.contains("timestamp") + if (timestampAfter && timestampBefore) { + messages.children[i].parentNode.removeChild(messages.children[i]) + } + } +} + /** * Add a message to the conversation. * @param message_object to treat @@ -1064,6 +1086,44 @@ function addOrUpdateMessage(message_object, new_message, insert_after = true) { message_div.appendChild(temp) } + const menu_element = document.createElement("div") + menu_element.setAttribute("class", "menu_interaction") + menu_element.innerHTML = "<label for=\"showmenu\">\ + <svg fill=\"#888888\" height=\"12\" viewBox=\"0 0 24 24\" width=\"12\" xmlns=\"http://www.w3.org/2000/svg\">\ + <path d=\"M0 0h24v24H0z\" fill=\"none\"/>\ + <path d=\"M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/>\ + </svg>\ + </label>\ + <input type=\"checkbox\" id=\"showmenu\" hidden />" + menu_element.onclick = function() { + const button = this.querySelector("#showmenu") + button.checked = !button.checked + } + menu_element.onmouseleave = function() { + const button = this.querySelector("#showmenu") + button.checked = false + } + const dropdown = document.createElement("div") + const dropdown_classes = [ + "dropdown", + `dropdown_${message_id}` + ] + dropdown.setAttribute("class", dropdown_classes.join(" ")) + const remove = document.createElement("div") + remove.innerHTML = "Delete" + remove.msg_id = message_id + remove.onclick = function() { + window.prompt(`DELETE_INTERACTION:${this.msg_id}`) + } + dropdown.appendChild(remove) + menu_element.appendChild(dropdown) + if (message_type !== "call") { + message_div.appendChild(menu_element) + } else { + var wrapper = message_div.querySelector(".message_wrapper") + wrapper.insertBefore(menu_element, wrapper.firstChild) + } + // Get timestamp to add const formattedTimestamp = getMessageTimestampText(message_timestamp, true) // Create the timestamp object