Compare commits

..

No commits in common. "6dbd5e77c4370792f68f16bd67a4504d1eb6c35c" and "3acd082fb0bfadcbc89740ea9e5791c4ed5a7747" have entirely different histories.

2 changed files with 29 additions and 103 deletions

20
main.js
View file

@ -1,7 +1,6 @@
const { app, BrowserWindow, session, desktopCapturer, ipcMain, clipboard } = require('electron'); const { app, BrowserWindow, session, desktopCapturer, ipcMain, clipboard } = require('electron');
const { autoUpdater } = require('electron-updater'); const { autoUpdater } = require('electron-updater');
const path = require('path'); const path = require('path');
const packageJson = require('./package.json');
const updateState = { const updateState = {
status: 'idle', // idle | checking | available | downloading | downloaded | not-available | error status: 'idle', // idle | checking | available | downloading | downloaded | not-available | error
@ -25,22 +24,6 @@ function isNewerVersionAvailable(info) {
let updateCheckInProgress = false; let updateCheckInProgress = false;
function resolveBackendUrl() {
const configuredUrl = (process.env.CHATTZ_URL || process.env.APP_BASE_URL || '').trim();
if (configuredUrl) {
return configuredUrl.replace(/\/$/, '');
}
const packagedFallback = typeof packageJson.homepage === 'string'
? packageJson.homepage.trim()
: '';
if (app.isPackaged && packagedFallback) {
return packagedFallback.replace(/\/$/, '');
}
return 'http://localhost:3000';
}
async function runUpdateCheck(reason = 'manual') { async function runUpdateCheck(reason = 'manual') {
if (updateCheckInProgress) { if (updateCheckInProgress) {
console.log(`Update check skipped (${reason}): another check is already in progress`); console.log(`Update check skipped (${reason}): another check is already in progress`);
@ -73,9 +56,8 @@ async function runUpdateCheck(reason = 'manual') {
function createWindow() { function createWindow() {
const sess = session.fromPartition('persist:chattz'); const sess = session.fromPartition('persist:chattz');
const backendUrl = resolveBackendUrl(); const backendUrl = (process.env.CHATTZ_URL || process.env.APP_BASE_URL || 'http://localhost:3000').replace(/\/$/, '');
const backendOrigin = new URL(backendUrl).origin; const backendOrigin = new URL(backendUrl).origin;
console.log(`Desktop backend URL: ${backendUrl}`);
const win = new BrowserWindow({ const win = new BrowserWindow({
width: 1200, width: 1200,

View file

@ -576,103 +576,50 @@ function renderChannels() {
pList.style.paddingLeft = "24px"; pList.style.paddingLeft = "24px";
for (const p of participants) { for (const p of participants) {
const pRow = document.createElement("div"); const pRow = document.createElement("div");
pRow.className = `channel-row voice-participant-row ${p.is_speaking ? 'voice-speaking' : ''}`; pRow.className = `channel-row ${p.is_speaking ? 'voice-speaking' : ''}`;
pRow.style.display = "block";
pRow.style.padding = "2px 8px"; pRow.style.padding = "2px 8px";
pRow.style.flexWrap = "wrap"; pRow.style.flexWrap = "wrap";
const topRow = document.createElement("div"); let sliderHtml = '';
topRow.style.display = "flex"; if (p.user_id !== state.me.id) {
topRow.style.alignItems = "center"; const vol = state.userVolumes.get(p.user_id) ?? 1.0;
topRow.style.gap = "8px"; const isVisible = state.voice.visibleVolumeSliders.has(p.user_id);
topRow.style.width = "100%"; sliderHtml = `
<div class="user-volume-control ${isVisible ? 'show-volume' : ''}" data-user-id="${p.user_id}">
const avatar = document.createElement("div"); <i data-lucide="volume-2" style="width: 12px; height: 12px; opacity: 0.6;"></i>
avatar.className = "avatar"; <input type="range" min="0" max="2" step="0.1" value="${vol}" class="volume-slider">
avatar.style.width = "20px"; <span class="vol-pct">${Math.round(vol * 100)}%</span>
avatar.style.height = "20px"; </div>
avatar.style.fontSize = "10px"; `;
avatar.textContent = shortName(p.display_name);
topRow.appendChild(avatar);
const name = document.createElement("span");
name.style.flex = "1";
name.style.overflow = "hidden";
name.style.textOverflow = "ellipsis";
name.textContent = p.display_name;
topRow.appendChild(name);
const isRemoteParticipant = p.user_id !== state.me.id;
if (isRemoteParticipant) {
const volumeToggle = document.createElement("button");
volumeToggle.type = "button";
volumeToggle.title = "Toggle volume slider";
volumeToggle.style.display = "grid";
volumeToggle.style.placeItems = "center";
volumeToggle.style.width = "20px";
volumeToggle.style.height = "20px";
volumeToggle.style.color = "var(--text-muted)";
volumeToggle.innerHTML = '<i data-lucide="volume-2" style="width: 14px; height: 14px;"></i>';
topRow.appendChild(volumeToggle);
volumeToggle.addEventListener('click', (e) => {
toggleVolumeSlider(e);
});
} }
if (p.is_muted) { pRow.innerHTML = `
const muteIcon = document.createElement("i"); <div style="display: flex; align-items: center; gap: 8px; width: 100%;">
muteIcon.setAttribute("data-lucide", "mic-off"); <div class="avatar" style="width:20px;height:20px;font-size:10px">${shortName(p.display_name)}</div>
muteIcon.className = "voice-muted-icon"; <span style="flex: 1; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(p.display_name)}</span>
topRow.appendChild(muteIcon); ${p.is_muted ? '<i data-lucide="mic-off" class="voice-muted-icon"></i>' : ''}
} </div>
${sliderHtml}
pRow.appendChild(topRow); `;
const volumeControl = document.createElement("div");
volumeControl.className = `user-volume-control ${state.voice.visibleVolumeSliders.has(p.user_id) ? 'show-volume' : ''}`;
volumeControl.dataset.userId = p.user_id;
if (isRemoteParticipant) {
const volumeIcon = document.createElement("i");
volumeIcon.setAttribute("data-lucide", "volume-2");
volumeIcon.style.width = "12px";
volumeIcon.style.height = "12px";
volumeIcon.style.opacity = "0.6";
volumeControl.appendChild(volumeIcon);
const slider = document.createElement("input");
slider.type = "range";
slider.min = "0";
slider.max = "2";
slider.step = "0.1";
slider.value = String(state.userVolumes.get(p.user_id) ?? 1.0);
slider.className = "volume-slider";
volumeControl.appendChild(slider);
const volumePct = document.createElement("span");
volumePct.className = "vol-pct";
volumePct.textContent = `${Math.round((state.userVolumes.get(p.user_id) ?? 1.0) * 100)}%`;
volumeControl.appendChild(volumePct);
const slider = pRow.querySelector('.volume-slider');
if (slider) {
slider.addEventListener('input', (e) => { slider.addEventListener('input', (e) => {
const val = parseFloat(e.target.value); const val = parseFloat(e.target.value);
state.userVolumes.set(p.user_id, val); state.userVolumes.set(p.user_id, val);
volumePct.textContent = `${Math.round(val * 100)}%`; pRow.querySelector('.vol-pct').textContent = `${Math.round(val * 100)}%`;
const gainNode = state.voice.peerGainNodes.get(p.user_id); const gainNode = state.voice.peerGainNodes.get(p.user_id);
if (gainNode && state.voice.audioContext) { if (gainNode) {
gainNode.gain.setTargetAtTime(val, state.voice.audioContext.currentTime, 0.05); gainNode.gain.setTargetAtTime(val, state.voice.audioContext.currentTime, 0.05);
} }
}); });
// Stop propagation to prevent joining channel again when clicking slider
volumeControl.addEventListener('click', (e) => e.stopPropagation()); slider.addEventListener('click', (e) => e.stopPropagation());
volumeControl.addEventListener('mousedown', (e) => e.stopPropagation());
pRow.appendChild(volumeControl);
} }
const toggleVolumeSlider = (e) => { const toggleVolumeSlider = (e) => {
if (!isRemoteParticipant) return; if (p.user_id === state.me.id) return;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (state.voice.visibleVolumeSliders.has(p.user_id)) { if (state.voice.visibleVolumeSliders.has(p.user_id)) {
@ -686,9 +633,6 @@ function renderChannels() {
// Electron can swallow contextmenu events on some platforms; use // Electron can swallow contextmenu events on some platforms; use
// right-button mousedown as a reliable fallback for slider toggle. // right-button mousedown as a reliable fallback for slider toggle.
pRow.addEventListener('contextmenu', toggleVolumeSlider); pRow.addEventListener('contextmenu', toggleVolumeSlider);
pRow.addEventListener('auxclick', (e) => {
if (e.button === 2) toggleVolumeSlider(e);
});
pRow.addEventListener('mousedown', (e) => { pRow.addEventListener('mousedown', (e) => {
if (e.button === 2) toggleVolumeSlider(e); if (e.button === 2) toggleVolumeSlider(e);
}); });