From 14f80309af6981329c8cd58558e2d45d29c6b9f2 Mon Sep 17 00:00:00 2001 From: Nexmoe <16796652+nexmoe@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:02:20 +0800 Subject: [PATCH] feat(extension): refine error UI and branding (#89) --- .github/workflows/extension-publish.yml | 84 ++++++++++++++++ extension/.gitignore | 3 + extension/README.md | 55 ++++++++++- extension/entrypoints/popup/App.css | 70 ++++++++----- extension/entrypoints/popup/App.tsx | 108 ++++++++++++++++----- extension/package.json | 4 +- extension/public/_locales/en/messages.json | 6 ++ extension/wxt.config.ts | 2 + 8 files changed, 281 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/extension-publish.yml diff --git a/.github/workflows/extension-publish.yml b/.github/workflows/extension-publish.yml new file mode 100644 index 0000000..df1caa4 --- /dev/null +++ b/.github/workflows/extension-publish.yml @@ -0,0 +1,84 @@ +name: Publish Extension + +on: + push: + branches: [ main ] + paths: + - extension/package.json + +jobs: + detect-version: + runs-on: ubuntu-latest + outputs: + changed: ${{ steps.version_check.outputs.changed }} + current_version: ${{ steps.version_check.outputs.current_version }} + previous_version: ${{ steps.version_check.outputs.previous_version }} + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check if extension version changed + id: version_check + shell: bash + run: | + set -euo pipefail + before_sha="${{ github.event.before }}" + current_version=$(node -e "const fs = require('fs'); const v = JSON.parse(fs.readFileSync('extension/package.json', 'utf8')).version; process.stdout.write(v);") + previous_version="" + if git cat-file -e "${before_sha}:extension/package.json" 2>/dev/null; then + previous_version=$(git show "${before_sha}:extension/package.json" | node -e "let data=''; process.stdin.on('data', d => data += d); process.stdin.on('end', () => { const v = JSON.parse(data).version; process.stdout.write(v); });") + fi + + echo "current_version=${current_version}" >> "$GITHUB_OUTPUT" + echo "previous_version=${previous_version}" >> "$GITHUB_OUTPUT" + + if [[ -n "${previous_version}" && "${current_version}" == "${previous_version}" ]]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "changed=true" >> "$GITHUB_OUTPUT" + + submit: + needs: detect-version + if: needs.detect-version.outputs.changed == 'true' + runs-on: ubuntu-latest + defaults: + run: + working-directory: extension + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 8 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: extension/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install + + - name: Zip extensions + run: | + pnpm zip + + - name: Submit to stores + run: | + pnpm wxt submit \ + --chrome-zip .output/*-chrome.zip + env: + CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + CHROME_PUBLISH_TARGET: "default" + CHROME_SKIP_SUBMIT_REVIEW: false diff --git a/extension/.gitignore b/extension/.gitignore index a256953..f548011 100644 --- a/extension/.gitignore +++ b/extension/.gitignore @@ -24,3 +24,6 @@ web-ext.config.ts *.njsproj *.sln *.sw? + +.env +.env.* diff --git a/extension/README.md b/extension/README.md index 78cf194..8dfb87e 100644 --- a/extension/README.md +++ b/extension/README.md @@ -1,3 +1,54 @@ -# WXT + React +# VidBee Video Downloader Extension -This template should help get you started developing with React in WXT. +VidBee Video Downloader is a lightweight browser companion for the VidBee desktop app. It detects the video on your current tab, shows the available formats, and hands the URL to VidBee so the download happens in the desktop app instead of your browser. + +## Why install it? + +- **One-click handoff:** Send the current video page to VidBee without copy-pasting links. +- **See formats before downloading:** Preview resolutions, file sizes, and audio-only options in the popup. +- **More reliable downloads:** VidBee handles large files and multi-format downloads better than most browsers. +- **Works on 1,000+ sites:** The extension uses the same site support as the VidBee app. + +## What it does + +1. Reads the active tab URL when you open the popup. +2. Asks the VidBee desktop app (running locally) to analyze the video. +3. Displays the formats it finds and lets you open VidBee to download. + +## Requirements + +- VidBee desktop app installed. +- VidBee running while you use the extension. + +## How to use + +1. Open a supported video page. +2. Click the VidBee extension icon. +3. Review available formats. +4. Click **Download with VidBee** to start the download in the desktop app. + +## Development + +```bash +pnpm install +pnpm dev +``` + +Build or package: + +```bash +pnpm build +pnpm zip +``` + +Firefox builds: + +```bash +pnpm dev:firefox +pnpm build:firefox +pnpm zip:firefox +``` + +## Notes on privacy + +The extension only sends the current tab URL to the local VidBee app on `127.0.0.1` and stores temporary results in browser storage for faster reloads. diff --git a/extension/entrypoints/popup/App.css b/extension/entrypoints/popup/App.css index 9c8e3b6..7be4198 100644 --- a/extension/entrypoints/popup/App.css +++ b/extension/entrypoints/popup/App.css @@ -208,16 +208,42 @@ h1 { gap: 12px; } -.troubleshoot-box { - background: var(--border); +.error-header { + display: flex; + flex-direction: column; + gap: 4px; +} + +.error-title { + font-size: 16px; + font-weight: 600; + margin: 0; + color: var(--fg); +} + +.error-description { + font-size: 12px; + color: var(--fg-secondary); + margin: 0; + line-height: 1.5; +} + +.action-grid { + display: grid; + gap: 12px; +} + +.action-card { + border: 1px solid var(--border); + border-radius: 8px; padding: 12px; - border-radius: 6px; display: flex; flex-direction: column; gap: 8px; + background: #fafafa; } -.troubleshoot-title { +.action-title { font-size: 11px; font-weight: 600; color: var(--fg-secondary); @@ -226,31 +252,31 @@ h1 { letter-spacing: 0.05em; } -.troubleshoot-actions { - display: flex; - align-items: center; - gap: 8px; +.action-text { font-size: 13px; + color: var(--fg); + margin: 0; + line-height: 1.5; } -.link-button { - background: none; - border: none; - padding: 0; - color: var(--accent); - text-decoration: none; +.secondary-button { + border: 1px solid var(--fg); + border-radius: 8px; + padding: 8px 10px; + font-size: 12px; font-weight: 500; cursor: pointer; - font-family: inherit; - font-size: inherit; + background: transparent; + color: var(--fg); + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: fit-content; } -.link-button:hover { - text-decoration: underline; -} - -.divider { - color: var(--fg-secondary); +.secondary-button:hover { + background: var(--border); } diff --git a/extension/entrypoints/popup/App.tsx b/extension/entrypoints/popup/App.tsx index dd074b3..07c0575 100644 --- a/extension/entrypoints/popup/App.tsx +++ b/extension/entrypoints/popup/App.tsx @@ -265,14 +265,49 @@ function App() { return result }, [groupedFormats.video]) + const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + const clientLaunchDelayMs = 2000 + + const openClientApp = async () => { + window.location.href = 'vidbee://' + await wait(clientLaunchDelayMs) + } + const handleOpenClient = () => { if (!currentUrl) return const deepLink = `vidbee://download?url=${encodeURIComponent(currentUrl)}` window.location.href = deepLink } + const handleOpenClientAndRetry = async () => { + await openClientApp() + setRetryTrigger((count) => count + 1) + } + + const handleRetry = () => { + setRetryTrigger((count) => count + 1) + } + + const isInvalidPageError = error === 'Please open a valid video page first.' + const isClientConnectionError = Boolean(error?.includes('Client connection failed')) + const errorTitle = isInvalidPageError + ? 'Open a video page' + : isClientConnectionError + ? 'Connect the VidBee app' + : 'Something went wrong' + const errorDescription = isInvalidPageError + ? 'Navigate to a supported video page, then try again.' + : isClientConnectionError + ? 'The extension needs the VidBee desktop app to be running.' + : 'Try again in a moment.' + const renderStatus = () => { - // if (loading) return null + if (loading) + return ( + +
Working + + ) if (error) return ( @@ -307,31 +342,54 @@ function App() { {!loading && error && (
-
{error}
-
-

Having trouble?

-
- - - - Download - -
+
+

{errorTitle}

+

{errorDescription}

+
{error}
+ {isClientConnectionError ? ( +
+
+

Client installed

+

+ Start VidBee and keep it running, then we will retry automatically. +

+ +
+
+

Need the app?

+

+ Download VidBee once, install it, then come back here to try again. +

+ + Download VidBee + +
+
+ ) : ( +
+

Try again

+

+ {isInvalidPageError + ? 'Open a supported video page, then retry.' + : 'Retry after a moment.'} +

+ +
+ )}
)} diff --git a/extension/package.json b/extension/package.json index 980316c..bfe81ef 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,8 +1,8 @@ { - "name": "wxt-react-starter", + "name": "vidbee-extension", "description": "manifest.json description", "private": true, - "version": "0.0.0", + "version": "0.0.1", "type": "module", "scripts": { "dev": "wxt", diff --git a/extension/public/_locales/en/messages.json b/extension/public/_locales/en/messages.json index 331f65c..51ba0d8 100644 --- a/extension/public/_locales/en/messages.json +++ b/extension/public/_locales/en/messages.json @@ -1,4 +1,10 @@ { + "extensionName": { + "message": "VidBee Video Downloader" + }, + "extensionDescription": { + "message": "Download videos from over 1,000 websites with VidBee." + }, "downloadWithVidBee": { "message": "Download with VidBee" }, diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index 12b77c1..9b90432 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -4,6 +4,8 @@ import { defineConfig } from 'wxt' export default defineConfig({ modules: ['@wxt-dev/module-react'], manifest: { + name: '__MSG_extensionName__', + description: '__MSG_extensionDescription__', default_locale: 'en', host_permissions: ['http://127.0.0.1/*'], permissions: ['activeTab', 'storage']