From 81e222250d71bfb3cfaa2437107f26dd4ad08bed Mon Sep 17 00:00:00 2001 From: olcxja Date: Thu, 21 May 2026 11:02:02 +0200 Subject: [PATCH] Add fetching invites --- .../src/main/assets/public/blah/en-us.json | 11 +- android/app/src/main/assets/public/index.html | 2 +- android/app/src/main/assets/public/main.js | 214 ++++++++++++++---- android/app/src/main/assets/public/screens.js | 46 +++- android/app/src/main/assets/public/style.css | 203 +++++++++++------ webroot/blah/en-us.json | 11 +- webroot/index.html | 2 +- webroot/main.js | 214 ++++++++++++++---- webroot/screens.js | 46 +++- webroot/style.css | 203 +++++++++++------ 10 files changed, 690 insertions(+), 262 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 f7cac081..056f4a5e 100644 --- a/android/app/src/main/assets/public/blah/en-us.json +++ b/android/app/src/main/assets/public/blah/en-us.json @@ -74,12 +74,21 @@ "title.unread": "Unread", "desc.no.invites": "No invites found", "desc.no.notifications": "No notifications found", + "desc.fetching.invites": "Fetching invites...", + "desc.fetching.notifications": "Fetching notifications...", + "desc.invite.dm.received": "{0} ({1}) invited you to chat", + "desc.invite.dm.sent": "You invited {0} ({1}) to chat", + "desc.invite.group.received": "{0} ({1}) invited you to a group", + "desc.invite.group.sent": "You invited {0} ({1}) to a group", "action.fetching.invites.sent": "Fetching sent invites...", "action.fetching.invites.recv": "Fetching received invites...", "action.dm.fetch": "Fetching dms...", - "action.dm.adding": "Adding...", "action.auth": "Authenticating...", + "action.dm.adding": "Adding...", + "action.invite.revoking": "Revoking invite...", + "action.invite.accepting": "Accepting invite...", + "action.invite.declining": "Declining invite...", "title.sign.up": "Sign Up", "title.sign.in": "Sign In", diff --git a/android/app/src/main/assets/public/index.html b/android/app/src/main/assets/public/index.html index 046a1a58..d8662375 100644 --- a/android/app/src/main/assets/public/index.html +++ b/android/app/src/main/assets/public/index.html @@ -94,7 +94,7 @@ { await updateProtocolAndUrl(window.location.hostname); } - sidebarPfp.src = await getAvatarUrl(username); + sidebarPfp.src = await getAvatarUrl(`${username}:${host}`); showAction("action.auth", "startauth"); let res = await Auth(username, password); diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js index 0bb3ee6a..49d76e4b 100644 --- a/android/app/src/main/assets/public/main.js +++ b/android/app/src/main/assets/public/main.js @@ -406,6 +406,7 @@ function showNotification(message, type = 'info', duration = 3500) { notif.addEventListener('transitionend', () => { notif.remove(); }); + setTimeout(() => notif.remove(), 300); }, duration); } @@ -419,7 +420,7 @@ function showAction(message, actionid) { } const notif = document.createElement('div'); - notif.className = `notification action`; + notif.className = `notification`; notif.textContent = processBlah(message); notif.id = `notification-${actionid}`; @@ -438,9 +439,10 @@ function clearAction(actionid) { notifications.forEach(notif => { notif.classList.remove('show'); - notif.addEventListener('transitionend', () => { + notif.addEventListener('transitionend', async () => { notif.remove(); }, { once: true }); + setTimeout(() => notif.remove(), 300); }); } @@ -596,13 +598,27 @@ async function initBlahs() { } } +function splitLimit(str, separator, limit) { + const parts = str.split(separator); + + if (parts.length <= limit) { + return parts; + } + + return [ + ...parts.slice(0, limit - 1), + parts.slice(limit - 1).join(separator) + ]; +} + function processBlah(blahmessage) { try { if (!blahmessage.includes(":")) { blahmessage = `:${blahmessage}`; } - let split = blahmessage.split(":"); + let split = splitLimit(blahmessage, ":", 3); + let values = []; try { values = split[2].split(";"); @@ -614,6 +630,11 @@ function processBlah(blahmessage) { let valueslist = ""; for (let i = 0; i < values.length; i++) { let value = processBlah(values[i]); + if (value.startsWith(":")) + { + value = value.substring(1); + } + valueslist+=`${value}, `; message = message.replaceAll(`{${i}}`, value); @@ -964,60 +985,153 @@ function setActiveRoombarItem(itemId) { } } +async function renderInvites(res, type) { + let container = document.getElementById("invites-container"); + if (!container) return; + + if (res && res != "") { + + res = JSON.parse(res); + + let invites = [ + ...Object.entries(res.dms).map(([id, value]) => ({ + id, + type: "dm", + ...value + })), + + ...Object.entries(res.groups).map(([id, value]) => ({ + id, + type: "group", + ...value + })) + ].sort((a, b) => b.timestamp - a.timestamp); + + container.classList.remove("empty"); + let html = ""; + let count = 0; + + for (let i = 0; i < invites.length; i++) { + let invite = invites[i]; + + + if (invite.type === "dm") { + let id = invite.id; + if (!id.includes(":")) { + id += `:${host}`; + } + let username = await fetchAsync(`${url}/idtoname?id=${id}`); + if (!(username && username.trim() !== "")) return; + count++; + + let pfp = await getAvatarUrl(username); + + let actions = ""; + let desc = ""; + + if (type === "received") { + desc = `:desc.invite.dm.received:${username};${id}`; + actions = ` + + + `; + } else { + desc = `:desc.invite.dm.sent:${username};${id}`; + actions = ` + + `; + } + + let entry = invitesEntry + .replaceAll("{username}", username.split(':')[0]) + .replaceAll("{pfp}", pfp) + .replaceAll("{desc}", desc) + .replaceAll("{actions}", actions); + + html += entry; + } + } + + if (count > 0) { + container.innerHTML = html; + } else { + container.classList.add("empty"); + container.innerHTML = invitesEmptyState; + } + } else { + container.classList.add("empty"); + container.innerHTML = invitesEmptyState; + } + + let blahTags = container.getElementsByTagName("blah"); + for (let i = 0; i < blahTags.length; i++) { + blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML); + } +} + +async function loadInvites(tab) { + try { + showAction(`action.fetching.invites.${tab === 'received' ? 'recv' : 'sent'}`, `fetching.invites.${tab}`); + let res = await fetchEncrypted(`user/invites/${tab}`); + await renderInvites(res, tab); + } catch (e) { + showBlahNotification("error:something.wrong"); + console.error(e); + await renderInvites("", tab); + } + clearAction(`fetching.invites.${tab}`); +} + async function switchInvitesTab(tab) { if (!document.getElementById(`tab-invites-${tab}`).classList.contains('tab-inactive')) return; document.getElementById('tab-invites-received').classList.add('tab-inactive'); document.getElementById('tab-invites-sent').classList.add('tab-inactive'); document.getElementById(`tab-invites-${tab}`).classList.remove('tab-inactive'); - let container = document.getElementById("invites-container"); - let innerString = ""; - - if (tab === "received") - { - try { - showAction("action.fetching.invites.recv", "fetching.invites.recv"); - let res = await fetchEncrypted("user/invites/received"); - if (res !== "") - { - if (res.includes(",")) { - console.log(res); - } - else - { - console.log(res); - container.innerHTML = invitesEntry; - } - } - } - catch (e) { - showBlahNotification("error:something.wrong"); - console.error(e); - } - clearAction("fetching.invites.recv"); + await loadInvites(tab); +} +async function acceptInvite(username) { //TODO + try { + showAction("action.invite.accepting", "invite.action"); + let res = await fetchEncrypted("user/dm/create", username); + clearAction("invite.action"); + showBlahNotification(res || "success:invite.accepted"); + await loadInvites('received'); + } catch (e) { + clearAction("invite.action"); + showBlahNotification("error:something.wrong"); } - else if (tab === "sent") - { - try { - showAction("action.fetching.invites.sent", "fetching.invites.sent"); - let res = await fetchEncrypted("user/invites/sent"); - if (res !== "") - { - if (res.includes(",")) { - console.log(res); - } - else - { - console.log(res); - container.innerHTML = invitesEntry; - } - } - } - catch (e) { - showBlahNotification("error:something.wrong"); - console.error(e); - } - clearAction("fetching.invites.sent"); +} + +async function declineInvite(username) { //TODO + try { + showAction("action.invite.declining", "invite.action"); + let res = await fetchEncrypted("user/dm/decline", username); + clearAction("invite.action"); + showBlahNotification(res || "success:invite.declined"); + await loadInvites('received'); + } catch (e) { + clearAction("invite.action"); + showBlahNotification("error:something.wrong"); + } +} + +async function revokeInvite(username) { //TODO + try { + showAction("action.invite.revoking", "invite.action"); + let res = await fetchEncrypted("user/dm/revoke", username); + clearAction("invite.action"); + showBlahNotification(res || "success:invite.revoked"); + await loadInvites('sent'); + } catch (e) { + clearAction("invite.action"); + showBlahNotification("error:something.wrong"); } } diff --git a/android/app/src/main/assets/public/screens.js b/android/app/src/main/assets/public/screens.js index 8a0c7085..94a426fd 100644 --- a/android/app/src/main/assets/public/screens.js +++ b/android/app/src/main/assets/public/screens.js @@ -73,30 +73,58 @@ var joinSpaceScreen = ` `; +var invitesEmptyState = ` + +
+

desc.no.invites

+`; +var invitesLoadingState = ` + +
+

desc.fetching.invites

+`; + var invitesScreen = `
-
- -
-

desc.no.invites

+
+ ${invitesLoadingState}
`; +var notifisEmptyState = ` + +
+

desc.no.notifications

+`; + var notifisScreen = `
-
- -
-

desc.no.notifications

+
+ ${notifisEmptyState} +
+
+`; + +var invitesEntry = ` +
+
+ +
+ {username} + {desc} +
+
+
+ {actions}
`; @@ -171,5 +199,3 @@ var addSpaceMenu = ` //elements var detailsBtn = ``; var backBtnHtml = ``; - -var invitesEntry = `???`; \ No newline at end of file diff --git a/android/app/src/main/assets/public/style.css b/android/app/src/main/assets/public/style.css index f60e7de1..5ad2a2f2 100644 --- a/android/app/src/main/assets/public/style.css +++ b/android/app/src/main/assets/public/style.css @@ -13,10 +13,10 @@ --icon-button-height: 3.4rem; --button-height: 2.4rem; --border-width: 0.09rem; - - --big-default: rgb(207 207 207); - --big-red: rgb(202 0 0); - --big-green: rgb(25 189 0); + + --big-default: rgb(207, 207, 207); + --big-red: rgb(195, 75, 75); + --big-green: rgb(75, 165, 95); } *:focus { outline: none; @@ -76,7 +76,7 @@ button, input { background-color: rgba(255, 255, 255, 0.05); margin: var(--button-margin); padding: var(--button-margin); - + height: var(--button-height); display: flex; cursor: pointer; @@ -148,12 +148,13 @@ indicator.active { width: calc(var(--icon-button-height) - (var(--border-width) * 2)); height: calc(var(--icon-button-height) - (var(--border-width) * 2)); pointer-events: none !important; + border-radius: 0.6rem; } roomcontent { display: flex; flex-direction: column; - + height: 100%; flex-grow: 1; border-right: var(--border-width) solid var(--light-border-color); @@ -161,7 +162,7 @@ roomcontent { roomcontent2 { display: flex; flex-direction: column; - + height: calc(100% - (var(--button-height) * 1.5)); width: 100%; } @@ -169,7 +170,7 @@ roomcontent2 { sidebar { display: flex; flex-direction: column; - + height: 100%; width: calc(var(--icon-button-height) + (var(--button-margin) * 2) + var(--border-width)); border-right: var(--border-width) solid var(--light-border-color); @@ -252,11 +253,11 @@ hr { box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.4); opacity: 0; - transform: translateY(-1rem); + transform: translateY(-2rem); transition: - opacity 0.3s ease, - transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), - background-color 0.2s ease; + opacity 0.2s ease, + transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275), + background-color 0.15s ease; } .notification.show { opacity: 1; @@ -290,8 +291,8 @@ roomtopbar { sidebar.second#roomdetailsbar { height: 100%; display: flex; - flex-direction: column; - border-left: var(--border-width) solid var(--light-border-color); + flex-direction: column; + border-left: var(--border-width) solid var(--light-border-color); } fullcontainer { width: 100%; @@ -330,52 +331,7 @@ herotitle { .bottom-element { margin-top: auto; } -@media (hover: hover) { - sidebarelement:hover indicator:not(.active) { - content: ""; - position: absolute; - width: 0; - height: 1.2rem; - left: -0.08rem; - border: 0.15rem solid var(--text-color); - border-radius: 0.8rem; - transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); - } - button:hover, input:hover { - background-color: rgba(255, 255, 255, 0.1); - } - .submit-button:hover { - background-color: rgba(255, 255, 255, 0.5); - } - .add-action-button:hover { - background-color: rgba(255, 255, 255, 0.1); - color: var(--text-color); - } -} -button:active, button.is-pressed, input:active, input.is-pressed { - background-color: rgba(255, 255, 255, 0.1); - transform: var(--press-scale); -} -button.active, input.active { - background-color: rgba(255, 255, 255, 0.1); -} -.submit-button:active, .submit-button.is-pressed { - background-color: rgba(255, 255, 255, 0.5); -} -.add-action-button:active, .add-action-button.is-pressed { - background-color: rgba(255, 255, 255, 0.1); - color: var(--text-color); -} -sidebarelement:has(.icon-button:active) indicator:not(.active) { - content: ""; - position: absolute; - width: 0; - height: 1.2rem; - left: -0.08rem; - border: 0.15rem solid var(--text-color); - border-radius: 0.8rem; - transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); -} + blah, inherit, .inherit { all: inherit; padding: 0; @@ -419,9 +375,7 @@ space{ gap: 0.5rem; font-weight: 600; } -.context-menu button:hover { - background-color: rgba(255, 255, 255, 0.08); -} + .mobile-nav-btn { display: none; background: transparent; @@ -466,7 +420,7 @@ space{ sidebar, sidebar.second, roomcontent { position: absolute !important; height: 100%; - transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1); + transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1); } sidebar { @@ -492,13 +446,13 @@ space{ } #roomdetailsbar { - left: auto !important; + left: auto !important; right: 0; - width: 90vw !important; + width: 90vw !important; min-width: unset !important; max-width: none !important; - transform: translateX(100vw); - z-index: 10; + transform: translateX(100vw); + z-index: 10; background-color: var(--main-bg-color); } @@ -537,7 +491,7 @@ space{ opacity: 0; pointer-events: none; - transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1); + transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1); } main.mobile-details roomcontent::after { @@ -553,4 +507,115 @@ space{ .submit-button.tab-inactive { background-color: transparent; color: var(--text-color); +} + +.list-container { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 1rem; + overflow-y: auto; + gap: 0.5rem; +} +.list-container.empty { + justify-content: center; + text-align: center; + opacity: 0.5; +} + +.invite-entry { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.8rem 1rem; + border: var(--border-width) solid var(--light-border-color); + border-radius: 0.8rem; + width: 100%; + background: rgba(255, 255, 255, 0.02); + flex-shrink: 0; +} +.invite-info { + display: flex; + align-items: center; + gap: 1rem; +} +.invite-pfp { + width: 2.8rem; + height: 2.8rem; + border-radius: 0.8rem; + object-fit: cover; +} +.invite-details { + display: flex; + flex-direction: column; + text-align: left; +} +.invite-name { + font-weight: 700; + font-size: 1.05rem; +} +.invite-desc { + font-size: 0.85rem; + opacity: 0.6; +} +.invite-actions { + display: flex; + gap: 0.5rem; +} +.invite-actions button { + margin: 0; +} + +@media (hover: hover) { + sidebarelement:hover indicator:not(.active) { + content: ""; + position: absolute; + width: 0; + height: 1.2rem; + left: -0.08rem; + border: 0.15rem solid var(--text-color); + border-radius: 0.8rem; + transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); + } + button:hover, input:hover { + background-color: rgba(255, 255, 255, 0.1); + } + .submit-button:hover { + background-color: rgba(255, 255, 255, 0.5); + } + .add-action-button:hover { + background-color: rgba(255, 255, 255, 0.1); + color: var(--text-color); + } + .context-menu button:hover { + background-color: rgba(255, 255, 255, 0.08); + } +} +button:active, button.is-pressed, input:active, input.is-pressed { + background-color: rgba(255, 255, 255, 0.1); + transform: var(--press-scale); +} +button.active, input.active { + background-color: rgba(255, 255, 255, 0.1); +} +.submit-button:active, .submit-button.is-pressed { + background-color: rgba(255, 255, 255, 0.5); +} +.add-action-button:active, .add-action-button.is-pressed { + background-color: rgba(255, 255, 255, 0.1); + color: var(--text-color); +} +.context-menu button:active, .context-menu button.is-pressed { + background-color: rgba(255, 255, 255, 0.08); +} +sidebarelement:has(.icon-button:active) indicator:not(.active) { + content: ""; + position: absolute; + width: 0; + height: 1.2rem; + left: -0.08rem; + border: 0.15rem solid var(--text-color); + border-radius: 0.8rem; + transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); } \ No newline at end of file diff --git a/webroot/blah/en-us.json b/webroot/blah/en-us.json index f7cac081..056f4a5e 100644 --- a/webroot/blah/en-us.json +++ b/webroot/blah/en-us.json @@ -74,12 +74,21 @@ "title.unread": "Unread", "desc.no.invites": "No invites found", "desc.no.notifications": "No notifications found", + "desc.fetching.invites": "Fetching invites...", + "desc.fetching.notifications": "Fetching notifications...", + "desc.invite.dm.received": "{0} ({1}) invited you to chat", + "desc.invite.dm.sent": "You invited {0} ({1}) to chat", + "desc.invite.group.received": "{0} ({1}) invited you to a group", + "desc.invite.group.sent": "You invited {0} ({1}) to a group", "action.fetching.invites.sent": "Fetching sent invites...", "action.fetching.invites.recv": "Fetching received invites...", "action.dm.fetch": "Fetching dms...", - "action.dm.adding": "Adding...", "action.auth": "Authenticating...", + "action.dm.adding": "Adding...", + "action.invite.revoking": "Revoking invite...", + "action.invite.accepting": "Accepting invite...", + "action.invite.declining": "Declining invite...", "title.sign.up": "Sign Up", "title.sign.in": "Sign In", diff --git a/webroot/index.html b/webroot/index.html index 046a1a58..d8662375 100644 --- a/webroot/index.html +++ b/webroot/index.html @@ -94,7 +94,7 @@ { await updateProtocolAndUrl(window.location.hostname); } - sidebarPfp.src = await getAvatarUrl(username); + sidebarPfp.src = await getAvatarUrl(`${username}:${host}`); showAction("action.auth", "startauth"); let res = await Auth(username, password); diff --git a/webroot/main.js b/webroot/main.js index 0bb3ee6a..49d76e4b 100644 --- a/webroot/main.js +++ b/webroot/main.js @@ -406,6 +406,7 @@ function showNotification(message, type = 'info', duration = 3500) { notif.addEventListener('transitionend', () => { notif.remove(); }); + setTimeout(() => notif.remove(), 300); }, duration); } @@ -419,7 +420,7 @@ function showAction(message, actionid) { } const notif = document.createElement('div'); - notif.className = `notification action`; + notif.className = `notification`; notif.textContent = processBlah(message); notif.id = `notification-${actionid}`; @@ -438,9 +439,10 @@ function clearAction(actionid) { notifications.forEach(notif => { notif.classList.remove('show'); - notif.addEventListener('transitionend', () => { + notif.addEventListener('transitionend', async () => { notif.remove(); }, { once: true }); + setTimeout(() => notif.remove(), 300); }); } @@ -596,13 +598,27 @@ async function initBlahs() { } } +function splitLimit(str, separator, limit) { + const parts = str.split(separator); + + if (parts.length <= limit) { + return parts; + } + + return [ + ...parts.slice(0, limit - 1), + parts.slice(limit - 1).join(separator) + ]; +} + function processBlah(blahmessage) { try { if (!blahmessage.includes(":")) { blahmessage = `:${blahmessage}`; } - let split = blahmessage.split(":"); + let split = splitLimit(blahmessage, ":", 3); + let values = []; try { values = split[2].split(";"); @@ -614,6 +630,11 @@ function processBlah(blahmessage) { let valueslist = ""; for (let i = 0; i < values.length; i++) { let value = processBlah(values[i]); + if (value.startsWith(":")) + { + value = value.substring(1); + } + valueslist+=`${value}, `; message = message.replaceAll(`{${i}}`, value); @@ -964,60 +985,153 @@ function setActiveRoombarItem(itemId) { } } +async function renderInvites(res, type) { + let container = document.getElementById("invites-container"); + if (!container) return; + + if (res && res != "") { + + res = JSON.parse(res); + + let invites = [ + ...Object.entries(res.dms).map(([id, value]) => ({ + id, + type: "dm", + ...value + })), + + ...Object.entries(res.groups).map(([id, value]) => ({ + id, + type: "group", + ...value + })) + ].sort((a, b) => b.timestamp - a.timestamp); + + container.classList.remove("empty"); + let html = ""; + let count = 0; + + for (let i = 0; i < invites.length; i++) { + let invite = invites[i]; + + + if (invite.type === "dm") { + let id = invite.id; + if (!id.includes(":")) { + id += `:${host}`; + } + let username = await fetchAsync(`${url}/idtoname?id=${id}`); + if (!(username && username.trim() !== "")) return; + count++; + + let pfp = await getAvatarUrl(username); + + let actions = ""; + let desc = ""; + + if (type === "received") { + desc = `:desc.invite.dm.received:${username};${id}`; + actions = ` + + + `; + } else { + desc = `:desc.invite.dm.sent:${username};${id}`; + actions = ` + + `; + } + + let entry = invitesEntry + .replaceAll("{username}", username.split(':')[0]) + .replaceAll("{pfp}", pfp) + .replaceAll("{desc}", desc) + .replaceAll("{actions}", actions); + + html += entry; + } + } + + if (count > 0) { + container.innerHTML = html; + } else { + container.classList.add("empty"); + container.innerHTML = invitesEmptyState; + } + } else { + container.classList.add("empty"); + container.innerHTML = invitesEmptyState; + } + + let blahTags = container.getElementsByTagName("blah"); + for (let i = 0; i < blahTags.length; i++) { + blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML); + } +} + +async function loadInvites(tab) { + try { + showAction(`action.fetching.invites.${tab === 'received' ? 'recv' : 'sent'}`, `fetching.invites.${tab}`); + let res = await fetchEncrypted(`user/invites/${tab}`); + await renderInvites(res, tab); + } catch (e) { + showBlahNotification("error:something.wrong"); + console.error(e); + await renderInvites("", tab); + } + clearAction(`fetching.invites.${tab}`); +} + async function switchInvitesTab(tab) { if (!document.getElementById(`tab-invites-${tab}`).classList.contains('tab-inactive')) return; document.getElementById('tab-invites-received').classList.add('tab-inactive'); document.getElementById('tab-invites-sent').classList.add('tab-inactive'); document.getElementById(`tab-invites-${tab}`).classList.remove('tab-inactive'); - let container = document.getElementById("invites-container"); - let innerString = ""; - - if (tab === "received") - { - try { - showAction("action.fetching.invites.recv", "fetching.invites.recv"); - let res = await fetchEncrypted("user/invites/received"); - if (res !== "") - { - if (res.includes(",")) { - console.log(res); - } - else - { - console.log(res); - container.innerHTML = invitesEntry; - } - } - } - catch (e) { - showBlahNotification("error:something.wrong"); - console.error(e); - } - clearAction("fetching.invites.recv"); + await loadInvites(tab); +} +async function acceptInvite(username) { //TODO + try { + showAction("action.invite.accepting", "invite.action"); + let res = await fetchEncrypted("user/dm/create", username); + clearAction("invite.action"); + showBlahNotification(res || "success:invite.accepted"); + await loadInvites('received'); + } catch (e) { + clearAction("invite.action"); + showBlahNotification("error:something.wrong"); } - else if (tab === "sent") - { - try { - showAction("action.fetching.invites.sent", "fetching.invites.sent"); - let res = await fetchEncrypted("user/invites/sent"); - if (res !== "") - { - if (res.includes(",")) { - console.log(res); - } - else - { - console.log(res); - container.innerHTML = invitesEntry; - } - } - } - catch (e) { - showBlahNotification("error:something.wrong"); - console.error(e); - } - clearAction("fetching.invites.sent"); +} + +async function declineInvite(username) { //TODO + try { + showAction("action.invite.declining", "invite.action"); + let res = await fetchEncrypted("user/dm/decline", username); + clearAction("invite.action"); + showBlahNotification(res || "success:invite.declined"); + await loadInvites('received'); + } catch (e) { + clearAction("invite.action"); + showBlahNotification("error:something.wrong"); + } +} + +async function revokeInvite(username) { //TODO + try { + showAction("action.invite.revoking", "invite.action"); + let res = await fetchEncrypted("user/dm/revoke", username); + clearAction("invite.action"); + showBlahNotification(res || "success:invite.revoked"); + await loadInvites('sent'); + } catch (e) { + clearAction("invite.action"); + showBlahNotification("error:something.wrong"); } } diff --git a/webroot/screens.js b/webroot/screens.js index 8a0c7085..94a426fd 100644 --- a/webroot/screens.js +++ b/webroot/screens.js @@ -73,30 +73,58 @@ var joinSpaceScreen = `
`; +var invitesEmptyState = ` + +
+

desc.no.invites

+`; +var invitesLoadingState = ` + +
+

desc.fetching.invites

+`; + var invitesScreen = `
-
- -
-

desc.no.invites

+
+ ${invitesLoadingState}
`; +var notifisEmptyState = ` + +
+

desc.no.notifications

+`; + var notifisScreen = `
-
- -
-

desc.no.notifications

+
+ ${notifisEmptyState} +
+
+`; + +var invitesEntry = ` +
+
+ +
+ {username} + {desc} +
+
+
+ {actions}
`; @@ -171,5 +199,3 @@ var addSpaceMenu = ` //elements var detailsBtn = ``; var backBtnHtml = ``; - -var invitesEntry = `???`; \ No newline at end of file diff --git a/webroot/style.css b/webroot/style.css index f60e7de1..5ad2a2f2 100644 --- a/webroot/style.css +++ b/webroot/style.css @@ -13,10 +13,10 @@ --icon-button-height: 3.4rem; --button-height: 2.4rem; --border-width: 0.09rem; - - --big-default: rgb(207 207 207); - --big-red: rgb(202 0 0); - --big-green: rgb(25 189 0); + + --big-default: rgb(207, 207, 207); + --big-red: rgb(195, 75, 75); + --big-green: rgb(75, 165, 95); } *:focus { outline: none; @@ -76,7 +76,7 @@ button, input { background-color: rgba(255, 255, 255, 0.05); margin: var(--button-margin); padding: var(--button-margin); - + height: var(--button-height); display: flex; cursor: pointer; @@ -148,12 +148,13 @@ indicator.active { width: calc(var(--icon-button-height) - (var(--border-width) * 2)); height: calc(var(--icon-button-height) - (var(--border-width) * 2)); pointer-events: none !important; + border-radius: 0.6rem; } roomcontent { display: flex; flex-direction: column; - + height: 100%; flex-grow: 1; border-right: var(--border-width) solid var(--light-border-color); @@ -161,7 +162,7 @@ roomcontent { roomcontent2 { display: flex; flex-direction: column; - + height: calc(100% - (var(--button-height) * 1.5)); width: 100%; } @@ -169,7 +170,7 @@ roomcontent2 { sidebar { display: flex; flex-direction: column; - + height: 100%; width: calc(var(--icon-button-height) + (var(--button-margin) * 2) + var(--border-width)); border-right: var(--border-width) solid var(--light-border-color); @@ -252,11 +253,11 @@ hr { box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.4); opacity: 0; - transform: translateY(-1rem); + transform: translateY(-2rem); transition: - opacity 0.3s ease, - transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), - background-color 0.2s ease; + opacity 0.2s ease, + transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275), + background-color 0.15s ease; } .notification.show { opacity: 1; @@ -290,8 +291,8 @@ roomtopbar { sidebar.second#roomdetailsbar { height: 100%; display: flex; - flex-direction: column; - border-left: var(--border-width) solid var(--light-border-color); + flex-direction: column; + border-left: var(--border-width) solid var(--light-border-color); } fullcontainer { width: 100%; @@ -330,52 +331,7 @@ herotitle { .bottom-element { margin-top: auto; } -@media (hover: hover) { - sidebarelement:hover indicator:not(.active) { - content: ""; - position: absolute; - width: 0; - height: 1.2rem; - left: -0.08rem; - border: 0.15rem solid var(--text-color); - border-radius: 0.8rem; - transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); - } - button:hover, input:hover { - background-color: rgba(255, 255, 255, 0.1); - } - .submit-button:hover { - background-color: rgba(255, 255, 255, 0.5); - } - .add-action-button:hover { - background-color: rgba(255, 255, 255, 0.1); - color: var(--text-color); - } -} -button:active, button.is-pressed, input:active, input.is-pressed { - background-color: rgba(255, 255, 255, 0.1); - transform: var(--press-scale); -} -button.active, input.active { - background-color: rgba(255, 255, 255, 0.1); -} -.submit-button:active, .submit-button.is-pressed { - background-color: rgba(255, 255, 255, 0.5); -} -.add-action-button:active, .add-action-button.is-pressed { - background-color: rgba(255, 255, 255, 0.1); - color: var(--text-color); -} -sidebarelement:has(.icon-button:active) indicator:not(.active) { - content: ""; - position: absolute; - width: 0; - height: 1.2rem; - left: -0.08rem; - border: 0.15rem solid var(--text-color); - border-radius: 0.8rem; - transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); -} + blah, inherit, .inherit { all: inherit; padding: 0; @@ -419,9 +375,7 @@ space{ gap: 0.5rem; font-weight: 600; } -.context-menu button:hover { - background-color: rgba(255, 255, 255, 0.08); -} + .mobile-nav-btn { display: none; background: transparent; @@ -466,7 +420,7 @@ space{ sidebar, sidebar.second, roomcontent { position: absolute !important; height: 100%; - transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1); + transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1); } sidebar { @@ -492,13 +446,13 @@ space{ } #roomdetailsbar { - left: auto !important; + left: auto !important; right: 0; - width: 90vw !important; + width: 90vw !important; min-width: unset !important; max-width: none !important; - transform: translateX(100vw); - z-index: 10; + transform: translateX(100vw); + z-index: 10; background-color: var(--main-bg-color); } @@ -537,7 +491,7 @@ space{ opacity: 0; pointer-events: none; - transition: opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1); + transition: all 0.25s cubic-bezier(0.25, 1, 0.5, 1); } main.mobile-details roomcontent::after { @@ -553,4 +507,115 @@ space{ .submit-button.tab-inactive { background-color: transparent; color: var(--text-color); +} + +.list-container { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 1rem; + overflow-y: auto; + gap: 0.5rem; +} +.list-container.empty { + justify-content: center; + text-align: center; + opacity: 0.5; +} + +.invite-entry { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.8rem 1rem; + border: var(--border-width) solid var(--light-border-color); + border-radius: 0.8rem; + width: 100%; + background: rgba(255, 255, 255, 0.02); + flex-shrink: 0; +} +.invite-info { + display: flex; + align-items: center; + gap: 1rem; +} +.invite-pfp { + width: 2.8rem; + height: 2.8rem; + border-radius: 0.8rem; + object-fit: cover; +} +.invite-details { + display: flex; + flex-direction: column; + text-align: left; +} +.invite-name { + font-weight: 700; + font-size: 1.05rem; +} +.invite-desc { + font-size: 0.85rem; + opacity: 0.6; +} +.invite-actions { + display: flex; + gap: 0.5rem; +} +.invite-actions button { + margin: 0; +} + +@media (hover: hover) { + sidebarelement:hover indicator:not(.active) { + content: ""; + position: absolute; + width: 0; + height: 1.2rem; + left: -0.08rem; + border: 0.15rem solid var(--text-color); + border-radius: 0.8rem; + transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); + } + button:hover, input:hover { + background-color: rgba(255, 255, 255, 0.1); + } + .submit-button:hover { + background-color: rgba(255, 255, 255, 0.5); + } + .add-action-button:hover { + background-color: rgba(255, 255, 255, 0.1); + color: var(--text-color); + } + .context-menu button:hover { + background-color: rgba(255, 255, 255, 0.08); + } +} +button:active, button.is-pressed, input:active, input.is-pressed { + background-color: rgba(255, 255, 255, 0.1); + transform: var(--press-scale); +} +button.active, input.active { + background-color: rgba(255, 255, 255, 0.1); +} +.submit-button:active, .submit-button.is-pressed { + background-color: rgba(255, 255, 255, 0.5); +} +.add-action-button:active, .add-action-button.is-pressed { + background-color: rgba(255, 255, 255, 0.1); + color: var(--text-color); +} +.context-menu button:active, .context-menu button.is-pressed { + background-color: rgba(255, 255, 255, 0.08); +} +sidebarelement:has(.icon-button:active) indicator:not(.active) { + content: ""; + position: absolute; + width: 0; + height: 1.2rem; + left: -0.08rem; + border: 0.15rem solid var(--text-color); + border-radius: 0.8rem; + transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem)); } \ No newline at end of file