feat: Bootstrap the Electron application with React, Convex, and LiveKit, including a FriendsView component and a Gitea CI/CD release workflow.
Some checks failed
Build and Release / build-and-release (push) Failing after 27m21s

This commit is contained in:
Bryan1029384756
2026-02-10 19:17:51 -06:00
parent 17790afa9b
commit e874b89fe8
8 changed files with 424 additions and 47 deletions

View File

@@ -13,7 +13,8 @@
"Bash(dir:*)", "Bash(dir:*)",
"Bash(npx vite build:*)", "Bash(npx vite build:*)",
"Bash(npx tsc:*)", "Bash(npx tsc:*)",
"Bash(npx -y esbuild:*)" "Bash(npx -y esbuild:*)",
"WebFetch(domain:gist.github.com)"
] ]
} }
} }

View File

@@ -0,0 +1,87 @@
name: Build and Release
on:
push:
branches: [main]
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Wine (for Windows cross-compile)
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y wine64 wine32
- name: Install dependencies
run: |
npm install
npm run install:frontend
- name: Read version from package.json
id: version
run: |
VERSION=$(node -p "require('./FrontEnd/Electron/package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build Electron app
run: |
cd FrontEnd/Electron
npm run build
npx electron-builder --linux --win
env:
GH_TOKEN: ${{ secrets.CI_TOKEN }}
- name: Delete existing latest release
run: |
RELEASE_ID=$(curl -s -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$REPO/releases/tags/latest" | jq -r '.id')
if [ "$RELEASE_ID" != "null" ] && [ -n "$RELEASE_ID" ]; then
curl -X DELETE -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$REPO/releases/$RELEASE_ID"
# Also delete the tag so it can be recreated
curl -X DELETE -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/repos/$REPO/tags/latest"
fi
env:
GITEA_URL: ${{ secrets.CI_URL }}
REPO: ${{ gitea.repository }}
TOKEN: ${{ secrets.CI_TOKEN }}
- name: Create release and upload artifacts
run: |
VERSION="${{ steps.version.outputs.version }}"
# Create new release with latest tag
RELEASE_ID=$(curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"latest\",\"name\":\"v${VERSION}\",\"body\":\"Auto-release v${VERSION} from CI\"}" \
"$GITEA_URL/api/v1/repos/$REPO/releases" | jq -r '.id')
echo "Created release ID: $RELEASE_ID"
# Upload each artifact
for file in FrontEnd/Electron/dist/latest*.yml \
FrontEnd/Electron/dist/*.exe \
FrontEnd/Electron/dist/*.exe.blockmap \
FrontEnd/Electron/dist/*.AppImage; do
[ -f "$file" ] || continue
FILENAME=$(basename "$file")
echo "Uploading: $FILENAME"
curl -X POST -H "Authorization: token $TOKEN" \
-F "attachment=@$file" \
"$GITEA_URL/api/v1/repos/$REPO/releases/$RELEASE_ID/assets?name=$FILENAME"
done
env:
GITEA_URL: ${{ secrets.CI_URL }}
REPO: ${{ gitea.repository }}
TOKEN: ${{ secrets.CI_TOKEN }}

View File

@@ -2,6 +2,7 @@ const { app, BrowserWindow, ipcMain, shell } = require('electron');
const path = require('path'); const path = require('path');
const https = require('https'); const https = require('https');
const http = require('http'); const http = require('http');
const { checkForUpdates } = require('./updater.cjs');
function createWindow() { function createWindow() {
const win = new BrowserWindow({ const win = new BrowserWindow({
@@ -28,8 +29,36 @@ function createWindow() {
} }
} }
app.whenReady().then(() => { function createSplashWindow() {
const splash = new BrowserWindow({
width: 300,
height: 350,
frame: false,
resizable: false,
alwaysOnTop: true,
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
});
splash.loadFile(path.join(__dirname, 'splash.html'));
return splash;
}
app.whenReady().then(async () => {
const isDev = !app.isPackaged;
if (isDev) {
createWindow(); createWindow();
} else {
const splash = createSplashWindow();
const noUpdate = await checkForUpdates(splash);
if (noUpdate === false) {
if (!splash.isDestroyed()) splash.close();
createWindow();
}
// If update downloaded, quitAndInstall handles restart
}
ipcMain.on('window-minimize', () => { ipcMain.on('window-minimize', () => {
const win = BrowserWindow.getFocusedWindow(); const win = BrowserWindow.getFocusedWindow();

View File

@@ -11,6 +11,8 @@
"@livekit/components-react": "^2.9.17", "@livekit/components-react": "^2.9.17",
"@livekit/components-styles": "^1.2.0", "@livekit/components-styles": "^1.2.0",
"convex": "^1.31.2", "convex": "^1.31.2",
"electron-log": "^5.4.3",
"electron-updater": "^6.7.3",
"livekit-client": "^2.16.1", "livekit-client": "^2.16.1",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
@@ -2772,7 +2774,6 @@
"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": {
@@ -4726,6 +4727,15 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/electron-log": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz",
"integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/electron-publish": { "node_modules/electron-publish": {
"version": "25.1.7", "version": "25.1.7",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.7.tgz", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.7.tgz",
@@ -4787,6 +4797,82 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/electron-updater": {
"version": "6.7.3",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.7.3.tgz",
"integrity": "sha512-EgkT8Z9noqXKbwc3u5FkJA+r48jwZ5DTUiOkJMOTEEH//n5Am6wfQGz7nvSFEA2oIAMv9jRzn5JKTyWeSKOPgg==",
"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/electron/node_modules/@types/node": { "node_modules/electron/node_modules/@types/node": {
"version": "20.19.27", "version": "20.19.27",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
@@ -5740,7 +5826,6 @@
"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": {
@@ -6394,7 +6479,6 @@
"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"
@@ -6482,7 +6566,6 @@
"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": {
@@ -6628,6 +6711,12 @@
"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",
@@ -6636,6 +6725,13 @@
"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",
@@ -9026,7 +9122,6 @@
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
"dev": true,
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
}, },
"node_modules/scheduler": { "node_modules/scheduler": {
@@ -9550,6 +9645,12 @@
"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/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",

View File

@@ -1,7 +1,7 @@
{ {
"name": "discord", "name": "discord",
"private": true, "private": true,
"version": "0.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"main": "main.cjs", "main": "main.cjs",
"homepage": "./", "homepage": "./",
@@ -14,26 +14,52 @@
"electron:build": "vite build && electron-builder" "electron:build": "vite build && electron-builder"
}, },
"build": { "build": {
"appId": "com.yourorg.discord-clone",
"productName": "Discord Clone",
"files": [ "files": [
"dist-react/**/*", "dist-react/**/*",
"main.cjs", "main.cjs",
"preload.cjs", "preload.cjs",
"updater.cjs",
"splash.html",
"package.json" "package.json"
], ],
"directories": { "directories": {
"output": "dist" "output": "dist"
},
"publish": [
{
"provider": "generic",
"url": "https://gitea.moyettes.com/Moyettes/DiscordClone/releases/download/latest"
}
],
"win": {
"target": ["nsis"]
},
"mac": {
"target": ["dmg", "zip"],
"category": "public.app-category.social-networking"
},
"linux": {
"target": ["AppImage"]
},
"nsis": {
"oneClick": true,
"perMachine": false
} }
}, },
"dependencies": { "dependencies": {
"@livekit/components-react": "^2.9.17", "@livekit/components-react": "^2.9.17",
"@livekit/components-styles": "^1.2.0", "@livekit/components-styles": "^1.2.0",
"convex": "^1.31.2",
"electron-log": "^5.4.3",
"electron-updater": "^6.7.3",
"livekit-client": "^2.16.1", "livekit-client": "^2.16.1",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^7.11.0", "react-router-dom": "^7.11.0",
"react-syntax-highlighter": "^16.1.0", "react-syntax-highlighter": "^16.1.0",
"convex": "^1.31.2",
"remark-gfm": "^4.0.1" "remark-gfm": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Discord Clone</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #313338;
color: #dbdee1;
font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
-webkit-app-region: drag;
user-select: none;
overflow: hidden;
}
.logo {
width: 80px;
height: 80px;
background: #5865f2;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
}
.logo svg {
width: 48px;
height: 48px;
fill: white;
}
.app-name {
font-size: 18px;
font-weight: 700;
color: #f2f3f5;
margin-bottom: 32px;
letter-spacing: 0.3px;
}
#status {
font-size: 13px;
color: #b5bac1;
margin-bottom: 16px;
min-height: 20px;
text-align: center;
}
.progress-container {
width: 220px;
height: 6px;
background: #1e1f22;
border-radius: 3px;
overflow: hidden;
opacity: 1;
transition: opacity 0.3s ease;
}
.progress-container.hidden {
opacity: 0;
}
#progress-bar {
width: 0%;
height: 100%;
background: #5865f2;
border-radius: 3px;
transition: width 0.2s ease;
}
</style>
</head>
<body>
<div class="logo">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.09.09 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.09 16.09 0 0 0-4.8 0c-.14-.34-.36-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02zM8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12zm6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12z"/>
</svg>
</div>
<div class="app-name">Discord Clone</div>
<div id="status">Starting up...</div>
<div class="progress-container" id="progress-container">
<div id="progress-bar"></div>
</div>
<script>
function setStatus(text) {
document.getElementById('status').textContent = text;
}
function setProgress(percent) {
const container = document.getElementById('progress-container');
const bar = document.getElementById('progress-bar');
container.classList.remove('hidden');
bar.style.width = Math.min(100, Math.max(0, percent)) + '%';
}
function hideProgress() {
document.getElementById('progress-container').classList.add('hidden');
}
</script>
</body>
</html>

