From 6b45661fe3a7648ed79b5054a2ba1dc3a7dda42f Mon Sep 17 00:00:00 2001 From: go0p Date: Wed, 19 Feb 2025 21:43:49 +0800 Subject: [PATCH] fix popup options.html context --- public/images/yak.svg | 10 + public/manifest.json | 16 +- public/proxy/content.js | 99 ++++++++-- src/components/ProxySwitch/index.css | 32 ++++ src/components/ProxySwitch/index.tsx | 262 +++++++++++++++++---------- 5 files changed, 303 insertions(+), 116 deletions(-) create mode 100644 public/images/yak.svg diff --git a/public/images/yak.svg b/public/images/yak.svg new file mode 100644 index 0000000..9e0bf10 --- /dev/null +++ b/public/images/yak.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json index 6dc35b9..f7a74bd 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -39,18 +39,20 @@ "tabs" ], "host_permissions": [ - "", - "*://mitm/*" + "" ], "web_accessible_resources": [ { "resources": [ - "types/*.js", - "proxy/*.js", - "socket.js", - "proxy.js", - "proxy/content.js" + "images/*", + "proxy/*" + ], + "matches": [""] + }, + { + "resources": [ + "/images/yak.svg" ], "matches": [""] } diff --git a/public/proxy/content.js b/public/proxy/content.js index b77396f..9917a30 100644 --- a/public/proxy/content.js +++ b/public/proxy/content.js @@ -9,6 +9,9 @@ const ProxyActionType = { UPDATE_PROXY_CONFIG: 'UPDATE_PROXY_CONFIG' }; +// 在文件顶部添加常量声明 +const YAK_ICON_URL = chrome.runtime.getURL('/images/yak.svg'); + // 添加一个通用的消息发送函数 async function sendMessageWithRetry(message, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { @@ -139,26 +142,39 @@ async function updatePanel() { // 修改这里的判断逻辑 let html = ` -
- 🔴 +
+ 🟢 [直接连接] +
-
+
⚙️ [系统代理] +
+
`; - // 添加自定义代理配置 + // 修改自定义代理配置的判断逻辑 configs.forEach(config => { if (config.id !== 'direct' && config.id !== 'system') { - // 检查当前代理是否与配置匹配 - const isActive = currentProxy.enable && - currentProxy.proxy === `${config.scheme}://${config.host}:${config.port}`; + const proxyUrl = `${config.scheme}://${config.host}:${config.port}`; + const isActive = currentProxy.enable && currentProxy.proxy === proxyUrl && config.enabled; + + // 添加 title 属性显示详细信息 + const tooltipText = `${config.scheme.toUpperCase()} ${config.host}:${config.port}`; + html += ` -
+
🌐 ${config.name} +
`; } @@ -247,7 +263,7 @@ function createFloatingPanel() { position: fixed; top: 20px; right: 0; - width: 200px; + width: 180px; background: white; border-radius: 8px 0 0 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); @@ -299,21 +315,41 @@ function createFloatingPanel() { } .panel-content { - padding: 12px; + padding: 4px; } .proxy-item { + position: relative; display: flex; align-items: center; - padding: 8px 12px; + padding: 4px 10px; cursor: pointer; transition: all 0.2s; color: #666; border-left: 3px solid transparent; + overflow: hidden; } - .proxy-item:hover { - background: #f5f5f5; + .proxy-item > span { + position: relative; + z-index: 1; + } + + .watermark-icon { + position: absolute; + right: 0; + width: 100%; + height: 100%; + opacity: 0.3; + pointer-events: none; + display: none; + object-fit: contain; + object-position: right center; + padding: 4px; + } + + .proxy-item.active .watermark-icon { + display: block; } .proxy-item.active { @@ -333,6 +369,10 @@ function createFloatingPanel() { transition: color 0.2s; } + .proxy-item:hover { + background: #f5f5f5; + } + .proxy-item:hover span:first-child { color: #ff6b00; } @@ -340,7 +380,7 @@ function createFloatingPanel() { .add-proxy { display: flex; align-items: center; - padding: 8px 12px; + padding: 4px 10px; color: #1890ff; cursor: pointer; border-top: 1px solid #eee; @@ -352,7 +392,7 @@ function createFloatingPanel() { } .settings { - padding: 8px 12px; + padding: 4px 10px; color: #666; cursor: pointer; border-top: 1px solid #eee; @@ -362,6 +402,25 @@ function createFloatingPanel() { .settings:hover { background: #f5f5f5; } + + .panel-header .header-content { + display: flex; + align-items: center; + gap: 8px; + padding: 0 4px; + } + + .yak-icon { + width: 24px; + height: 24px; + object-fit: contain; + } + + .divider { + height: 1px; + background-color: #eee; + margin: 4px 0; + } `; // 创建面板内容 @@ -370,20 +429,24 @@ function createFloatingPanel() { panel.innerHTML = `
- 代理设置 +
+ Yak + 代理设置 +
- 🔴 + 🟢 [直接连接]
⚙️ [系统代理]
+
- 添加代理... + [添加代理...]
👨‍💻 diff --git a/src/components/ProxySwitch/index.css b/src/components/ProxySwitch/index.css index f004061..92a75af 100644 --- a/src/components/ProxySwitch/index.css +++ b/src/components/ProxySwitch/index.css @@ -200,4 +200,36 @@ body { .ant-menu-item .menu-icon { transition: color 0.3s ease; +} + +.proxy-switch-container { + position: relative; + width: 180px; + overflow: hidden; +} + +.panel-watermark { + position: absolute; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + opacity: 0.03; + pointer-events: none; + object-fit: contain; + object-position: right bottom; + z-index: 0; +} + +/* 确保菜单项在水印上层 */ +.ant-menu-item { + position: relative; + z-index: 1; + background: transparent !important; +} + +/* 确保分割线在水印上层 */ +.ant-menu-item-divider { + position: relative; + z-index: 1; } \ No newline at end of file diff --git a/src/components/ProxySwitch/index.tsx b/src/components/ProxySwitch/index.tsx index b8b0969..de927bb 100644 --- a/src/components/ProxySwitch/index.tsx +++ b/src/components/ProxySwitch/index.tsx @@ -6,6 +6,9 @@ import "./index.css"; import type { MenuProps } from 'antd'; import type { ProxyConfig } from '@/types/proxy'; +// 添加 YAK 图标 URL 常量 +const YAK_ICON_URL = chrome.runtime.getURL('/images/yak.svg'); + // 固定的代理模式 const FIXED_MODES = [ { @@ -48,18 +51,21 @@ interface ProxySwitchProps { onProxyChange: (config: ProxyConfig) => void; } -export const ProxySwitch: React.FC = ({ - proxyConfigs, - currentProxy, - onProxyChange, -}) => { - const [currentMode, setCurrentMode] = useState('direct'); +export const ProxySwitch: React.FC = () => { + const [initialized, setInitialized] = useState(false); + const [currentMode, setCurrentMode] = useState(''); const [customProxies, setCustomProxies] = useState([]); const [isLoading, setIsLoading] = useState(false); useEffect(() => { - loadProxyStatus(); - loadCustomProxies(); + const init = async () => { + await Promise.all([ + loadProxyStatus(), + loadCustomProxies() + ]); + setInitialized(true); + }; + init(); }, []); const loadProxyStatus = async () => { @@ -68,55 +74,154 @@ export const ProxySwitch: React.FC = ({ action: ProxyActionType.GET_PROXY_STATUS }); + if (!response) { + console.log('No response from background script'); + return; + } + if (response.success) { const activeMode = response.data.mode; - setCurrentMode(activeMode); - if (FIXED_MODES.some(mode => mode.key === activeMode)) { setCurrentMode(activeMode); } } } catch (error) { console.error('Error loading proxy status:', error); + setCurrentMode('direct'); } }; const loadCustomProxies = async () => { try { - const response = await chrome.runtime.sendMessage({ - action: ProxyActionType.GET_PROXY_CONFIGS - }); - - if (response.success && response.data) { - const proxies = response.data - .filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id)) - .map((proxy: ProxyConfig): CustomProxy => ({ - key: proxy.id, - name: proxy.name, - color: '#1890ff', - config: proxy, - enabled: proxy.enabled - })); - setCustomProxies(proxies); + // 首先检查当前是否在 options 页面的上下文中 + const currentUrl = window.location.href; + const isInOptionsContext = currentUrl.includes('chrome-extension://') && currentUrl.includes('options.html'); - const enabledProxy = proxies.find((proxy: ProxyConfig) => proxy.enabled); - if (enabledProxy) { - setCurrentMode(enabledProxy.key); + if (isInOptionsContext) { + // 如果在 options 页面上下文中,直接使用消息通信 + const response = await chrome.runtime.sendMessage({ + action: ProxyActionType.GET_PROXY_CONFIGS + }); + + if (response?.success && response.data) { + const proxies = response.data + .filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id)) + .map((proxy: ProxyConfig): CustomProxy => ({ + key: proxy.id, + name: proxy.name, + color: '#1890ff', + config: proxy, + enabled: proxy.enabled + })); + setCustomProxies(proxies); + + const enabledProxy = response.data.find((proxy: ProxyConfig) => proxy.enabled); + if (enabledProxy) { + setCurrentMode(enabledProxy.id); + } else { + setCurrentMode('direct'); + } + return; } } + + // 如果不在 options 页面上下文中,使用 IndexedDB + const DB_NAME = 'yaklang_extension'; + const STORE_NAME = 'proxy_configs'; + + // 打开数据库 + const db = await new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 1); + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + }); + + // 从数据库读取代理配置 + const configs = await new Promise((resolve, reject) => { + try { + const transaction = db.transaction([STORE_NAME], 'readonly'); + const store = transaction.objectStore(STORE_NAME); + const request = store.getAll(); + + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result || []); + } catch (error) { + reject(error); + } + }); + + // 处理代理配置 + const proxies = configs + .filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id)) + .map((proxy: ProxyConfig): CustomProxy => ({ + key: proxy.id, + name: proxy.name, + color: '#1890ff', + config: proxy, + enabled: proxy.enabled + })); + setCustomProxies(proxies); + + const enabledProxy = configs.find((proxy: ProxyConfig) => proxy.enabled); + if (enabledProxy) { + setCurrentMode(enabledProxy.id); + } else { + setCurrentMode('direct'); + } + } catch (error) { console.error('Error loading custom proxies:', error); + setCustomProxies([]); + setCurrentMode('direct'); } }; - const handleApplyConfig = async (mode: string) => { + const handleModeChange = async (mode: string) => { + if (mode === 'setting') { + await chrome.runtime.openOptionsPage?.(); + return; + } + + if (mode === 'add') { + try { + const [activeTab] = await chrome.tabs.query({ + active: true, + currentWindow: true + }); + const optionsUrl = chrome.runtime.getURL('/proxy/options.html'); + + if (activeTab?.url === optionsUrl) { + chrome.tabs.sendMessage(activeTab.id!, { + action: 'TRIGGER_ADD_PROXY' + }); + } else { + const tab = await chrome.tabs.create({ + url: optionsUrl + }); + + const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => { + if (tabId === tab.id && changeInfo.status === 'complete') { + chrome.tabs.onUpdated.removeListener(listener); + chrome.tabs.sendMessage(tab.id!, { + action: 'TRIGGER_ADD_PROXY' + }); + } + }; + + chrome.tabs.onUpdated.addListener(listener); + } + } catch (error) { + console.error('Failed to get current tab:', error); + } + return; + } + try { setIsLoading(true); const fixedMode = FIXED_MODES.find(fixed => fixed.key === mode); const customProxy = customProxies.find(proxy => proxy.key === mode); const config = fixedMode?.config || customProxy?.config; - if (!config) { console.error('No config found for mode:', mode); return; @@ -149,63 +254,13 @@ export const ProxySwitch: React.FC = ({ } }; - const handleModeChange = async (mode: string) => { - if (mode === 'setting') { - await chrome.runtime.openOptionsPage?.(); - return; - } - - if (mode === 'add') { - try { - // 获取当前活动标签页 - const [activeTab] = await chrome.tabs.query({ - active: true, - currentWindow: true - }); - const optionsUrl = chrome.runtime.getURL('/proxy/options.html'); - - if (activeTab?.url === optionsUrl) { - // 如果当前就在 options 页面,直接发消息触发添加代理 - chrome.tabs.sendMessage(activeTab.id!, { - action: 'TRIGGER_ADD_PROXY' - }); - } else { - // 如果不在 options 页面,创建新的 - const tab = await chrome.tabs.create({ - url: optionsUrl - }); - - // 等待页面加载完成 - const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => { - if (tabId === tab.id && changeInfo.status === 'complete') { - chrome.tabs.onUpdated.removeListener(listener); - chrome.tabs.sendMessage(tab.id!, { - action: 'TRIGGER_ADD_PROXY' - }); - } - }; - - chrome.tabs.onUpdated.addListener(listener); - } - } catch (error) { - console.error('Failed to get current tab:', error); - } - return; - } - - try { - await handleApplyConfig(mode); - } catch (error) { - console.error('Failed to change proxy mode:', error); - } - }; - const menuItems: MenuProps['items'] = [ ...FIXED_MODES.map(mode => ({ key: mode.key, icon: {mode.icon}, label: mode.name, - className: `${currentMode === mode.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}` + className: `${currentMode === mode.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`, + title: mode.name.replace(/[\[\]]/g, '') })), { type: 'divider' }, ...customProxies.map(proxy => ({ @@ -215,7 +270,8 @@ export const ProxySwitch: React.FC = ({ color: currentMode === proxy.key ? 'var(--yakit-primary)' : 'inherit', opacity: isLoading ? 0.7 : 1 }}>{proxy.name}, - className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}` + className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`, + title: `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}` })), { key: 'add', @@ -231,13 +287,37 @@ export const ProxySwitch: React.FC = ({ } ]; - return ( - !isLoading && handleModeChange(key)} - style={{ width: 180 }} - className={isLoading ? 'menu-loading' : ''} - /> + return initialized ? ( +
+ + !isLoading && handleModeChange(key)} + style={{ width: 180, position: 'relative', zIndex: 1, background: 'transparent' }} + className={isLoading ? 'menu-loading' : ''} + /> +
+ ) : ( +
+ 加载中... +
); }; \ No newline at end of file