SHA256
UI: иконки управления звонком
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.348
|
client.version=1.2.349
|
||||||
server.version=1.2.319
|
server.version=1.2.319
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ let hangupBtn = null;
|
|||||||
let minimizeBtn = null;
|
let minimizeBtn = null;
|
||||||
let barEl = null;
|
let barEl = null;
|
||||||
let barTextEl = null;
|
let barTextEl = null;
|
||||||
|
let barActionsEl = null;
|
||||||
|
let barMuteBtn = null;
|
||||||
|
let barHangupBtn = null;
|
||||||
let unbind = null;
|
let unbind = null;
|
||||||
let tickerId = 0;
|
let tickerId = 0;
|
||||||
let currentSnapshot = null;
|
let currentSnapshot = null;
|
||||||
@@ -55,6 +58,59 @@ function buildBarText(snapshot) {
|
|||||||
return `Звонок с ${peer} · ${elapsed}`;
|
return `Звонок с ${peer} · ${elapsed}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stopEvent(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIconSvg(name) {
|
||||||
|
if (name === 'mic-on') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="3.5" width="6" height="11" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M6 11.5a6 6 0 0 0 12 0" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 17.5v3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M8.5 20.5h7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>';
|
||||||
|
}
|
||||||
|
if (name === 'mic-off') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="3.5" width="6" height="11" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M6 11.5a6 6 0 0 0 7.2 5.88" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M12 17.5v3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M8.5 20.5h7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M5 5l14 14" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>';
|
||||||
|
}
|
||||||
|
if (name === 'hangup') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 7l10 10M17 7 7 17" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>';
|
||||||
|
}
|
||||||
|
if (name === 'minimize') {
|
||||||
|
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 6v12M7.5 10.5 12 6l4.5 4.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButtonIcon(button, iconName, label) {
|
||||||
|
if (!button) return;
|
||||||
|
button.innerHTML = getIconSvg(iconName);
|
||||||
|
button.setAttribute('aria-label', label);
|
||||||
|
button.title = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMuteButtons(muted) {
|
||||||
|
const iconName = muted ? 'mic-off' : 'mic-on';
|
||||||
|
const label = muted ? 'Включить микрофон' : 'Выключить микрофон';
|
||||||
|
setButtonIcon(muteBtn, iconName, label);
|
||||||
|
setButtonIcon(barMuteBtn, iconName, label);
|
||||||
|
if (muteBtn) muteBtn.dataset.muted = muted ? '1' : '0';
|
||||||
|
if (barMuteBtn) barMuteBtn.dataset.muted = muted ? '1' : '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMuteClick(event) {
|
||||||
|
stopEvent(event);
|
||||||
|
const nowMuted = event.currentTarget?.dataset?.muted === '1';
|
||||||
|
await setMicMuted(!nowMuted);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEndCallClick(event) {
|
||||||
|
stopEvent(event);
|
||||||
|
if (!currentSnapshot) return;
|
||||||
|
if (currentSnapshot.canDecline && !currentSnapshot.canHangup) {
|
||||||
|
await declineIncomingCall();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await hangupActiveCall();
|
||||||
|
}
|
||||||
|
|
||||||
function setMinimized(nextValue) {
|
function setMinimized(nextValue) {
|
||||||
minimized = Boolean(nextValue);
|
minimized = Boolean(nextValue);
|
||||||
render();
|
render();
|
||||||
@@ -81,18 +137,33 @@ function ensureUi() {
|
|||||||
rootEl.className = 'call-ui-root';
|
rootEl.className = 'call-ui-root';
|
||||||
rootEl.hidden = true;
|
rootEl.hidden = true;
|
||||||
|
|
||||||
barEl = document.createElement('button');
|
barEl = document.createElement('div');
|
||||||
barEl.type = 'button';
|
|
||||||
barEl.className = 'call-minimized-bar';
|
barEl.className = 'call-minimized-bar';
|
||||||
barEl.hidden = true;
|
barEl.hidden = true;
|
||||||
barEl.addEventListener('click', () => {
|
|
||||||
|
barTextEl = document.createElement('button');
|
||||||
|
barTextEl.type = 'button';
|
||||||
|
barTextEl.className = 'call-minimized-bar-text';
|
||||||
|
barTextEl.addEventListener('click', () => {
|
||||||
if (!currentSnapshot) return;
|
if (!currentSnapshot) return;
|
||||||
setMinimized(false);
|
setMinimized(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
barTextEl = document.createElement('span');
|
barActionsEl = document.createElement('div');
|
||||||
barTextEl.className = 'call-minimized-bar-text';
|
barActionsEl.className = 'call-minimized-bar-actions';
|
||||||
barEl.append(barTextEl);
|
|
||||||
|
barMuteBtn = document.createElement('button');
|
||||||
|
barMuteBtn.type = 'button';
|
||||||
|
barMuteBtn.className = 'call-icon-btn call-icon-btn--bar call-icon-btn--secondary';
|
||||||
|
barMuteBtn.addEventListener('click', handleMuteClick);
|
||||||
|
|
||||||
|
barHangupBtn = document.createElement('button');
|
||||||
|
barHangupBtn.type = 'button';
|
||||||
|
barHangupBtn.className = 'call-icon-btn call-icon-btn--bar call-icon-btn--danger';
|
||||||
|
barHangupBtn.addEventListener('click', handleEndCallClick);
|
||||||
|
|
||||||
|
barActionsEl.append(barMuteBtn, barHangupBtn);
|
||||||
|
barEl.append(barTextEl, barActionsEl);
|
||||||
|
|
||||||
overlayEl = document.createElement('div');
|
overlayEl = document.createElement('div');
|
||||||
overlayEl.className = 'call-overlay';
|
overlayEl.className = 'call-overlay';
|
||||||
@@ -109,9 +180,10 @@ function ensureUi() {
|
|||||||
|
|
||||||
minimizeBtn = document.createElement('button');
|
minimizeBtn = document.createElement('button');
|
||||||
minimizeBtn.type = 'button';
|
minimizeBtn.type = 'button';
|
||||||
minimizeBtn.className = 'secondary-btn call-overlay-minimize-btn';
|
minimizeBtn.className = 'call-icon-btn call-icon-btn--secondary call-overlay-minimize-btn';
|
||||||
minimizeBtn.textContent = 'Свернуть';
|
setButtonIcon(minimizeBtn, 'minimize', 'Свернуть звонок');
|
||||||
minimizeBtn.addEventListener('click', () => {
|
minimizeBtn.addEventListener('click', (event) => {
|
||||||
|
stopEvent(event);
|
||||||
if (!currentSnapshot) return;
|
if (!currentSnapshot) return;
|
||||||
setMinimized(true);
|
setMinimized(true);
|
||||||
});
|
});
|
||||||
@@ -124,12 +196,8 @@ function ensureUi() {
|
|||||||
|
|
||||||
muteBtn = document.createElement('button');
|
muteBtn = document.createElement('button');
|
||||||
muteBtn.type = 'button';
|
muteBtn.type = 'button';
|
||||||
muteBtn.className = 'secondary-btn';
|
muteBtn.className = 'call-icon-btn call-icon-btn--secondary';
|
||||||
muteBtn.textContent = 'Микрофон';
|
muteBtn.addEventListener('click', handleMuteClick);
|
||||||
muteBtn.addEventListener('click', async () => {
|
|
||||||
const nowMuted = muteBtn.dataset.muted === '1';
|
|
||||||
await setMicMuted(!nowMuted);
|
|
||||||
});
|
|
||||||
|
|
||||||
acceptBtn = document.createElement('button');
|
acceptBtn = document.createElement('button');
|
||||||
acceptBtn.type = 'button';
|
acceptBtn.type = 'button';
|
||||||
@@ -141,19 +209,17 @@ function ensureUi() {
|
|||||||
|
|
||||||
declineBtn = document.createElement('button');
|
declineBtn = document.createElement('button');
|
||||||
declineBtn.type = 'button';
|
declineBtn.type = 'button';
|
||||||
declineBtn.className = 'destructive-btn';
|
declineBtn.className = 'call-icon-btn call-icon-btn--danger';
|
||||||
declineBtn.textContent = 'Сбросить';
|
setButtonIcon(declineBtn, 'hangup', 'Отклонить звонок');
|
||||||
declineBtn.addEventListener('click', async () => {
|
declineBtn.addEventListener('click', async () => {
|
||||||
await declineIncomingCall();
|
await declineIncomingCall();
|
||||||
});
|
});
|
||||||
|
|
||||||
hangupBtn = document.createElement('button');
|
hangupBtn = document.createElement('button');
|
||||||
hangupBtn.type = 'button';
|
hangupBtn.type = 'button';
|
||||||
hangupBtn.className = 'destructive-btn';
|
hangupBtn.className = 'call-icon-btn call-icon-btn--danger';
|
||||||
hangupBtn.textContent = 'Положить трубку';
|
setButtonIcon(hangupBtn, 'hangup', 'Положить трубку');
|
||||||
hangupBtn.addEventListener('click', async () => {
|
hangupBtn.addEventListener('click', handleEndCallClick);
|
||||||
await hangupActiveCall();
|
|
||||||
});
|
|
||||||
|
|
||||||
controls.append(muteBtn, acceptBtn, declineBtn, hangupBtn);
|
controls.append(muteBtn, acceptBtn, declineBtn, hangupBtn);
|
||||||
headerEl.append(titleEl, minimizeBtn);
|
headerEl.append(titleEl, minimizeBtn);
|
||||||
@@ -171,8 +237,7 @@ function applyExpandedState(snapshot) {
|
|||||||
statusEl.textContent = snapshot.statusText || '';
|
statusEl.textContent = snapshot.statusText || '';
|
||||||
|
|
||||||
const muted = Boolean(snapshot.muted);
|
const muted = Boolean(snapshot.muted);
|
||||||
muteBtn.dataset.muted = muted ? '1' : '0';
|
updateMuteButtons(muted);
|
||||||
muteBtn.textContent = muted ? 'Включить микрофон' : 'Выключить микрофон';
|
|
||||||
|
|
||||||
muteBtn.hidden = !snapshot.canMute;
|
muteBtn.hidden = !snapshot.canMute;
|
||||||
muteBtn.disabled = !snapshot.canMute;
|
muteBtn.disabled = !snapshot.canMute;
|
||||||
@@ -186,6 +251,14 @@ function applyMinimizedState(snapshot) {
|
|||||||
overlayEl.hidden = true;
|
overlayEl.hidden = true;
|
||||||
barEl.hidden = false;
|
barEl.hidden = false;
|
||||||
barTextEl.textContent = buildBarText(snapshot);
|
barTextEl.textContent = buildBarText(snapshot);
|
||||||
|
const muted = Boolean(snapshot.muted);
|
||||||
|
updateMuteButtons(muted);
|
||||||
|
barMuteBtn.hidden = !snapshot.canMute;
|
||||||
|
barMuteBtn.disabled = !snapshot.canMute;
|
||||||
|
const canEnd = snapshot.canHangup || snapshot.canDecline;
|
||||||
|
barHangupBtn.hidden = !canEnd;
|
||||||
|
barHangupBtn.disabled = !canEnd;
|
||||||
|
setButtonIcon(barHangupBtn, 'hangup', snapshot.canDecline && !snapshot.canHangup ? 'Отклонить звонок' : 'Положить трубку');
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncShellState() {
|
function syncShellState() {
|
||||||
|
|||||||
@@ -1749,29 +1749,106 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
padding: calc(8px + env(safe-area-inset-top)) 14px 8px;
|
padding: calc(8px + env(safe-area-inset-top)) 14px 8px;
|
||||||
border: 0;
|
|
||||||
border-bottom: 1px solid rgba(159, 255, 196, 0.28);
|
border-bottom: 1px solid rgba(159, 255, 196, 0.28);
|
||||||
background: linear-gradient(180deg, rgba(30, 138, 79, 0.98), rgba(18, 106, 60, 0.98));
|
background: linear-gradient(180deg, rgba(30, 138, 79, 0.98), rgba(18, 106, 60, 0.98));
|
||||||
box-shadow: 0 10px 24px rgba(5, 30, 16, 0.28);
|
box-shadow: 0 10px 24px rgba(5, 30, 16, 0.28);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-minimized-bar-text {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
color: #f3fff8;
|
color: #f3fff8;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
pointer-events: auto;
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-minimized-bar-text {
|
.call-minimized-bar-actions {
|
||||||
display: block;
|
display: flex;
|
||||||
overflow: hidden;
|
align-items: center;
|
||||||
text-overflow: ellipsis;
|
gap: 8px;
|
||||||
white-space: nowrap;
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn {
|
||||||
|
width: 40px;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(132, 162, 228, 0.42);
|
||||||
|
background: linear-gradient(180deg, rgba(19, 35, 63, 0.9), rgba(14, 26, 50, 0.95));
|
||||||
|
color: #e7efff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
color: #93a6cc;
|
||||||
|
background: linear-gradient(180deg, rgba(18, 30, 54, 0.8), rgba(11, 20, 39, 0.86));
|
||||||
|
border-color: rgba(124, 145, 189, 0.28);
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn--secondary {
|
||||||
|
border-color: rgba(132, 162, 228, 0.42);
|
||||||
|
background: linear-gradient(180deg, rgba(19, 35, 63, 0.9), rgba(14, 26, 50, 0.95));
|
||||||
|
color: #e7efff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn--secondary:hover {
|
||||||
|
border-color: rgba(167, 195, 255, 0.76);
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn--danger {
|
||||||
|
border-color: rgba(255, 129, 147, 0.52);
|
||||||
|
background: linear-gradient(120deg, rgba(188, 42, 65, 0.96), rgba(142, 28, 47, 0.98));
|
||||||
|
color: #fff2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn--danger:hover {
|
||||||
|
border-color: rgba(255, 163, 176, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn--bar {
|
||||||
|
width: 34px;
|
||||||
|
min-width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.call-icon-btn--bar svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-overlay {
|
.call-overlay {
|
||||||
@@ -1823,6 +1900,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.call-accept-btn {
|
.call-accept-btn {
|
||||||
|
|||||||
Reference in New Issue
Block a user