View File

@@ -74,45 +74,8 @@ const FriendsView = ({ onOpenDM }) => {
{tab} {tab}
</div> </div>
))} ))}
<div
onClick={() => setActiveTab('Add Friend')}
className="friends-tab"
style={{
cursor: 'pointer',
color: activeTab === 'Add Friend' ? '#fff' : '#3ba55c',
backgroundColor: activeTab === 'Add Friend' ? '#3ba55c' : 'transparent',
padding: '2px 8px',
borderRadius: '4px',
fontWeight: 500,
fontSize: '14px'
}}
>
Add Friend
</div> </div>
</div> </div>
</div>
{activeTab === 'Add Friend' && (
<div style={{ padding: '16px 20px' }}>
<input
type="text"
placeholder="Search for a user to start a conversation..."
value={addFriendSearch}
onChange={(e) => setAddFriendSearch(e.target.value)}
style={{
width: '100%',
backgroundColor: 'var(--bg-tertiary)',
border: '1px solid var(--border-subtle)',
borderRadius: '8px',
color: 'var(--text-normal)',
padding: '10px 14px',
fontSize: '14px',
outline: 'none',
boxSizing: 'border-box'
}}
/>
</div>
)}
{/* List Header */} {/* List Header */}
<div style={{ padding: '16px 20px 8px' }}> <div style={{ padding: '16px 20px 8px' }}>

