diff --git a/.forgejo/workflows/pipeline.yaml b/.forgejo/workflows/pipeline.yaml index 9e241e8..30aa0d7 100644 --- a/.forgejo/workflows/pipeline.yaml +++ b/.forgejo/workflows/pipeline.yaml @@ -11,25 +11,6 @@ jobs: with: node-version: 24 - 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 run dist:linux - name: Build Windows installer with 32-bit Wine prefix @@ -55,9 +36,6 @@ jobs: copy_first '*.AppImage' 'chattz-linux.AppImage' copy_first '*.exe' 'chattz-windows.exe' 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 uses: actions/cache@v4 with: diff --git a/desktop/app.js b/desktop/app.js index d260cfa..a0d5a5a 100644 --- a/desktop/app.js +++ b/desktop/app.js @@ -28,7 +28,6 @@ const state = { viewMode: 'chat', // 'chat' or 'video' iceServers: [{ urls: "stun:stun.l.google.com:19302" }], peerGainNodes: new Map(), // userId -> GainNode - visibleVolumeSliders: new Set(), // userIds whose sliders are visible }, voicePresencePollId: null, onlineUsers: new Set(), @@ -112,9 +111,6 @@ const el = { userName: document.getElementById("user-name"), userAvatar: document.getElementById("user-avatar"), logoutBtn: document.getElementById("logout-btn"), - updateNotifier: document.getElementById("update-notifier"), - updateDownloadBtn: document.getElementById("update-download-btn"), - updateInstallBtn: document.getElementById("update-install-btn"), // Voice Connection voiceConnection: document.getElementById("voice-connection"), @@ -420,9 +416,8 @@ function renderChannels() { let sliderHtml = ''; if (p.user_id !== state.me.id) { const vol = state.userVolumes.get(p.user_id) ?? 1.0; - const isVisible = state.voice.visibleVolumeSliders.has(p.user_id); sliderHtml = ` -
+
${Math.round(vol * 100)}% @@ -459,12 +454,10 @@ function renderChannels() { pRow.addEventListener('contextmenu', (e) => { if (p.user_id === state.me.id) return; e.preventDefault(); - if (state.voice.visibleVolumeSliders.has(p.user_id)) { - state.voice.visibleVolumeSliders.delete(p.user_id); - } else { - state.voice.visibleVolumeSliders.add(p.user_id); + const control = pRow.querySelector('.user-volume-control'); + if (control) { + control.classList.toggle('show-volume'); } - renderChannels(); }); pList.appendChild(pRow); @@ -1943,324 +1936,6 @@ async function init() { await loadGuilds(); 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 = '
Searching...
'; - 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 = '
Error loading GIFs
'; - } - } - - function renderGifs(gifs) { - el.gifResults.innerHTML = ""; - if (!gifs || gifs.length === 0) { - el.gifResults.innerHTML = '
No GIFs found
'; - 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 { const online = await api("/presence"); @@ -2285,7 +1960,6 @@ async function init() { initChatWs(); startVoicePresencePolling(); - initUpdater(); lucide.createIcons(); } catch (err) { console.error("init failed", err); diff --git a/desktop/index.html b/desktop/index.html index ac9fa8d..28521c9 100644 --- a/desktop/index.html +++ b/desktop/index.html @@ -114,11 +114,6 @@
Online
-
@@ -268,4 +263,4 @@ - \ No newline at end of file + diff --git a/desktop/preload.js b/desktop/preload.js index ae9d90e..4c4f19c 100644 --- a/desktop/preload.js +++ b/desktop/preload.js @@ -2,11 +2,5 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { copyToClipboard: (text) => ipcRenderer.invoke('clipboard-write', text), - 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)) + getConfig: () => ipcRenderer.invoke('get-config') }); diff --git a/desktop/styles.css b/desktop/styles.css index fda1104..5d4e40a 100644 --- a/desktop/styles.css +++ b/desktop/styles.css @@ -621,35 +621,6 @@ select { 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 { flex: 1; min-width: 0; diff --git a/main.js b/main.js index 0f0878d..96e11b4 100644 --- a/main.js +++ b/main.js @@ -1,5 +1,4 @@ const { app, BrowserWindow, session, desktopCapturer, ipcMain, clipboard } = require('electron'); -const { autoUpdater } = require('electron-updater'); const path = require('path'); const url = require('url'); @@ -122,44 +121,6 @@ app.commandLine.appendSwitch('log-level', '3'); // Silences standard Chromium wa app.whenReady().then(() => { 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', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); diff --git a/package-lock.json b/package-lock.json index 7c39f92..7efcf1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,6 @@ "": { "name": "chattz-electron", "version": "0.1.0", - "dependencies": { - "electron-updater": "^6.8.3" - }, "devDependencies": { "electron": "^34.5.8", "electron-builder": "^25.1.8" @@ -1094,6 +1091,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/assert-plus": { @@ -1848,6 +1846,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2370,82 +2369,6 @@ "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": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2981,6 +2904,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/has-flag": { @@ -3355,6 +3279,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3422,6 +3347,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, "license": "MIT" }, "node_modules/lazystream": { @@ -3497,12 +3423,6 @@ "license": "MIT", "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": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", @@ -3511,13 +3431,6 @@ "license": "MIT", "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": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -3874,6 +3787,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -4542,6 +4456,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -4957,12 +4872,6 @@ "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": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", diff --git a/package.json b/package.json index ef4716e..bd50d6c 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,5 @@ "devDependencies": { "electron": "^34.5.8", "electron-builder": "^25.1.8" - }, - "dependencies": { - "electron-updater": "^6.8.3" } } diff --git a/static/app.js b/static/app.js index 89614a0..c07063f 100644 --- a/static/app.js +++ b/static/app.js @@ -28,7 +28,6 @@ const state = { viewMode: 'chat', // 'chat' or 'video' iceServers: [{ urls: "stun:stun.l.google.com:19302" }], peerGainNodes: new Map(), // userId -> GainNode - visibleVolumeSliders: new Set(), // userIds whose sliders are currently visible }, voicePresencePollId: null, chatWs: null, @@ -372,9 +371,8 @@ function renderChannels() { let sliderHtml = ''; if (p.user_id !== state.me.id) { const vol = state.userVolumes.get(p.user_id) ?? 1.0; - const isVisible = state.voice.visibleVolumeSliders.has(p.user_id); sliderHtml = ` -
+
${Math.round(vol * 100)}% @@ -411,12 +409,10 @@ function renderChannels() { pRow.addEventListener('contextmenu', (e) => { if (p.user_id === state.me.id) return; e.preventDefault(); - if (state.voice.visibleVolumeSliders.has(p.user_id)) { - state.voice.visibleVolumeSliders.delete(p.user_id); - } else { - state.voice.visibleVolumeSliders.add(p.user_id); + const control = pRow.querySelector('.user-volume-control'); + if (control) { + control.classList.toggle('show-volume'); } - renderChannels(); }); pList.appendChild(pRow); @@ -698,9 +694,8 @@ function initChatWs() { }; ws.onclose = () => { - console.log("Chat WS closed, refreshing page for resilience..."); - // Automatically refresh on connection loss instead of just background reconnecting - location.reload(); + console.log("Chat WS closed, reconnecting..."); + setTimeout(initChatWs, 3000); }; } @@ -1188,7 +1183,6 @@ async function joinVoice() { state.voice.ws = ws; 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. createLocalVoiceStream() @@ -1320,7 +1314,6 @@ async function joinVoice() { } async function leaveVoice() { - localStorage.removeItem("active_voice_channel"); if (state.voice.ws) state.voice.ws.close(); } @@ -1903,18 +1896,6 @@ async function init() { initChatWs(); 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(); } catch (err) { console.error("init failed", err);