Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a28b0db85 | ||
|
|
ca4b851ba4 | ||
|
|
0742dac057 | ||
|
|
9d2e35c322 | ||
|
|
526bd994d8 | ||
|
|
37c1f22aee | ||
|
|
10a97b1f4a |
27
.github/workflows/ci.yml
vendored
27
.github/workflows/ci.yml
vendored
@@ -6,7 +6,19 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: windows-latest
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
build-script: pnpm run build:win
|
||||||
|
ytdlp-asset: yt-dlp.exe
|
||||||
|
ytdlp-output: yt-dlp.exe
|
||||||
|
- os: ubuntu-latest
|
||||||
|
build-script: pnpm run build:linux
|
||||||
|
ytdlp-asset: yt-dlp
|
||||||
|
ytdlp-output: yt-dlp_linux
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
@@ -25,13 +37,16 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download yt-dlp binaries
|
- name: Download yt-dlp binary
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
# Download yt-dlp.exe for Windows
|
curl -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/${{ matrix.ytdlp-asset }}" -o "resources/${{ matrix.ytdlp-output }}"
|
||||||
curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe -o resources/yt-dlp.exe
|
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||||
|
chmod +x "resources/${{ matrix.ytdlp-output }}"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Lint and format check
|
- name: Lint and format check
|
||||||
run: pnpm run check
|
run: pnpm run check
|
||||||
|
|
||||||
- name: build-win
|
- name: Build application
|
||||||
run: pnpm run build:win
|
run: ${{ matrix.build-script }}
|
||||||
|
|||||||
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@@ -11,7 +11,19 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, macos-latest]
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
build_script: pnpm run build:win
|
||||||
|
ytdlp_asset: yt-dlp.exe
|
||||||
|
ytdlp_output: yt-dlp.exe
|
||||||
|
- os: macos-latest
|
||||||
|
build_script: pnpm run build:mac
|
||||||
|
ytdlp_asset: yt-dlp_macos
|
||||||
|
ytdlp_output: yt-dlp_macos
|
||||||
|
- os: ubuntu-latest
|
||||||
|
build_script: pnpm run build:linux
|
||||||
|
ytdlp_asset: yt-dlp
|
||||||
|
ytdlp_output: yt-dlp_linux
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
@@ -30,33 +42,19 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download yt-dlp binaries
|
- name: Download yt-dlp binary
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
# Download yt-dlp.exe for Windows
|
curl -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/${{ matrix.ytdlp_asset }}" -o "resources/${{ matrix.ytdlp_output }}"
|
||||||
curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe -o resources/yt-dlp.exe
|
if [[ "${{ runner.os }}" == "Linux" ]]; then
|
||||||
|
chmod +x "resources/${{ matrix.ytdlp_output }}"
|
||||||
# Download yt-dlp for macOS (for cross-platform builds)
|
fi
|
||||||
curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos -o resources/yt-dlp_macos
|
|
||||||
chmod +x resources/yt-dlp_macos
|
|
||||||
|
|
||||||
# Download yt-dlp for Linux (for cross-platform builds)
|
|
||||||
# curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o resources/yt-dlp_linux
|
|
||||||
# chmod +x resources/yt-dlp_linux
|
|
||||||
|
|
||||||
- name: Lint and format check
|
- name: Lint and format check
|
||||||
run: pnpm run check
|
run: pnpm run check
|
||||||
|
|
||||||
# - name: build-linux
|
- name: Build application
|
||||||
# if: matrix.os == 'ubuntu-latest'
|
run: ${{ matrix.build_script }}
|
||||||
# run: pnpm run build:linux
|
|
||||||
|
|
||||||
- name: build-mac
|
|
||||||
if: matrix.os == 'macos-latest'
|
|
||||||
run: pnpm run build:mac
|
|
||||||
|
|
||||||
- name: build-win
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
run: pnpm run build:win
|
|
||||||
|
|
||||||
- name: release
|
- name: release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
|
|||||||
@@ -21,14 +21,17 @@ nsis:
|
|||||||
mac:
|
mac:
|
||||||
entitlementsInherit: build/entitlements.mac.plist
|
entitlementsInherit: build/entitlements.mac.plist
|
||||||
notarize: false
|
notarize: false
|
||||||
dmg:
|
artifactName: ${name}-${version}-${arch}.${ext}
|
||||||
artifactName: ${name}-${version}.${ext}
|
target:
|
||||||
|
- target: dmg
|
||||||
|
arch:
|
||||||
|
- arm64
|
||||||
|
- x64
|
||||||
linux:
|
linux:
|
||||||
target:
|
target:
|
||||||
- AppImage
|
- AppImage
|
||||||
- snap
|
|
||||||
- deb
|
- deb
|
||||||
maintainer: yourname@example.com
|
maintainer: nexmoex@gmail.com
|
||||||
category: Utility
|
category: Utility
|
||||||
appImage:
|
appImage:
|
||||||
artifactName: ${name}-${version}.${ext}
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vidbee",
|
"name": "vidbee",
|
||||||
"version": "0.3.0",
|
"version": "0.3.2",
|
||||||
"description": "A modern Electron application for downloading videos and audios",
|
"description": "A modern Electron application for downloading videos and audios",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
"author": "VidBee",
|
"author": "VidBee",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"postinstall": "electron-builder install-app-deps",
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"build:unpack": "pnpm run build && electron-builder --dir",
|
"build:unpack": "pnpm run build && electron-builder --dir",
|
||||||
"build:win": "node scripts/check-ytdlp.js win && pnpm run build && electron-builder --win",
|
"build:win": "node scripts/check-ytdlp.js win && pnpm run build && electron-builder --win",
|
||||||
"build:mac": "node scripts/check-ytdlp.js mac && pnpm run build && electron-builder --mac",
|
"build:mac": "node scripts/check-ytdlp.js mac && pnpm run build && electron-builder --mac --x64 --arm64",
|
||||||
"build:linux": "node scripts/check-ytdlp.js linux && pnpm run build && electron-builder --linux",
|
"build:linux": "node scripts/check-ytdlp.js linux && pnpm run build && electron-builder --linux",
|
||||||
"release": "pnpm run check && bumpp"
|
"release": "pnpm run check && bumpp"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||||
import { app, BrowserWindow, shell } from 'electron'
|
import { app, BrowserWindow, type BrowserWindowConstructorOptions, shell } from 'electron'
|
||||||
import log from 'electron-log/main'
|
import log from 'electron-log/main'
|
||||||
import { autoUpdater } from 'electron-updater'
|
import { autoUpdater } from 'electron-updater'
|
||||||
import appIcon from '../../build/icon.png?asset'
|
import appIcon from '../../build/icon.png?asset'
|
||||||
@@ -22,18 +22,16 @@ let mainWindow: BrowserWindow | null = null
|
|||||||
let isQuitting = false
|
let isQuitting = false
|
||||||
|
|
||||||
export function createWindow(): void {
|
export function createWindow(): void {
|
||||||
// Create the browser window
|
const isMac = process.platform === 'darwin'
|
||||||
mainWindow = new BrowserWindow({
|
const isWindows = process.platform === 'win32'
|
||||||
|
|
||||||
|
const windowOptions: BrowserWindowConstructorOptions = {
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 800,
|
height: 800,
|
||||||
show: false,
|
show: false,
|
||||||
titleBarStyle: 'hidden', // Hide title bar on macOS
|
|
||||||
trafficLightPosition: { x: 12.5, y: 10 },
|
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
icon: appIcon, // Set application icon
|
icon: appIcon, // Set application icon
|
||||||
frame: false,
|
frame: false,
|
||||||
vibrancy: 'fullscreen-ui', // on MacOS
|
|
||||||
backgroundMaterial: 'acrylic', // on Windows 11
|
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, '../preload/index.js'),
|
preload: join(__dirname, '../preload/index.js'),
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
@@ -41,7 +39,20 @@ export function createWindow(): void {
|
|||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
webSecurity: false // Allow drag regions to work
|
webSecurity: false // Allow drag regions to work
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if (isMac) {
|
||||||
|
windowOptions.titleBarStyle = 'hidden'
|
||||||
|
windowOptions.trafficLightPosition = { x: 12.5, y: 10 }
|
||||||
|
windowOptions.vibrancy = 'fullscreen-ui'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWindows) {
|
||||||
|
windowOptions.backgroundMaterial = 'acrylic'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the browser window
|
||||||
|
mainWindow = new BrowserWindow(windowOptions)
|
||||||
|
|
||||||
mainWindow.on('close', (event) => {
|
mainWindow.on('close', (event) => {
|
||||||
const closeToTray = settingsManager.get('closeToTray')
|
const closeToTray = settingsManager.get('closeToTray')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { execFile } from 'node:child_process'
|
import { execFile, execSync } from 'node:child_process'
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import os from 'node:os'
|
import os from 'node:os'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
@@ -40,7 +40,20 @@ class FileSystemService extends IpcService {
|
|||||||
|
|
||||||
@IpcMethod()
|
@IpcMethod()
|
||||||
getDefaultDownloadPath(_context: IpcContext): string {
|
getDefaultDownloadPath(_context: IpcContext): string {
|
||||||
return `${os.homedir()}/Downloads`
|
const fallbackPath = path.join(os.homedir(), 'Downloads')
|
||||||
|
|
||||||
|
if (process.platform === 'linux' || process.platform === 'freebsd') {
|
||||||
|
try {
|
||||||
|
const xdgPath = execSync('xdg-user-dir DOWNLOAD', { encoding: 'utf8' }).trim()
|
||||||
|
if (xdgPath) {
|
||||||
|
return xdgPath
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Unable to resolve XDG download directory, falling back to default:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackPath
|
||||||
}
|
}
|
||||||
|
|
||||||
@IpcMethod()
|
@IpcMethod()
|
||||||
|
|||||||
@@ -2,19 +2,27 @@ import { ScrollArea } from '@renderer/components/ui/scroll-area'
|
|||||||
import { Sidebar } from '@renderer/components/ui/sidebar'
|
import { Sidebar } from '@renderer/components/ui/sidebar'
|
||||||
import { Toaster } from '@renderer/components/ui/sonner'
|
import { Toaster } from '@renderer/components/ui/sonner'
|
||||||
import { TitleBar } from '@renderer/components/ui/title-bar'
|
import { TitleBar } from '@renderer/components/ui/title-bar'
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
import { ThemeProvider } from 'next-themes'
|
import { ThemeProvider } from 'next-themes'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { ipcServices } from './lib/ipc'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
import { ipcEvents, ipcServices } from './lib/ipc'
|
||||||
import { About } from './pages/About'
|
import { About } from './pages/About'
|
||||||
import { Home } from './pages/Home'
|
import { Home } from './pages/Home'
|
||||||
import { Settings } from './pages/Settings'
|
import { Settings } from './pages/Settings'
|
||||||
import { SupportedSites } from './pages/SupportedSites'
|
import { SupportedSites } from './pages/SupportedSites'
|
||||||
|
import { settingsAtom } from './store/settings'
|
||||||
|
|
||||||
type Page = 'home' | 'settings' | 'about' | 'sites'
|
type Page = 'home' | 'settings' | 'about' | 'sites'
|
||||||
|
|
||||||
function AppContent() {
|
function AppContent() {
|
||||||
const [currentPage, setCurrentPage] = useState<Page>('home')
|
const [currentPage, setCurrentPage] = useState<Page>('home')
|
||||||
const [platform, setPlatform] = useState<string>('')
|
const [platform, setPlatform] = useState<string>('')
|
||||||
|
const [settings] = useAtom(settingsAtom)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const autoUpdateEnabled = settings.autoUpdate
|
||||||
|
const updateDownloadInProgressRef = useRef(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Get platform info to determine if we should show title bar
|
// Get platform info to determine if we should show title bar
|
||||||
@@ -31,6 +39,130 @@ function AppContent() {
|
|||||||
getPlatform()
|
getPlatform()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!window?.api) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const showRestartPrompt = () => {
|
||||||
|
toast.info(t('about.notifications.restartToUpdate'), {
|
||||||
|
action: {
|
||||||
|
label: t('about.notifications.restartNowAction'),
|
||||||
|
onClick: () => {
|
||||||
|
void ipcServices.update.quitAndInstall()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetDownloadState = () => {
|
||||||
|
if (updateDownloadInProgressRef.current) {
|
||||||
|
updateDownloadInProgressRef.current = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startUpdateDownload = async () => {
|
||||||
|
if (updateDownloadInProgressRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDownloadInProgressRef.current = true
|
||||||
|
toast.info(t('about.notifications.downloadStarted'))
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await ipcServices.update.downloadUpdate()
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error ?? 'Unknown error')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
updateDownloadInProgressRef.current = false
|
||||||
|
const message =
|
||||||
|
error instanceof Error && error.message
|
||||||
|
? error.message
|
||||||
|
: t('about.notifications.unknownErrorFallback')
|
||||||
|
toast.error(t('about.notifications.updateError', { error: message }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdateAvailable = (rawInfo: unknown) => {
|
||||||
|
const info = (rawInfo ?? {}) as { version?: string }
|
||||||
|
const versionLabel = info.version ?? ''
|
||||||
|
toast.success(t('about.notifications.updateAvailable', { version: versionLabel }))
|
||||||
|
|
||||||
|
if (autoUpdateEnabled) {
|
||||||
|
void startUpdateDownload()
|
||||||
|
} else {
|
||||||
|
toast(t('about.notifications.downloadUpdate', { version: versionLabel }), {
|
||||||
|
action: {
|
||||||
|
label: t('about.notifications.manualDownloadAction'),
|
||||||
|
onClick: () => {
|
||||||
|
void startUpdateDownload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdateDownloaded = (rawInfo: unknown) => {
|
||||||
|
const info = (rawInfo ?? {}) as { version?: string }
|
||||||
|
resetDownloadState()
|
||||||
|
|
||||||
|
const versionLabel = info?.version ?? ''
|
||||||
|
const downloadedMessage = versionLabel
|
||||||
|
? t('about.notifications.updateDownloadedVersion', { version: versionLabel })
|
||||||
|
: t('about.notifications.updateDownloaded')
|
||||||
|
toast.success(downloadedMessage)
|
||||||
|
|
||||||
|
showRestartPrompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdateError = (rawMessage: unknown) => {
|
||||||
|
const message = typeof rawMessage === 'string' ? rawMessage : ''
|
||||||
|
resetDownloadState()
|
||||||
|
|
||||||
|
const errorMessage = message || t('about.notifications.unknownErrorFallback')
|
||||||
|
toast.error(t('about.notifications.updateError', { error: errorMessage }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownloadProgress = (rawProgress: unknown) => {
|
||||||
|
const progress = (rawProgress ?? {}) as { percent?: number }
|
||||||
|
if (typeof progress?.percent === 'number') {
|
||||||
|
console.info('Update download progress:', progress.percent.toFixed(2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdateNotification = (rawPayload: unknown) => {
|
||||||
|
const payload = (rawPayload ?? {}) as { body?: string; version?: string }
|
||||||
|
const versionLabel = payload.version ?? ''
|
||||||
|
const downloadedMessage = versionLabel
|
||||||
|
? t('about.notifications.updateDownloadedVersion', { version: versionLabel })
|
||||||
|
: t('about.notifications.updateDownloaded')
|
||||||
|
|
||||||
|
toast.info(payload?.body ?? downloadedMessage, {
|
||||||
|
action: {
|
||||||
|
label: t('about.notifications.restartNowAction'),
|
||||||
|
onClick: () => {
|
||||||
|
void ipcServices.update.quitAndInstall()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcEvents.on('update:available', handleUpdateAvailable)
|
||||||
|
ipcEvents.on('update:downloaded', handleUpdateDownloaded)
|
||||||
|
ipcEvents.on('update:error', handleUpdateError)
|
||||||
|
ipcEvents.on('update:download-progress', handleDownloadProgress)
|
||||||
|
ipcEvents.on('update:show-notification', handleUpdateNotification)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
ipcEvents.removeListener('update:available', handleUpdateAvailable)
|
||||||
|
ipcEvents.removeListener('update:downloaded', handleUpdateDownloaded)
|
||||||
|
ipcEvents.removeListener('update:error', handleUpdateError)
|
||||||
|
ipcEvents.removeListener('update:download-progress', handleDownloadProgress)
|
||||||
|
ipcEvents.removeListener('update:show-notification', handleUpdateNotification)
|
||||||
|
}
|
||||||
|
}, [autoUpdateEnabled, t])
|
||||||
|
|
||||||
const renderPage = () => {
|
const renderPage = () => {
|
||||||
switch (currentPage) {
|
switch (currentPage) {
|
||||||
case 'home':
|
case 'home':
|
||||||
|
|||||||
@@ -27,11 +27,15 @@
|
|||||||
"downloadError": "Failed to download update",
|
"downloadError": "Failed to download update",
|
||||||
"downloadStarted": "Download started...",
|
"downloadStarted": "Download started...",
|
||||||
"downloadUpdate": "Download and install update {{version}}?",
|
"downloadUpdate": "Download and install update {{version}}?",
|
||||||
|
"manualDownloadAction": "Download now",
|
||||||
"noUpdatesAvailable": "You're using the latest version",
|
"noUpdatesAvailable": "You're using the latest version",
|
||||||
"restartToUpdate": "Restart now to install update?",
|
"restartToUpdate": "Restart now to install update?",
|
||||||
|
"restartNowAction": "Restart now",
|
||||||
"updateAvailable": "Update available: {{version}}",
|
"updateAvailable": "Update available: {{version}}",
|
||||||
"updateDownloaded": "Update downloaded, restart to install",
|
"updateDownloaded": "Update downloaded, restart to install",
|
||||||
"updateError": "Failed to check for updates: {{error}}"
|
"updateDownloadedVersion": "Update {{version}} downloaded, restart to install",
|
||||||
|
"updateError": "Failed to check for updates: {{error}}",
|
||||||
|
"unknownErrorFallback": "Unknown error"
|
||||||
},
|
},
|
||||||
"preferencesDescription": "Tune update settings without leaving this page.",
|
"preferencesDescription": "Tune update settings without leaving this page.",
|
||||||
"preferencesTitle": "Quick Toggles",
|
"preferencesTitle": "Quick Toggles",
|
||||||
|
|||||||
Reference in New Issue
Block a user