View File

@@ -0,0 +1,61 @@
const { autoUpdater } = require('electron-updater');
const log = require('electron-log');
autoUpdater.logger = log;
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
function checkForUpdates(splashWindow) {
return new Promise((resolve) => {
function sendToSplash(js) {
if (splashWindow && !splashWindow.isDestroyed()) {
splashWindow.webContents.executeJavaScript(js).catch(() => {});
}
}
autoUpdater.on('checking-for-update', () => {
sendToSplash('setStatus("Checking for updates...")');
});
autoUpdater.on('update-available', () => {
sendToSplash('setStatus("Downloading update...")');
autoUpdater.downloadUpdate();
});
autoUpdater.on('download-progress', (progress) => {
const percent = Math.round(progress.percent);
sendToSplash(`setProgress(${percent})`);
sendToSplash(`setStatus("Downloading update... ${percent}%")`);
});
autoUpdater.on('update-downloaded', () => {
sendToSplash('setStatus("Installing update...")');
sendToSplash('setProgress(100)');
setTimeout(() => {
autoUpdater.quitAndInstall();
}, 1500);
});
autoUpdater.on('update-not-available', () => {
sendToSplash('setStatus("Up to date!")');
sendToSplash('hideProgress()');
setTimeout(() => resolve(false), 1000);
});
autoUpdater.on('error', (err) => {
log.error('Auto-updater error:', err);
sendToSplash('setStatus("Update check failed")');
sendToSplash('hideProgress()');
setTimeout(() => resolve(false), 2000);
});
autoUpdater.checkForUpdates().catch((err) => {
log.error('checkForUpdates failed:', err);
sendToSplash('setStatus("Update check failed")');
sendToSplash('hideProgress()');
setTimeout(() => resolve(false), 2000);
});
});
}
module.exports = { checkForUpdates };