[call-me] - feat: improve userSidebar UX with avatar...

This commit is contained in:
Miroslav Pejic
2026-04-13 21:28:03 +02:00
parent 54b82a8aba
commit ec5f1c3f45
18 changed files with 190 additions and 43 deletions
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "أنت تتصل بـ __username__.<br/>يرجى الانتظار حتى يجيب",
"acceptCallFrom": "هل تريد قبول المكالمة من __username__ ؟",
"hangupWith": "إنهاء المكالمة مع __username__",
"hangup": "إنهاء المكالمة",
"callUser": "اتصل بـ __username__",
"call": "اتصل",
"videoOff": "الفيديو متوقف",
"videoDisabled": "الفيديو معطّل",
"noUsersOnline": "لا يوجد مستخدمون آخرون متصلون بعد",
@@ -209,6 +211,7 @@
"cancelled": "تم إلغاء نقل الملف",
"cancelledByRemote": "تم إلغاء نقل الملف من الطرف البعيد",
"sendToUser": "إرسال ملف إلى __username__",
"shareFile": "مشاركة ملف",
"newFileFrom": "ملف جديد من __username__",
"sentFileLabel": " أرسل ملفًا: ",
"sending": "جارٍ الإرسال: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Du rufst __username__ an.<br/>Bitte warte, bis die Person antwortet",
"acceptCallFrom": "Möchtest du den Anruf von __username__ annehmen?",
"hangupWith": "Anruf mit __username__ auflegen",
"hangup": "Auflegen",
"callUser": "__username__ anrufen",
"call": "Anrufen",
"videoOff": "Video aus",
"videoDisabled": "Video deaktiviert",
"noUsersOnline": "Noch keine anderen Benutzer online",
@@ -209,6 +211,7 @@
"cancelled": "Dateiübertragung abgebrochen",
"cancelledByRemote": "Dateiübertragung vom Gegenüber abgebrochen",
"sendToUser": "Datei an __username__ senden",
"shareFile": "Datei teilen",
"newFileFrom": "Neue Datei von __username__",
"sentFileLabel": " hat eine Datei gesendet: ",
"sending": "Senden: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "You are calling __username__.<br/>Please wait for them to answer",
"acceptCallFrom": "Do you want to accept call from __username__ ?",
"hangupWith": "Hang up call with __username__",
"hangup": "Hang up",
"callUser": "Call __username__",
"call": "Call",
"videoOff": "Video Off",
"videoDisabled": "Video Disabled",
"noUsersOnline": "No other users online yet",
@@ -210,6 +212,7 @@
"cancelled": "File transfer cancelled",
"cancelledByRemote": "File transfer cancelled by remote",
"sendToUser": "Send file to __username__",
"shareFile": "Share file",
"newFileFrom": "New file from __username__",
"sentFileLabel": " sent file: ",
"sending": "Sending: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Estás llamando a __username__.<br/>Por favor espera a que responda",
"acceptCallFrom": "¿Quieres aceptar la llamada de __username__ ?",
"hangupWith": "Colgar la llamada con __username__",
"hangup": "Colgar",
"callUser": "Llamar a __username__",
"call": "Llamar",
"videoOff": "Vídeo desactivado",
"videoDisabled": "Vídeo deshabilitado",
"noUsersOnline": "Aún no hay otros usuarios en línea",
@@ -209,6 +211,7 @@
"cancelled": "Transferencia de archivos cancelada",
"cancelledByRemote": "Transferencia de archivos cancelada por el remoto",
"sendToUser": "Enviar archivo a __username__",
"shareFile": "Compartir archivo",
"newFileFrom": "Nuevo archivo de __username__",
"sentFileLabel": " envió el archivo: ",
"sending": "Enviando: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Vous appelez __username__.<br/>Veuillez attendre quil/elle réponde",
"acceptCallFrom": "Voulez-vous accepter lappel de __username__ ?",
"hangupWith": "Raccrocher avec __username__",
"hangup": "Raccrocher",
"callUser": "Appeler __username__",
"call": "Appeler",
"videoOff": "Vidéo coupée",
"videoDisabled": "Vidéo désactivée",
"noUsersOnline": "Aucun autre utilisateur en ligne pour le moment",
@@ -209,6 +211,7 @@
"cancelled": "Transfert de fichier annulé",
"cancelledByRemote": "Transfert de fichier annulé par le distant",
"sendToUser": "Envoyer un fichier à __username__",
"shareFile": "Partager un fichier",
"newFileFrom": "Nouveau fichier de __username__",
"sentFileLabel": " a envoyé le fichier : ",
"sending": "Envoi : __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "आप __username__ को कॉल कर रहे हैं।<br/>कृपया उनके उत्तर देने का इंतज़ार करें।",
"acceptCallFrom": "क्या आप __username__ से कॉल स्वीकार करना चाहते हैं ?",
"hangupWith": "__username__ के साथ कॉल समाप्त करें",
"hangup": "कॉल समाप्त करें",
"callUser": "__username__ को कॉल करें",
"call": "कॉल करें",
"videoOff": "वीडियो बंद",
"videoDisabled": "वीडियो अक्षम",
"noUsersOnline": "अभी कोई अन्य उपयोगकर्ता ऑनलाइन नहीं है",
@@ -209,6 +211,7 @@
"cancelled": "फ़ाइल ट्रांसफ़र रद्द किया गया",
"cancelledByRemote": "रिमोट द्वारा फ़ाइल ट्रांसफ़र रद्द किया गया",
"sendToUser": "__username__ को फ़ाइल भेजें",
"shareFile": "फ़ाइल साझा करें",
"newFileFrom": "__username__ से नई फ़ाइल",
"sentFileLabel": " ने फ़ाइल भेजी: ",
"sending": "भेजा जा रहा है: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Zovete __username__.<br/>Pričekajte da se javi",
"acceptCallFrom": "Želite li prihvatiti poziv od __username__ ?",
"hangupWith": "Prekini poziv s __username__",
"hangup": "Prekini poziv",
"callUser": "Nazovi __username__",
"call": "Nazovi",
"videoOff": "Video isključen",
"videoDisabled": "Video onemogućen",
"noUsersOnline": "Još nema drugih korisnika na mreži",
@@ -209,6 +211,7 @@
"cancelled": "Prijenos datoteke otkazan",
"cancelledByRemote": "Prijenos datoteke otkazan od strane udaljenog korisnika",
"sendToUser": "Pošalji datoteku korisniku __username__",
"shareFile": "Podijeli datoteku",
"newFileFrom": "Nova datoteka od __username__",
"sentFileLabel": " je poslao/la datoteku: ",
"sending": "Slanje: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Stai chiamando __username__.<br/>Attendi che risponda",
"acceptCallFrom": "Vuoi accettare la chiamata da __username__ ?",
"hangupWith": "Riaggancia la chiamata con __username__",
"hangup": "Riaggancia",
"callUser": "Chiama __username__",
"call": "Chiama",
"videoOff": "Video disattivato",
"videoDisabled": "Video disabilitato",
"noUsersOnline": "Nessun altro utente online al momento",
@@ -209,6 +211,7 @@
"cancelled": "Trasferimento file annullato",
"cancelledByRemote": "Trasferimento file annullato dal remoto",
"sendToUser": "Invia file a __username__",
"shareFile": "Condividi file",
"newFileFrom": "Nuovo file da __username__",
"sentFileLabel": " ha inviato il file: ",
"sending": "Invio: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "__username__ に発信しています。<br/>応答するまでお待ちください。",
"acceptCallFrom": "__username__ からの通話を受けますか?",
"hangupWith": "__username__ との通話を終了",
"hangup": "通話終了",
"callUser": "__username__ に発信",
"call": "発信",
"videoOff": "ビデオオフ",
"videoDisabled": "ビデオ無効",
"noUsersOnline": "他のユーザーはまだオンラインではありません",
@@ -209,6 +211,7 @@
"cancelled": "ファイル転送がキャンセルされました",
"cancelledByRemote": "相手によってファイル転送がキャンセルされました",
"sendToUser": "__username__ にファイルを送信",
"shareFile": "ファイルを共有",
"newFileFrom": "__username__ から新しいファイル",
"sentFileLabel": " がファイルを送信: ",
"sending": "送信中: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Você está ligando para __username__.<br/>Aguarde ele/ela atender",
"acceptCallFrom": "Deseja aceitar a chamada de __username__ ?",
"hangupWith": "Encerrar chamada com __username__",
"hangup": "Desligar",
"callUser": "Ligar para __username__",
"call": "Ligar",
"videoOff": "Vídeo desligado",
"videoDisabled": "Vídeo desativado",
"noUsersOnline": "Nenhum outro usuário online ainda",
@@ -209,6 +211,7 @@
"cancelled": "Transferência de arquivo cancelada",
"cancelledByRemote": "Transferência de arquivo cancelada pelo remoto",
"sendToUser": "Enviar arquivo para __username__",
"shareFile": "Compartilhar arquivo",
"newFileFrom": "Novo arquivo de __username__",
"sentFileLabel": " enviou o arquivo: ",
"sending": "Enviando: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Вы звоните __username__.<br/>Пожалуйста, дождитесь ответа",
"acceptCallFrom": "Хотите принять звонок от __username__ ?",
"hangupWith": "Завершить звонок с __username__",
"hangup": "Завершить",
"callUser": "Позвонить __username__",
"call": "Позвонить",
"videoOff": "Видео выключено",
"videoDisabled": "Видео отключено",
"noUsersOnline": "Пока нет других пользователей онлайн",
@@ -209,6 +211,7 @@
"cancelled": "Передача файла отменена",
"cancelledByRemote": "Передача файла отменена удалённой стороной",
"sendToUser": "Отправить файл пользователю __username__",
"shareFile": "Поделиться файлом",
"newFileFrom": "Новый файл от __username__",
"sentFileLabel": " отправил файл: ",
"sending": "Отправка: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "Zoveš __username__.<br/>Sačekaj da se javi",
"acceptCallFrom": "Da li želiš da prihvatiš poziv od __username__ ?",
"hangupWith": "Prekini poziv sa __username__",
"hangup": "Prekini poziv",
"callUser": "Pozovi __username__",
"call": "Pozovi",
"videoOff": "Video isključen",
"videoDisabled": "Video onemogućen",
"noUsersOnline": "Još nema drugih korisnika na mreži",
@@ -209,6 +211,7 @@
"cancelled": "Prenos fajla je otkazan",
"cancelledByRemote": "Prenos fajla je otkazala udaljena strana",
"sendToUser": "Pošalji fajl korisniku __username__",
"shareFile": "Podeli fajl",
"newFileFrom": "Novi fajl od __username__",
"sentFileLabel": " poslao fajl: ",
"sending": "Slanje: __filename__",
+3
View File
@@ -29,7 +29,9 @@
"callingUser": "你正在呼叫 __username__。<br/>请等待对方接听。",
"acceptCallFrom": "是否接受来自 __username__ 的通话?",
"hangupWith": "挂断与 __username__ 的通话",
"hangup": "挂断",
"callUser": "呼叫 __username__",
"call": "呼叫",
"videoOff": "视频已关闭",
"videoDisabled": "视频已禁用",
"noUsersOnline": "目前没有其他用户在线",
@@ -209,6 +211,7 @@
"cancelled": "文件传输已取消",
"cancelledByRemote": "文件传输已被对方取消",
"sendToUser": "发送文件给 __username__",
"shareFile": "分享文件",
"newFileFrom": "来自 __username__ 的新文件",
"sentFileLabel": " 发送了文件: ",
"sending": "发送中: __filename__",
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "call-me",
"version": "1.3.34",
"version": "1.3.35",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "call-me",
"version": "1.3.34",
"version": "1.3.35",
"license": "AGPLv3",
"dependencies": {
"@ngrok/ngrok": "1.7.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "call-me",
"version": "1.3.34",
"version": "1.3.35",
"description": "Your Go-To for Instant Video Calls",
"author": "Miroslav Pejic - miroslav.pejic.85@gmail.com",
"license": "AGPLv3",
+28 -7
View File
@@ -2597,14 +2597,13 @@ function renderUserList() {
// Create call/hangup button based on active call state
const actionBtnEl = document.createElement('button');
actionBtnEl.style.marginRight = '10px';
actionBtnEl.style.cursor = 'pointer';
if (isInActiveCall) {
// Show hang-up button only if in active call (user has answered)
actionBtnEl.className = 'btn btn-custom btn-danger btn-m hangup-user-btn';
actionBtnEl.innerHTML = '<i class="fas fa-phone-slash"></i>';
actionBtnEl.title = t('room.hangupWith', { username: user });
actionBtnEl.title = t('room.hangup');
actionBtnEl.addEventListener('click', (e) => {
e.stopPropagation();
if (!userSignedIn) return;
@@ -2618,7 +2617,7 @@ function renderUserList() {
// Show call button if not in active call
actionBtnEl.className = 'btn btn-custom btn-warning btn-m call-user-btn';
actionBtnEl.innerHTML = '<i class="fas fa-phone"></i>';
actionBtnEl.title = t('room.callUser', { username: user });
actionBtnEl.title = t('room.call');
actionBtnEl.addEventListener('click', (e) => {
e.stopPropagation();
if (!userSignedIn) return;
@@ -2634,9 +2633,8 @@ function renderUserList() {
const sendFileBtn = document.createElement('button');
sendFileBtn.className = 'btn btn-custom btn-success btn-m';
sendFileBtn.innerHTML = '<i class="fas fa-paperclip"></i>';
sendFileBtn.style.marginRight = '10px';
sendFileBtn.style.cursor = 'pointer';
sendFileBtn.title = t('file.sendToUser', { username: user });
sendFileBtn.title = t('file.shareFile');
sendFileBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (!userSignedIn) return;
@@ -2657,9 +2655,32 @@ function renderUserList() {
}
});
li.appendChild(actionBtnEl);
li.appendChild(sendFileBtn);
// User avatar with initials
const avatarColors = [
'linear-gradient(135deg, #3b82f6, #1d4ed8)',
'linear-gradient(135deg, #8b5cf6, #6d28d9)',
'linear-gradient(135deg, #10b981, #047857)',
'linear-gradient(135deg, #f59e0b, #d97706)',
'linear-gradient(135deg, #ef4444, #b91c1c)',
'linear-gradient(135deg, #06b6d4, #0e7490)',
'linear-gradient(135deg, #ec4899, #be185d)',
];
const avatarDiv = document.createElement('div');
avatarDiv.className = 'user-avatar';
const initials = user
.split(/[\s_-]+/)
.map((w) => w[0])
.join('')
.substring(0, 2);
avatarDiv.textContent = initials;
let hash = 0;
for (let i = 0; i < user.length; i++) hash = user.charCodeAt(i) + ((hash << 5) - hash);
avatarDiv.style.background = avatarColors[Math.abs(hash) % avatarColors.length];
li.appendChild(avatarDiv);
li.appendChild(nameSpan);
li.appendChild(sendFileBtn);
li.appendChild(actionBtnEl);
li.addEventListener('click', () => {
if (!userSignedIn) return;
+10 -7
View File
@@ -323,13 +323,16 @@
<!-- Users Tab Content -->
<div id="usersContent" class="tab-content active">
<div class="user-search-bar">
<input
type="text"
id="userSearchInput"
placeholder="Search users..."
data-i18n-placeholder="room.searchUsers"
autocomplete="off"
/>
<div class="user-search-wrapper">
<i class="fas fa-search"></i>
<input
type="text"
id="userSearchInput"
placeholder="Search users..."
data-i18n-placeholder="room.searchUsers"
autocomplete="off"
/>
</div>
</div>
<ul id="userList" class="user-list"></ul>
</div>
+110 -26
View File
@@ -1270,6 +1270,25 @@ input {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-left: 1px solid var(--glass-border);
border-radius: 16px 0 0 16px;
}
/* Custom scrollbar for sidebar */
.user-sidebar ::-webkit-scrollbar {
width: 6px;
}
.user-sidebar ::-webkit-scrollbar-track {
background: transparent;
}
.user-sidebar ::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
border-radius: 3px;
}
.user-sidebar ::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.25);
}
.user-sidebar.active {
@@ -1289,6 +1308,7 @@ input {
padding: var(--spacing-md);
background: rgba(15, 20, 25, 0.95);
border-bottom: 1px solid var(--glass-border);
border-radius: 16px 0 0 0;
gap: var(--spacing-sm);
overflow: visible;
backdrop-filter: blur(12px);
@@ -1300,7 +1320,7 @@ input {
gap: 6px;
flex: 1;
min-width: 0;
overflow: hidden;
overflow: visible;
}
.sidebar-tab {
@@ -1505,9 +1525,28 @@ input {
border-bottom: 1px solid var(--glass-border);
}
.user-search-wrapper {
position: relative;
display: flex;
align-items: center;
}
.user-search-wrapper i {
position: absolute;
left: 14px;
color: var(--text-muted);
font-size: var(--font-size-sm);
pointer-events: none;
transition: color var(--transition-base);
}
.user-search-wrapper:focus-within i {
color: var(--primary-light);
}
#userSearchInput {
width: 100%;
padding: 12px 16px;
padding: 12px 16px 12px 40px;
border-radius: var(--border-radius);
border: 2px solid var(--glass-border);
background: rgba(30, 35, 50, 0.6);
@@ -1535,13 +1574,14 @@ input {
.user-list {
flex: 1;
margin: 0;
padding: var(--spacing-sm) 0;
padding: var(--spacing-sm) var(--spacing-md);
list-style: none;
overflow-y: auto;
overflow-x: hidden;
}
.user-list li {
padding: 14px var(--spacing-lg);
padding: 12px var(--spacing-md);
color: #ffffff;
cursor: pointer;
transition: all var(--transition-base);
@@ -1550,26 +1590,28 @@ input {
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
border-left: 3px solid transparent;
border-radius: var(--border-radius-sm);
position: relative;
margin: 2px 0;
gap: var(--spacing-sm);
}
.user-list li::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
top: 4px;
bottom: 4px;
width: 0;
background: var(--primary-color);
border-radius: 0 3px 3px 0;
transition: width var(--transition-base);
}
.user-list li:hover {
background: rgba(59, 130, 246, 0.1);
background: rgba(59, 130, 246, 0.08);
color: var(--primary-light);
border-left-color: var(--primary-color);
transform: translateX(4px);
}
.user-list li:hover::before {
@@ -1577,7 +1619,7 @@ input {
}
.user-list li.selected {
background: linear-gradient(90deg, rgba(59, 130, 246, 0.2), transparent);
background: linear-gradient(90deg, rgba(59, 130, 246, 0.15), transparent);
color: var(--primary-light);
border-left-color: var(--primary-color);
font-weight: var(--font-weight-semibold);
@@ -1587,10 +1629,25 @@ input {
width: 3px;
}
/* User Avatar */
.user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-sm);
font-weight: var(--font-weight-bold);
color: #fff;
flex-shrink: 0;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* User List Buttons */
.user-list li .btn-custom {
flex-shrink: 0;
margin-right: 10px;
vertical-align: middle;
}
@@ -1731,7 +1788,6 @@ input {
.chat-message:hover {
background: rgba(30, 35, 50, 0.8);
border-left-color: var(--glass-border-hover);
transform: translateX(4px);
box-shadow: var(--shadow-sm);
}
@@ -1778,6 +1834,7 @@ input {
padding: var(--spacing-md);
background: rgba(15, 20, 25, 0.95);
border-top: 1px solid var(--glass-border);
border-radius: 0 0 0 16px;
gap: var(--spacing-sm);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
@@ -1815,14 +1872,18 @@ input {
/* Emoji Picker Container */
.emoji-picker-container {
position: absolute;
bottom: 80px;
bottom: 90px;
left: 16px;
z-index: 1001;
border-radius: 8px;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.5),
0 0 0 1px var(--glass-border);
max-width: 320px;
display: none; /* Hidden by default */
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
.emoji-picker-container.show {
@@ -1830,11 +1891,11 @@ input {
}
.emoji-picker-container em-emoji-picker {
--rgb-background: 30, 32, 36;
--rgb-accent: 0, 123, 255;
--rgb-border: 255, 255, 255, 0.1;
--rgb-background: 30, 35, 50;
--rgb-accent: 59, 130, 246;
--rgb-border: 255, 255, 255, 0.12;
--rgb-color: 255, 255, 255;
--border-radius: 8px;
--border-radius: 12px;
width: 320px;
height: 400px;
}
@@ -1900,6 +1961,7 @@ input {
background: rgba(15, 20, 25, 0.4);
height: 100%;
overflow-y: auto;
border-radius: 0 0 0 16px;
}
.settings-title {
@@ -2137,6 +2199,11 @@ input {
z-index: 1000;
height: 100dvh;
padding-bottom: env(safe-area-inset-bottom, 0px);
border-radius: 0;
}
.user-sidebar-header {
border-radius: 0;
}
.tab-content {
@@ -2176,6 +2243,11 @@ input {
.chat-form {
padding: 12px;
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
border-radius: 0;
}
.settings-section {
border-radius: 0;
}
.btn-emoji {
@@ -2977,13 +3049,14 @@ z-index:
.user-list-empty {
text-align: center;
padding: 40px 20px !important;
padding: 48px 24px !important;
color: var(--text-muted);
cursor: default !important;
border-left: none !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
border-radius: 0 !important;
}
.user-list-empty:hover {
@@ -3001,38 +3074,49 @@ z-index:
}
.user-list-empty i {
font-size: 2rem;
margin-bottom: 12px;
font-size: 2.5rem;
margin-bottom: 16px;
display: block;
opacity: 0.5;
opacity: 0.35;
color: var(--primary-light);
}
.user-list-empty p {
margin: 4px 0;
font-size: var(--font-size-sm);
width: 100%;
line-height: 1.5;
}
.user-list-empty p:first-of-type {
font-weight: var(--font-weight-semibold);
color: var(--text-secondary);
font-size: var(--font-size-base);
}
.user-list-empty p:last-child {
font-size: 0.85rem;
opacity: 0.7;
}
.chat-empty-state {
text-align: center;
padding: 40px 20px;
padding: 48px 24px;
color: var(--text-muted);
}
.chat-empty-state i {
font-size: 2rem;
margin-bottom: 12px;
font-size: 2.5rem;
margin-bottom: 16px;
display: block;
opacity: 0.5;
opacity: 0.35;
color: var(--primary-light);
}
.chat-empty-state p {
margin: 4px 0;
font-size: var(--font-size-sm);
line-height: 1.5;
}
/*--------------------------------------------------------------