Compare commits
No commits in common. "832de038ca54570ccfc3fbb785c162844a61919b" and "77a9f5ee88ecea16cd0226ba3c9eb955d1926c6c" have entirely different histories.
832de038ca
...
77a9f5ee88
9 changed files with 19 additions and 559 deletions
|
|
@ -11,25 +11,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Update version from tag
|
|
||||||
run: |
|
|
||||||
# Extract version from tag (e.g. v1.2.3 -> 1.2.3)
|
|
||||||
VERSION=${GITHUB_REF_NAME#v}
|
|
||||||
echo "Bumping version to $VERSION"
|
|
||||||
npm version $VERSION --no-git-tag-version
|
|
||||||
|
|
||||||
# Configure Git
|
|
||||||
git config user.name "Forgejo Actions"
|
|
||||||
git config user.email "actions@noreply.flegr.me"
|
|
||||||
|
|
||||||
# Commit and push back to main if version changed
|
|
||||||
if [ -n "$(git status --porcelain package.json)" ]; then
|
|
||||||
git add package.json package-lock.json
|
|
||||||
git commit -m "chore: bump version to $VERSION [skip ci]"
|
|
||||||
git push origin HEAD:main
|
|
||||||
fi
|
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run dist:linux
|
- run: npm run dist:linux
|
||||||
- name: Build Windows installer with 32-bit Wine prefix
|
- name: Build Windows installer with 32-bit Wine prefix
|
||||||
|
|
@ -55,9 +36,6 @@ jobs:
|
||||||
copy_first '*.AppImage' 'chattz-linux.AppImage'
|
copy_first '*.AppImage' 'chattz-linux.AppImage'
|
||||||
copy_first '*.exe' 'chattz-windows.exe'
|
copy_first '*.exe' 'chattz-windows.exe'
|
||||||
copy_first '*.msi' 'chattz-windows.msi'
|
copy_first '*.msi' 'chattz-windows.msi'
|
||||||
# Metadata files for electron-updater
|
|
||||||
copy_first 'latest-linux.yml' 'latest-linux.yml'
|
|
||||||
copy_first 'latest.yml' 'latest.yml'
|
|
||||||
- name: Cache Cargo registry
|
- name: Cache Cargo registry
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
334
desktop/app.js
334
desktop/app.js
|
|
@ -28,7 +28,6 @@ const state = {
|
||||||
viewMode: 'chat', // 'chat' or 'video'
|
viewMode: 'chat', // 'chat' or 'video'
|
||||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||||
peerGainNodes: new Map(), // userId -> GainNode
|
peerGainNodes: new Map(), // userId -> GainNode
|
||||||
visibleVolumeSliders: new Set(), // userIds whose sliders are visible
|
|
||||||
},
|
},
|
||||||
voicePresencePollId: null,
|
voicePresencePollId: null,
|
||||||
onlineUsers: new Set(),
|
onlineUsers: new Set(),
|
||||||
|
|
@ -112,9 +111,6 @@ const el = {
|
||||||
userName: document.getElementById("user-name"),
|
userName: document.getElementById("user-name"),
|
||||||
userAvatar: document.getElementById("user-avatar"),
|
userAvatar: document.getElementById("user-avatar"),
|
||||||
logoutBtn: document.getElementById("logout-btn"),
|
logoutBtn: document.getElementById("logout-btn"),
|
||||||
updateNotifier: document.getElementById("update-notifier"),
|
|
||||||
updateDownloadBtn: document.getElementById("update-download-btn"),
|
|
||||||
updateInstallBtn: document.getElementById("update-install-btn"),
|
|
||||||
|
|
||||||
// Voice Connection
|
// Voice Connection
|
||||||
voiceConnection: document.getElementById("voice-connection"),
|
voiceConnection: document.getElementById("voice-connection"),
|
||||||
|
|
@ -420,9 +416,8 @@ function renderChannels() {
|
||||||
let sliderHtml = '';
|
let sliderHtml = '';
|
||||||
if (p.user_id !== state.me.id) {
|
if (p.user_id !== state.me.id) {
|
||||||
const vol = state.userVolumes.get(p.user_id) ?? 1.0;
|
const vol = state.userVolumes.get(p.user_id) ?? 1.0;
|
||||||
const isVisible = state.voice.visibleVolumeSliders.has(p.user_id);
|
|
||||||
sliderHtml = `
|
sliderHtml = `
|
||||||
<div class="user-volume-control ${isVisible ? 'show-volume' : ''}" data-user-id="${p.user_id}">
|
<div class="user-volume-control" data-user-id="${p.user_id}">
|
||||||
<i data-lucide="volume-2" style="width: 12px; height: 12px; opacity: 0.6;"></i>
|
<i data-lucide="volume-2" style="width: 12px; height: 12px; opacity: 0.6;"></i>
|
||||||
<input type="range" min="0" max="2" step="0.1" value="${vol}" class="volume-slider">
|
<input type="range" min="0" max="2" step="0.1" value="${vol}" class="volume-slider">
|
||||||
<span class="vol-pct">${Math.round(vol * 100)}%</span>
|
<span class="vol-pct">${Math.round(vol * 100)}%</span>
|
||||||
|
|
@ -459,12 +454,10 @@ function renderChannels() {
|
||||||
pRow.addEventListener('contextmenu', (e) => {
|
pRow.addEventListener('contextmenu', (e) => {
|
||||||
if (p.user_id === state.me.id) return;
|
if (p.user_id === state.me.id) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (state.voice.visibleVolumeSliders.has(p.user_id)) {
|
const control = pRow.querySelector('.user-volume-control');
|
||||||
state.voice.visibleVolumeSliders.delete(p.user_id);
|
if (control) {
|
||||||
} else {
|
control.classList.toggle('show-volume');
|
||||||
state.voice.visibleVolumeSliders.add(p.user_id);
|
|
||||||
}
|
}
|
||||||
renderChannels();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pList.appendChild(pRow);
|
pList.appendChild(pRow);
|
||||||
|
|
@ -1943,324 +1936,6 @@ async function init() {
|
||||||
|
|
||||||
await loadGuilds();
|
await loadGuilds();
|
||||||
await loadDMConversations();
|
await loadDMConversations();
|
||||||
} catch (err) {
|
|
||||||
console.error("presence sync failed", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initUpdater() {
|
|
||||||
if (!window.electronAPI) return;
|
|
||||||
|
|
||||||
window.electronAPI.onUpdateAvailable((info) => {
|
|
||||||
console.log("Update available:", info.version);
|
|
||||||
el.updateNotifier.classList.remove("hidden");
|
|
||||||
el.updateDownloadBtn.classList.remove("hidden");
|
|
||||||
el.updateInstallBtn.classList.add("hidden");
|
|
||||||
});
|
|
||||||
|
|
||||||
window.electronAPI.onUpdateDownloaded((info) => {
|
|
||||||
console.log("Update downloaded:", info.version);
|
|
||||||
el.updateNotifier.classList.remove("hidden");
|
|
||||||
el.updateDownloadBtn.classList.add("hidden");
|
|
||||||
el.updateInstallBtn.classList.remove("hidden");
|
|
||||||
el.updateInstallBtn.title = `Install Update ${info.version}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
window.electronAPI.onUpdateError((err) => {
|
|
||||||
console.error("Update error:", err);
|
|
||||||
// Optionally hide indicator on error
|
|
||||||
// el.updateNotifier.classList.add("hidden");
|
|
||||||
});
|
|
||||||
|
|
||||||
el.updateDownloadBtn.onclick = () => {
|
|
||||||
el.updateDownloadBtn.disabled = true;
|
|
||||||
el.updateDownloadBtn.style.opacity = "0.5";
|
|
||||||
window.electronAPI.downloadUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
el.updateInstallBtn.onclick = () => {
|
|
||||||
window.electronAPI.quitAndInstall();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
lucide.createIcons();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
|
||||||
const jwtToken = searchParams.get("token");
|
|
||||||
const refreshToken = searchParams.get("refresh_token");
|
|
||||||
|
|
||||||
if (jwtToken || refreshToken) {
|
|
||||||
if (jwtToken) localStorage.setItem("chattz_token", jwtToken);
|
|
||||||
if (refreshToken) localStorage.setItem("chattz_refresh_token", refreshToken);
|
|
||||||
|
|
||||||
searchParams.delete("token");
|
|
||||||
searchParams.delete("refresh_token");
|
|
||||||
const nextQuery = searchParams.toString();
|
|
||||||
const nextUrl = `${location.pathname}${nextQuery ? `?${nextQuery}` : ""}`;
|
|
||||||
history.replaceState(null, "", nextUrl);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to parse token from URL:", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
el.loginBtn.onclick = () => { location.href = "/auth/login"; };
|
|
||||||
|
|
||||||
el.logoutBtn.onclick = async () => {
|
|
||||||
await leaveVoice();
|
|
||||||
localStorage.removeItem("chattz_token");
|
|
||||||
localStorage.removeItem("chattz_refresh_token");
|
|
||||||
try { await api("/auth/logout", { method: "POST" }); } catch (e) { }
|
|
||||||
location.reload();
|
|
||||||
};
|
|
||||||
|
|
||||||
el.addGuildBtn.onclick = () => { el.modalContainer.classList.remove("hidden"); };
|
|
||||||
el.createInviteBtn.onclick = createInviteLink;
|
|
||||||
el.modalCancel.onclick = () => { el.modalContainer.classList.add("hidden"); };
|
|
||||||
|
|
||||||
el.addTextBtn.onclick = () => {
|
|
||||||
if (!state.selectedGuildId) {
|
|
||||||
alert("Create or select a server first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
el.modalTitle.textContent = "Create Text Channel";
|
|
||||||
el.channelModal.classList.remove("hidden");
|
|
||||||
};
|
|
||||||
|
|
||||||
el.addVoiceBtn.onclick = () => {
|
|
||||||
if (!state.selectedGuildId) {
|
|
||||||
alert("Create or select a server first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
el.modalTitle.textContent = "Create Voice Channel";
|
|
||||||
el.channelModal.classList.remove("hidden");
|
|
||||||
};
|
|
||||||
|
|
||||||
el.channelModalCancel.onclick = () => { el.channelModal.classList.add("hidden"); };
|
|
||||||
|
|
||||||
el.guildForm.onsubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const name = el.guildName.value.trim();
|
|
||||||
if (!name) return;
|
|
||||||
try {
|
|
||||||
const guild = await api("/guilds", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ name }),
|
|
||||||
});
|
|
||||||
el.guildName.value = "";
|
|
||||||
el.modalContainer.classList.add("hidden");
|
|
||||||
state.guilds.push(guild);
|
|
||||||
state.selectedGuildId = guild.id;
|
|
||||||
state.selectedTextChannelId = null;
|
|
||||||
state.selectedVoiceChannelId = null;
|
|
||||||
state.selectedDmUserId = null;
|
|
||||||
state.selectedDmDisplayName = null;
|
|
||||||
localStorage.setItem(LAST_GUILD_STORAGE_KEY, guild.id);
|
|
||||||
renderGuilds();
|
|
||||||
await loadChannels();
|
|
||||||
await loadGuildMembers();
|
|
||||||
await refreshVoicePresence();
|
|
||||||
renderMessages([]);
|
|
||||||
updateHeaderLabels();
|
|
||||||
} catch (err) { alert(err.message); }
|
|
||||||
};
|
|
||||||
|
|
||||||
el.channelForm.onsubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!state.selectedGuildId) {
|
|
||||||
alert("Select a server first.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const kindInput = document.querySelector('input[name="channel-kind"]:checked');
|
|
||||||
const kind = kindInput ? kindInput.value : "text";
|
|
||||||
const channelName = el.channelName.value.trim();
|
|
||||||
if (!channelName) {
|
|
||||||
alert("Channel name is required.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const created = await api("/channels", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
guild_id: state.selectedGuildId,
|
|
||||||
name: channelName,
|
|
||||||
kind: kind,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
el.channelName.value = "";
|
|
||||||
el.channelModal.classList.add("hidden");
|
|
||||||
await loadChannels();
|
|
||||||
if (created.kind === "text") {
|
|
||||||
state.selectedTextChannelId = created.id;
|
|
||||||
state.selectedVoiceChannelId = null;
|
|
||||||
state.selectedDmUserId = null;
|
|
||||||
state.selectedDmDisplayName = null;
|
|
||||||
renderChannels();
|
|
||||||
renderDMs();
|
|
||||||
updateHeaderLabels();
|
|
||||||
await api(`/channels/${created.id}/messages?limit=100`).then(renderMessages);
|
|
||||||
} else {
|
|
||||||
state.selectedVoiceChannelId = created.id;
|
|
||||||
state.selectedTextChannelId = null;
|
|
||||||
state.selectedDmUserId = null;
|
|
||||||
state.selectedDmDisplayName = null;
|
|
||||||
renderChannels();
|
|
||||||
renderDMs();
|
|
||||||
updateHeaderLabels();
|
|
||||||
await joinVoice();
|
|
||||||
}
|
|
||||||
} catch (err) { alert(err.message); }
|
|
||||||
};
|
|
||||||
|
|
||||||
el.messageForm.onsubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const body = el.messageBody.value.trim();
|
|
||||||
if (!body) return;
|
|
||||||
try {
|
|
||||||
if (state.selectedTextChannelId) {
|
|
||||||
await api(`/channels/${state.selectedTextChannelId}/messages`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ body }),
|
|
||||||
});
|
|
||||||
} else if (state.selectedDmUserId) {
|
|
||||||
await api(`/dms/${state.selectedDmUserId}/messages`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ body }),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
el.messageBody.value = "";
|
|
||||||
const messages = state.selectedTextChannelId
|
|
||||||
? await api(`/channels/${state.selectedTextChannelId}/messages?limit=100`)
|
|
||||||
: await api(`/dms/${state.selectedDmUserId}/messages?limit=100`);
|
|
||||||
renderMessages(messages);
|
|
||||||
await loadDMConversations();
|
|
||||||
renderDMs();
|
|
||||||
} catch (err) { alert(err.message); }
|
|
||||||
};
|
|
||||||
|
|
||||||
el.voiceVideoBtn.onclick = toggleVideo;
|
|
||||||
el.voiceScreenBtn.onclick = toggleScreenShare;
|
|
||||||
el.voiceMuteBtn.onclick = toggleMute;
|
|
||||||
el.voiceLeaveBtn.onclick = leaveVoice;
|
|
||||||
|
|
||||||
el.addSoundBtn.onclick = () => {
|
|
||||||
el.soundModal.classList.remove("hidden");
|
|
||||||
};
|
|
||||||
el.soundModalCancel.onclick = () => {
|
|
||||||
el.soundModal.classList.add("hidden");
|
|
||||||
};
|
|
||||||
|
|
||||||
el.soundForm.onsubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!state.selectedGuildId) return;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('name', el.soundName.value);
|
|
||||||
formData.append('icon', el.soundIcon.value);
|
|
||||||
formData.append('file', el.soundFile.files[0]);
|
|
||||||
|
|
||||||
el.soundSubmitBtn.disabled = true;
|
|
||||||
el.soundSubmitBtn.textContent = 'Uploading...';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Need to use fetch directly because api() might not handle FormData correctly depending on implementation
|
|
||||||
const response = await fetch(`/guilds/${state.selectedGuildId}/sounds`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
if (!response.ok) {
|
|
||||||
const err = await response.json();
|
|
||||||
throw new Error(err.error || 'Upload failed');
|
|
||||||
}
|
|
||||||
el.soundModal.classList.add("hidden");
|
|
||||||
el.soundForm.reset();
|
|
||||||
await loadSounds();
|
|
||||||
} catch (err) {
|
|
||||||
alert(err.message);
|
|
||||||
} finally {
|
|
||||||
el.soundSubmitBtn.disabled = false;
|
|
||||||
el.soundSubmitBtn.textContent = 'Add Sound';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- GIF Picker Logic ---
|
|
||||||
const TENOR_API_KEY = "exTiFGKJ0CzESIHzVQWy3pRO8I1MAdpRomg95DBSu2sg6e7YcHgThMI4giGAx8D0"; // Placeholder / Public key if available, otherwise "YOUR_API_KEY"
|
|
||||||
const TENOR_CLIENT_KEY = "pavel-discord";
|
|
||||||
|
|
||||||
el.gifBtn.onclick = () => {
|
|
||||||
el.gifPicker.classList.remove("hidden");
|
|
||||||
searchGifs(""); // Initial featured search
|
|
||||||
};
|
|
||||||
|
|
||||||
el.gifPickerClose.onclick = () => {
|
|
||||||
el.gifPicker.classList.add("hidden");
|
|
||||||
};
|
|
||||||
|
|
||||||
let searchTimeout = null;
|
|
||||||
el.gifSearchInput.oninput = () => {
|
|
||||||
clearTimeout(searchTimeout);
|
|
||||||
searchTimeout = setTimeout(() => {
|
|
||||||
searchGifs(el.gifSearchInput.value);
|
|
||||||
}, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function searchGifs(query) {
|
|
||||||
el.gifResults.innerHTML = '<div class="gif-loading">Searching...</div>';
|
|
||||||
const baseUrl = "https://api.klipy.com/v2";
|
|
||||||
const endpoint = query
|
|
||||||
? `${baseUrl}/search?q=${encodeURIComponent(query)}&key=${TENOR_API_KEY}&client_key=${TENOR_CLIENT_KEY}&limit=20`
|
|
||||||
: `${baseUrl}/featured?key=${TENOR_API_KEY}&client_key=${TENOR_CLIENT_KEY}&limit=20`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(endpoint);
|
|
||||||
const data = await res.json();
|
|
||||||
renderGifs(data.results);
|
|
||||||
} catch (err) {
|
|
||||||
el.gifResults.innerHTML = '<div class="gif-loading">Error loading GIFs</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderGifs(gifs) {
|
|
||||||
el.gifResults.innerHTML = "";
|
|
||||||
if (!gifs || gifs.length === 0) {
|
|
||||||
el.gifResults.innerHTML = '<div class="gif-loading">No GIFs found</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gifs.forEach(gif => {
|
|
||||||
// Use tinygif for preview, standard gif for sending
|
|
||||||
const previewUrl = gif.media_formats.tinygif.url;
|
|
||||||
const fullUrl = gif.media_formats.gif.url;
|
|
||||||
|
|
||||||
const item = document.createElement("div");
|
|
||||||
item.className = "gif-item";
|
|
||||||
|
|
||||||
const img = document.createElement("img");
|
|
||||||
img.src = previewUrl;
|
|
||||||
img.loading = "lazy";
|
|
||||||
img.onclick = () => {
|
|
||||||
el.messageBody.value = fullUrl;
|
|
||||||
el.gifPicker.classList.add("hidden");
|
|
||||||
el.messageForm.dispatchEvent(new Event('submit'));
|
|
||||||
};
|
|
||||||
|
|
||||||
item.appendChild(img);
|
|
||||||
el.gifResults.appendChild(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
state.me = await api("/me");
|
|
||||||
el.userName.textContent = state.me.display_name;
|
|
||||||
el.userAvatar.textContent = state.me.display_name[0].toUpperCase();
|
|
||||||
|
|
||||||
state.guilds = await api("/guilds");
|
|
||||||
renderGuilds();
|
|
||||||
|
|
||||||
await loadDMConversations();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const online = await api("/presence");
|
const online = await api("/presence");
|
||||||
|
|
@ -2285,7 +1960,6 @@ async function init() {
|
||||||
|
|
||||||
initChatWs();
|
initChatWs();
|
||||||
startVoicePresencePolling();
|
startVoicePresencePolling();
|
||||||
initUpdater();
|
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("init failed", err);
|
console.error("init failed", err);
|
||||||
|
|
|
||||||
|
|
@ -114,11 +114,6 @@
|
||||||
<div class="user-status">Online</div>
|
<div class="user-status">Online</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-actions">
|
<div class="user-actions">
|
||||||
<div id="update-notifier" class="update-notifier hidden">
|
|
||||||
<button id="update-download-btn" title="Download Update"><i data-lucide="download"></i></button>
|
|
||||||
<button id="update-install-btn" title="Install Update" class="hidden"><i
|
|
||||||
data-lucide="arrow-up-circle"></i></button>
|
|
||||||
</div>
|
|
||||||
<button id="user-settings-btn" title="Settings"><i data-lucide="settings"></i></button>
|
<button id="user-settings-btn" title="Settings"><i data-lucide="settings"></i></button>
|
||||||
<button id="logout-btn" title="Logout"><i data-lucide="log-out"></i></button>
|
<button id="logout-btn" title="Logout"><i data-lucide="log-out"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,5 @@ const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
copyToClipboard: (text) => ipcRenderer.invoke('clipboard-write', text),
|
copyToClipboard: (text) => ipcRenderer.invoke('clipboard-write', text),
|
||||||
getConfig: () => ipcRenderer.invoke('get-config'),
|
getConfig: () => ipcRenderer.invoke('get-config')
|
||||||
checkForUpdates: () => ipcRenderer.send('check-for-updates'),
|
|
||||||
downloadUpdate: () => ipcRenderer.send('download-update'),
|
|
||||||
quitAndInstall: () => ipcRenderer.send('quit-and-install'),
|
|
||||||
onUpdateAvailable: (callback) => ipcRenderer.on('update-available', (event, info) => callback(info)),
|
|
||||||
onUpdateDownloaded: (callback) => ipcRenderer.on('update-downloaded', (event, info) => callback(info)),
|
|
||||||
onUpdateError: (callback) => ipcRenderer.on('update-error', (event, error) => callback(error))
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -621,35 +621,6 @@ select {
|
||||||
box-shadow: 0 0 4px var(--brand-glow);
|
box-shadow: 0 0 4px var(--brand-glow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-notifier {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-notifier button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 4px;
|
|
||||||
color: var(--brand);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-notifier button:hover {
|
|
||||||
transform: scale(1.2);
|
|
||||||
color: var(--brand-glow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-notifier button i {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|
|
||||||
39
main.js
39
main.js
|
|
@ -1,5 +1,4 @@
|
||||||
const { app, BrowserWindow, session, desktopCapturer, ipcMain, clipboard } = require('electron');
|
const { app, BrowserWindow, session, desktopCapturer, ipcMain, clipboard } = require('electron');
|
||||||
const { autoUpdater } = require('electron-updater');
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
|
|
@ -122,44 +121,6 @@ app.commandLine.appendSwitch('log-level', '3'); // Silences standard Chromium wa
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
||||||
// Configure Auto-Updater
|
|
||||||
autoUpdater.autoDownload = false;
|
|
||||||
autoUpdater.logger = console;
|
|
||||||
|
|
||||||
autoUpdater.on('update-available', (info) => {
|
|
||||||
const wins = BrowserWindow.getAllWindows();
|
|
||||||
if (wins.length > 0) wins[0].webContents.send('update-available', info);
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on('update-downloaded', (info) => {
|
|
||||||
const wins = BrowserWindow.getAllWindows();
|
|
||||||
if (wins.length > 0) wins[0].webContents.send('update-downloaded', info);
|
|
||||||
});
|
|
||||||
|
|
||||||
autoUpdater.on('error', (err) => {
|
|
||||||
const wins = BrowserWindow.getAllWindows();
|
|
||||||
if (wins.length > 0) wins[0].webContents.send('update-error', err.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('check-for-updates', () => {
|
|
||||||
autoUpdater.checkForUpdatesAndNotify().catch(err => {
|
|
||||||
console.error("Manual update check failed", err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('download-update', () => {
|
|
||||||
autoUpdater.downloadUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('quit-and-install', () => {
|
|
||||||
autoUpdater.quitAndInstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check once on startup
|
|
||||||
setTimeout(() => {
|
|
||||||
autoUpdater.checkForUpdatesAndNotify().catch(() => { });
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
createWindow();
|
createWindow();
|
||||||
|
|
|
||||||
105
package-lock.json
generated
105
package-lock.json
generated
|
|
@ -7,9 +7,6 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "chattz-electron",
|
"name": "chattz-electron",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
|
||||||
"electron-updater": "^6.8.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^34.5.8",
|
"electron": "^34.5.8",
|
||||||
"electron-builder": "^25.1.8"
|
"electron-builder": "^25.1.8"
|
||||||
|
|
@ -1094,6 +1091,7 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/assert-plus": {
|
"node_modules/assert-plus": {
|
||||||
|
|
@ -1848,6 +1846,7 @@
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
|
|
@ -2370,82 +2369,6 @@
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-updater": {
|
|
||||||
"version": "6.8.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz",
|
|
||||||
"integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"builder-util-runtime": "9.5.1",
|
|
||||||
"fs-extra": "^10.1.0",
|
|
||||||
"js-yaml": "^4.1.0",
|
|
||||||
"lazy-val": "^1.0.5",
|
|
||||||
"lodash.escaperegexp": "^4.1.2",
|
|
||||||
"lodash.isequal": "^4.5.0",
|
|
||||||
"semver": "~7.7.3",
|
|
||||||
"tiny-typed-emitter": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-updater/node_modules/builder-util-runtime": {
|
|
||||||
"version": "9.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz",
|
|
||||||
"integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.3.4",
|
|
||||||
"sax": "^1.2.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-updater/node_modules/fs-extra": {
|
|
||||||
"version": "10.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
|
||||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.0",
|
|
||||||
"jsonfile": "^6.0.1",
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-updater/node_modules/jsonfile": {
|
|
||||||
"version": "6.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
|
||||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-updater/node_modules/semver": {
|
|
||||||
"version": "7.7.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-updater/node_modules/universalify": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
|
@ -2981,6 +2904,7 @@
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
|
|
@ -3355,6 +3279,7 @@
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
|
|
@ -3422,6 +3347,7 @@
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||||
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lazystream": {
|
"node_modules/lazystream": {
|
||||||
|
|
@ -3497,12 +3423,6 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.escaperegexp": {
|
|
||||||
"version": "4.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
|
||||||
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.flatten": {
|
"node_modules/lodash.flatten": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||||
|
|
@ -3511,13 +3431,6 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isequal": {
|
|
||||||
"version": "4.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
|
||||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
|
||||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isplainobject": {
|
"node_modules/lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
|
@ -3874,6 +3787,7 @@
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
|
|
@ -4542,6 +4456,7 @@
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
||||||
"integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
|
"integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
|
||||||
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=11.0.0"
|
"node": ">=11.0.0"
|
||||||
|
|
@ -4957,12 +4872,6 @@
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tiny-typed-emitter": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/tmp": {
|
"node_modules/tmp": {
|
||||||
"version": "0.2.5",
|
"version": "0.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,5 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^34.5.8",
|
"electron": "^34.5.8",
|
||||||
"electron-builder": "^25.1.8"
|
"electron-builder": "^25.1.8"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"electron-updater": "^6.8.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ const state = {
|
||||||
viewMode: 'chat', // 'chat' or 'video'
|
viewMode: 'chat', // 'chat' or 'video'
|
||||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||||
peerGainNodes: new Map(), // userId -> GainNode
|
peerGainNodes: new Map(), // userId -> GainNode
|
||||||
visibleVolumeSliders: new Set(), // userIds whose sliders are currently visible
|
|
||||||
},
|
},
|
||||||
voicePresencePollId: null,
|
voicePresencePollId: null,
|
||||||
chatWs: null,
|
chatWs: null,
|
||||||
|
|
@ -372,9 +371,8 @@ function renderChannels() {
|
||||||
let sliderHtml = '';
|
let sliderHtml = '';
|
||||||
if (p.user_id !== state.me.id) {
|
if (p.user_id !== state.me.id) {
|
||||||
const vol = state.userVolumes.get(p.user_id) ?? 1.0;
|
const vol = state.userVolumes.get(p.user_id) ?? 1.0;
|
||||||
const isVisible = state.voice.visibleVolumeSliders.has(p.user_id);
|
|
||||||
sliderHtml = `
|
sliderHtml = `
|
||||||
<div class="user-volume-control ${isVisible ? 'show-volume' : ''}" data-user-id="${p.user_id}">
|
<div class="user-volume-control" data-user-id="${p.user_id}">
|
||||||
<i data-lucide="volume-2" style="width: 12px; height: 12px; opacity: 0.6;"></i>
|
<i data-lucide="volume-2" style="width: 12px; height: 12px; opacity: 0.6;"></i>
|
||||||
<input type="range" min="0" max="2" step="0.1" value="${vol}" class="volume-slider">
|
<input type="range" min="0" max="2" step="0.1" value="${vol}" class="volume-slider">
|
||||||
<span class="vol-pct">${Math.round(vol * 100)}%</span>
|
<span class="vol-pct">${Math.round(vol * 100)}%</span>
|
||||||
|
|
@ -411,12 +409,10 @@ function renderChannels() {
|
||||||
pRow.addEventListener('contextmenu', (e) => {
|
pRow.addEventListener('contextmenu', (e) => {
|
||||||
if (p.user_id === state.me.id) return;
|
if (p.user_id === state.me.id) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (state.voice.visibleVolumeSliders.has(p.user_id)) {
|
const control = pRow.querySelector('.user-volume-control');
|
||||||
state.voice.visibleVolumeSliders.delete(p.user_id);
|
if (control) {
|
||||||
} else {
|
control.classList.toggle('show-volume');
|
||||||
state.voice.visibleVolumeSliders.add(p.user_id);
|
|
||||||
}
|
}
|
||||||
renderChannels();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pList.appendChild(pRow);
|
pList.appendChild(pRow);
|
||||||
|
|
@ -698,9 +694,8 @@ function initChatWs() {
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = () => {
|
ws.onclose = () => {
|
||||||
console.log("Chat WS closed, refreshing page for resilience...");
|
console.log("Chat WS closed, reconnecting...");
|
||||||
// Automatically refresh on connection loss instead of just background reconnecting
|
setTimeout(initChatWs, 3000);
|
||||||
location.reload();
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1188,7 +1183,6 @@ async function joinVoice() {
|
||||||
|
|
||||||
state.voice.ws = ws;
|
state.voice.ws = ws;
|
||||||
state.voice.joinedChannelId = state.selectedVoiceChannelId;
|
state.voice.joinedChannelId = state.selectedVoiceChannelId;
|
||||||
localStorage.setItem("active_voice_channel", state.selectedVoiceChannelId);
|
|
||||||
|
|
||||||
// Start mic setup in parallel so channel join is not blocked by device init.
|
// Start mic setup in parallel so channel join is not blocked by device init.
|
||||||
createLocalVoiceStream()
|
createLocalVoiceStream()
|
||||||
|
|
@ -1320,7 +1314,6 @@ async function joinVoice() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function leaveVoice() {
|
async function leaveVoice() {
|
||||||
localStorage.removeItem("active_voice_channel");
|
|
||||||
if (state.voice.ws) state.voice.ws.close();
|
if (state.voice.ws) state.voice.ws.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1903,18 +1896,6 @@ async function init() {
|
||||||
|
|
||||||
initChatWs();
|
initChatWs();
|
||||||
startVoicePresencePolling();
|
startVoicePresencePolling();
|
||||||
|
|
||||||
// Auto-reconnect to voice if previously joined
|
|
||||||
const savedVoiceChannel = localStorage.getItem("active_voice_channel");
|
|
||||||
if (savedVoiceChannel) {
|
|
||||||
console.log("Auto-reconnecting to voice channel:", savedVoiceChannel);
|
|
||||||
state.selectedVoiceChannelId = savedVoiceChannel;
|
|
||||||
// Wait for components to be ready
|
|
||||||
setTimeout(() => {
|
|
||||||
joinVoice().catch(err => console.error("Auto-reconnect failed", err));
|
|
||||||
}, 800);
|
|
||||||
}
|
|
||||||
|
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("init failed", err);
|
console.error("init failed", err);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue