From e975598a40eee965e7256390d9539e35cb0e1942 Mon Sep 17 00:00:00 2001 From: olcxja Date: Sat, 22 Aug 2026 09:18:12 +0200 Subject: [PATCH] add more group setting messages & fix leaving empty groups & fix image cropper default zoom --- .../src/main/assets/public/blah/en-us.json | 3 + .../src/main/assets/public/blah/pl-pl.json | 3 + android/app/src/main/assets/public/index.html | 5 +- android/app/src/main/assets/public/main.js | 154 +++++++++++++++--- android/app/src/main/assets/public/screens.js | 4 +- android/app/src/main/assets/public/style.css | 8 + webroot/blah/en-us.json | 3 + webroot/blah/pl-pl.json | 3 + webroot/index.html | 5 +- webroot/main.js | 154 +++++++++++++++--- webroot/screens.js | 4 +- webroot/style.css | 8 + 12 files changed, 288 insertions(+), 66 deletions(-) diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json index 15e60677..42d98cb7 100644 --- a/android/app/src/main/assets/public/blah/en-us.json +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -256,6 +256,9 @@ "history.visible": "enabled", "history.hidden": "disabled", "group.history.changed.by": "{0} changed history visibility to: {1}", + "group.name.changed.by": "{0} changed the group name to {1}", + "group.pfp.changed.by": "{0} changed the group icon", + "group.pfp.removed.by": "{0} removed the group icon", "prompt.are.you.sure": "Are you sure?", "title.unknown.group": "Unknown Group", "title.unknown": "Unknown", diff --git a/android/app/src/main/assets/public/blah/pl-pl.json b/android/app/src/main/assets/public/blah/pl-pl.json index 1afbc3b1..d1fea3af 100644 --- a/android/app/src/main/assets/public/blah/pl-pl.json +++ b/android/app/src/main/assets/public/blah/pl-pl.json @@ -255,6 +255,9 @@ "history.visible": "włączona", "history.hidden": "wyłączona", "group.history.changed.by": "{0} zmienił widoczność historii na: {1}", + "group.name.changed.by": "{0} zmienił(a) nazwę grupy na: {1}", + "group.pfp.changed.by": "{0} zmienił(a) ikonę grupy", + "group.pfp.removed.by": "{0} usunął(ęła) ikonę grupy", "prompt.are.you.sure": "Czy na pewno?", "title.unknown.group": "Nieznana grupa", "title.unknown": "Nieznany", diff --git a/android/app/src/main/assets/public/index.html b/android/app/src/main/assets/public/index.html index ace3372d..27ac622f 100644 --- a/android/app/src/main/assets/public/index.html +++ b/android/app/src/main/assets/public/index.html @@ -109,10 +109,7 @@
-
- title.zoom - -
+
diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index 6207779b..c00574ed 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -3170,7 +3170,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { content = processBlah(content); if (msg.type && msg.type.startsWith("larp.info")) { - if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history" || msg.type === "larp.info.kick" || msg.type === "larp.info.transfer_owner") { + if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history" || msg.type === "larp.info.kick" || msg.type === "larp.info.transfer_owner" || msg.type === "larp.info.name" || msg.type === "larp.info.pfp" || msg.type === "larp.info.pfp_removed") { let authorName = processBlah("title.unknown"); if (msg.author && msg.author !== "0") { if (!dmUsernameCache[msg.author]) { @@ -3196,6 +3196,9 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { else if (msg.type === "larp.info.history") actionStr = "group.history.changed.by"; else if (msg.type === "larp.info.kick") actionStr = "group.user.kicked.by"; else if (msg.type === "larp.info.transfer_owner") actionStr = "group.owner.transferred.by"; + else if (msg.type === "larp.info.name") actionStr = "group.name.changed.by"; + else if (msg.type === "larp.info.pfp") actionStr = "group.pfp.changed.by"; + else if (msg.type === "larp.info.pfp_removed") actionStr = "group.pfp.removed.by"; let clickableAuthor = `${authorName}`; @@ -3203,8 +3206,9 @@ async function generateMessagesHtml(entries, isPinnedContext = false) { if (msg.type.endsWith(".pin") || msg.type.endsWith(".unpin")) { let clickableMsg = `${processBlah('title.message.lowercase')}`; content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg); - } else if (msg.type === "larp.info.history") { - content = textStr.replace("{0}", clickableAuthor).replace("{1}", content); + } else if (msg.type === "larp.info.history" || msg.type === "larp.info.name") { + let escapedContent = escapeHtml(content); + content = textStr.replace("{0}", clickableAuthor).replace("{1}", escapedContent); } else if (msg.type === "larp.info.kick" || msg.type === "larp.info.transfer_owner") { let targetUsername = ""; let targetId = ""; @@ -4478,6 +4482,7 @@ let cropperImageObj = null; let cropperPosX = 0; let cropperPosY = 0; let cropperBaseZoom = 1; +let cropperZoom = 1; let cropperOriginalType = ''; let cropperOriginalB64 = ''; @@ -4509,9 +4514,10 @@ function openCropper(input, type) { } function initCropper() { + document.getElementById('cropper-popup-container').classList.add('show'); + let container = document.getElementById('cropper-viewport-container'); let imgEl = document.getElementById('cropper-image'); - let zoomSlider = document.getElementById('cropper-zoom'); // Set aspect ratio let targetWidth = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 494; @@ -4533,16 +4539,12 @@ function initCropper() { let scaleX = containerWidth / imgWidth; let scaleY = (containerWidth * (targetHeight / targetWidth)) / imgHeight; cropperBaseZoom = Math.max(scaleX, scaleY); - - zoomSlider.min = cropperBaseZoom; - zoomSlider.max = cropperBaseZoom * 5; - zoomSlider.value = cropperBaseZoom; + cropperZoom = cropperBaseZoom; cropperPosX = 0; cropperPosY = 0; updateCropperTransform(); - document.getElementById('cropper-popup-container').classList.add('show'); } function updateCropperZoom() { @@ -4550,21 +4552,40 @@ function updateCropperZoom() { } function updateCropperTransform() { - let zoom = parseFloat(document.getElementById('cropper-zoom').value); let imgEl = document.getElementById('cropper-image'); let container = document.getElementById('cropper-viewport-container'); if (cropperImageObj) { + let containerWidth = container.clientWidth || 300; + let targetWidth = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 494; + let targetHeight = cropperType === 'avatar' || cropperType === 'group-avatar' ? targetWidth : 200; + let containerHeight = container.clientHeight || (containerWidth * (targetHeight / targetWidth)); + + let scaleX = containerWidth / cropperImageObj.width; + let scaleY = containerHeight / cropperImageObj.height; + cropperBaseZoom = Math.max(scaleX, scaleY); + + if (cropperZoom < cropperBaseZoom) cropperZoom = cropperBaseZoom; + if (cropperZoom > cropperBaseZoom * 5) cropperZoom = cropperBaseZoom * 5; + + let zoom = cropperZoom; let scaledW = cropperImageObj.width * zoom; let scaledH = cropperImageObj.height * zoom; - let minX = container.clientWidth - scaledW; + containerWidth = container.clientWidth || 300; + containerHeight = container.clientHeight || (containerWidth * (targetHeight / targetWidth)); + + let minX = containerWidth - scaledW; + let minY = containerHeight - scaledH; let maxX = 0; - let minY = container.clientHeight - scaledH; let maxY = 0; - if (minX > maxX) minX = maxX; - if (minY > maxY) minY = maxY; + if (scaledW < containerWidth) { + minX = maxX = (containerWidth - scaledW) / 2; + } + if (scaledH < containerHeight) { + minY = maxY = (containerHeight - scaledH) / 2; + } if (cropperPosX < minX) cropperPosX = minX; if (cropperPosX > maxX) cropperPosX = maxX; @@ -4572,7 +4593,7 @@ function updateCropperTransform() { if (cropperPosY > maxY) cropperPosY = maxY; } - imgEl.style.transform = `translate(${cropperPosX}px, ${cropperPosY}px) scale(${zoom})`; + imgEl.style.transform = `translate(${cropperPosX}px, ${cropperPosY}px) scale(${cropperZoom})`; } function closeCropper() { @@ -4580,16 +4601,16 @@ function closeCropper() { } async function confirmCropper() { - let zoom = parseFloat(document.getElementById('cropper-zoom').value); let container = document.getElementById('cropper-viewport-container'); let targetWidth = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 988; let targetHeight = cropperType === 'avatar' || cropperType === 'group-avatar' ? 512 : 400; - let scaleRatio = targetWidth / container.clientWidth; + + let scaleRatio = targetWidth / (container.clientWidth || 300); let drawX = cropperPosX * scaleRatio; let drawY = cropperPosY * scaleRatio; - let drawW = cropperImageObj.width * zoom * scaleRatio; - let drawH = cropperImageObj.height * zoom * scaleRatio; + let drawW = cropperImageObj.width * cropperZoom * scaleRatio; + let drawH = cropperImageObj.height * cropperZoom * scaleRatio; let b64; let newSrc; @@ -4794,9 +4815,20 @@ async function confirmCropper() { } -let container = document.getElementById('cropper-viewport-container'); -if (container) { +let cropperContainer = document.getElementById('cropper-viewport-container'); +if (cropperContainer) { + let initialPinchDistance = null; + let initialPinchZoom = null; + const startDrag = (e) => { + if (e.touches && e.touches.length === 2) { + isDraggingCropper = false; + let dx = e.touches[0].clientX - e.touches[1].clientX; + let dy = e.touches[0].clientY - e.touches[1].clientY; + initialPinchDistance = Math.sqrt(dx*dx + dy*dy); + initialPinchZoom = cropperZoom; + return; + } isDraggingCropper = true; startDragX = e.clientX || (e.touches && e.touches[0].clientX); startDragY = e.clientY || (e.touches && e.touches[0].clientY); @@ -4806,6 +4838,30 @@ if (container) { }; const onDrag = (e) => { + if (e.touches && e.touches.length === 2 && initialPinchDistance !== null) { + let dx = e.touches[0].clientX - e.touches[1].clientX; + let dy = e.touches[0].clientY - e.touches[1].clientY; + let distance = Math.sqrt(dx*dx + dy*dy); + let zoomFactor = distance / initialPinchDistance; + + let oldZoom = cropperZoom; + cropperZoom = initialPinchZoom * zoomFactor; + + if (cropperZoom < cropperBaseZoom) cropperZoom = cropperBaseZoom; + if (cropperZoom > cropperBaseZoom * 5) cropperZoom = cropperBaseZoom * 5; + + let zoomRatio = cropperZoom / oldZoom; + + let cw = cropperContainer.clientWidth || 300; + let ch = cropperContainer.clientHeight || 300; + + cropperPosX = (cropperPosX - cw/2) * zoomRatio + cw/2; + cropperPosY = (cropperPosY - ch/2) * zoomRatio + ch/2; + + updateCropperTransform(); + return; + } + if (!isDraggingCropper) return; let clientX = e.clientX || (e.touches && e.touches[0].clientX); let clientY = e.clientY || (e.touches && e.touches[0].clientY); @@ -4814,16 +4870,57 @@ if (container) { updateCropperTransform(); }; - const endDrag = () => { - isDraggingCropper = false; + const endDrag = (e) => { + if (e.touches && e.touches.length < 2) { + initialPinchDistance = null; + initialPinchZoom = null; + } + if (e.touches && e.touches.length === 1) { + isDraggingCropper = true; + startDragX = e.touches[0].clientX; + startDragY = e.touches[0].clientY; + startPosX = cropperPosX; + startPosY = cropperPosY; + } else { + isDraggingCropper = false; + } + }; + + const onWheel = (e) => { + if (document.getElementById('cropper-popup-container').classList.contains('show')) { + e.preventDefault(); + let oldZoom = cropperZoom; + let zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; + cropperZoom *= zoomFactor; + if (cropperZoom < cropperBaseZoom) cropperZoom = cropperBaseZoom; + if (cropperZoom > cropperBaseZoom * 5) cropperZoom = cropperBaseZoom * 5; + + let zoomRatio = cropperZoom / oldZoom; + + let rect = cropperContainer.getBoundingClientRect(); + let mouseX = e.clientX - rect.left; + let mouseY = e.clientY - rect.top; + + cropperPosX = (cropperPosX - mouseX) * zoomRatio + mouseX; + cropperPosY = (cropperPosY - mouseY) * zoomRatio + mouseY; + + updateCropperTransform(); + } }; - container.addEventListener('mousedown', startDrag); - container.addEventListener('touchstart', startDrag, {passive: true}); + cropperContainer.addEventListener('mousedown', startDrag); + cropperContainer.addEventListener('touchstart', startDrag, {passive: true}); document.addEventListener('mousemove', onDrag); - document.addEventListener('touchmove', onDrag, {passive: true}); + document.addEventListener('touchmove', onDrag, {passive: false}); document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag); + cropperContainer.addEventListener('wheel', onWheel, {passive: false}); + + new ResizeObserver(() => { + if (document.getElementById('cropper-popup-container').classList.contains('show')) { + updateCropperTransform(); + } + }).observe(cropperContainer); } function updateDmListUI(roomId, isRead, type = 'dms', timestamp = null) { @@ -5291,7 +5388,7 @@ async function confirmLeaveGroup(groupId) { try { let payload = JSON.stringify({ string1: groupId }); let res = await fetchEncrypted("group/leave", payload); - if (res === "success") { + if (res === "success" || res === "error:not.a.member" || res === "error:group.not.found") { if (currentDmId === groupId) { currentDmId = null; const rem = parseFloat(getComputedStyle(document.documentElement).fontSize); @@ -5302,6 +5399,9 @@ async function confirmLeaveGroup(groupId) { } } refreshGroups(0, false); + if (res !== "success") { + showBlahNotification(res); + } } else if (res.startsWith("error:")) { showBlahNotification(res); } diff --git a/android/app/src/main/assets/public/screens.js b/android/app/src/main/assets/public/screens.js index 2fc8d98c..d0b90b22 100644 --- a/android/app/src/main/assets/public/screens.js +++ b/android/app/src/main/assets/public/screens.js @@ -349,7 +349,7 @@ var profileSettings = `
- +
title.avatar
@@ -381,7 +381,7 @@ var profileSettings = ` var groupSettingsScreen = `