mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 19:22:44 +08:00
feat(ui): add QR code sharing for remote profiles
Add "Share QR Code" context menu item to remote profile cards.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
- 订阅 QR code 分享
|
||||
- 新增 macOS 托盘速率显示
|
||||
- 快捷键操作通知操作结果
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"monaco-editor": "^0.55.1",
|
||||
"monaco-yaml": "^5.4.1",
|
||||
"nanoid": "^5.1.7",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "19.2.5",
|
||||
"react-dom": "19.2.5",
|
||||
"react-error-boundary": "6.1.1",
|
||||
|
||||
Generated
+12
@@ -107,6 +107,9 @@ importers:
|
||||
nanoid:
|
||||
specifier: ^5.1.7
|
||||
version: 5.1.7
|
||||
qrcode.react:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0(react@19.2.5)
|
||||
react:
|
||||
specifier: 19.2.5
|
||||
version: 19.2.5
|
||||
@@ -3101,6 +3104,11 @@ packages:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
qrcode.react@4.2.0:
|
||||
resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
react-dom@19.2.5:
|
||||
resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==}
|
||||
peerDependencies:
|
||||
@@ -6762,6 +6770,10 @@ snapshots:
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
qrcode.react@4.2.0(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
|
||||
react-dom@19.2.5(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import {
|
||||
RefreshRounded,
|
||||
DragIndicatorRounded,
|
||||
CheckBoxRounded,
|
||||
CheckBoxOutlineBlankRounded,
|
||||
CheckBoxRounded,
|
||||
DragIndicatorRounded,
|
||||
RefreshRounded,
|
||||
} from '@mui/icons-material'
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
keyframes,
|
||||
MenuItem,
|
||||
LinearProgress,
|
||||
Menu,
|
||||
CircularProgress,
|
||||
MenuItem,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { open } from '@tauri-apps/plugin-shell'
|
||||
import { useLockFn } from 'ahooks'
|
||||
@@ -28,11 +28,11 @@ import { GroupsEditorViewer } from '@/components/profile/groups-editor-viewer'
|
||||
import { RulesEditorViewer } from '@/components/profile/rules-editor-viewer'
|
||||
import { useEditorDocument } from '@/hooks/use-editor-document'
|
||||
import {
|
||||
viewProfile,
|
||||
readProfileFile,
|
||||
updateProfile,
|
||||
saveProfileFile,
|
||||
getNextUpdateTime,
|
||||
readProfileFile,
|
||||
saveProfileFile,
|
||||
updateProfile,
|
||||
viewProfile,
|
||||
} from '@/services/cmds'
|
||||
import { showNotice } from '@/services/notice-service'
|
||||
import { useLoadingCache, useSetLoadingCache } from '@/services/states'
|
||||
@@ -42,6 +42,7 @@ import parseTraffic from '@/utils/parse-traffic'
|
||||
|
||||
import { ProfileBox } from './profile-box'
|
||||
import { ProxiesEditorViewer } from './proxies-editor-viewer'
|
||||
import { QrViewer } from './qr-viewer'
|
||||
const round = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
@@ -285,6 +286,7 @@ export const ProfileItem = (props: Props) => {
|
||||
const [mergeOpen, setMergeOpen] = useState(false)
|
||||
const [scriptOpen, setScriptOpen] = useState(false)
|
||||
const [confirmOpen, setConfirmOpen] = useState(false)
|
||||
const [qrOpen, setQrOpen] = useState(false)
|
||||
|
||||
const loadProfileDocument = useCallback(() => readProfileFile(uid), [uid])
|
||||
const loadMergeDocument = useCallback(
|
||||
@@ -319,6 +321,11 @@ export const ProfileItem = (props: Props) => {
|
||||
onEdit()
|
||||
}
|
||||
|
||||
const onShareQrCode = () => {
|
||||
setAnchorEl(null)
|
||||
setQrOpen(true)
|
||||
}
|
||||
|
||||
const onEditFile = () => {
|
||||
setAnchorEl(null)
|
||||
setFileOpen(true)
|
||||
@@ -409,6 +416,7 @@ export const ProfileItem = (props: Props) => {
|
||||
const menuLabels: Record<string, TranslationKey> = {
|
||||
home: 'profiles.components.menu.home',
|
||||
select: 'profiles.components.menu.select',
|
||||
shareQrCode: 'profiles.components.menu.shareQrCode',
|
||||
editInfo: 'profiles.components.menu.editInfo',
|
||||
editFile: 'profiles.components.menu.editFile',
|
||||
editRules: 'profiles.components.menu.editRules',
|
||||
@@ -437,6 +445,11 @@ export const ProfileItem = (props: Props) => {
|
||||
handler: onForceSelect,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: menuLabels.shareQrCode,
|
||||
handler: onShareQrCode,
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: menuLabels.editInfo,
|
||||
handler: onEditInfo,
|
||||
@@ -978,6 +991,13 @@ export const ProfileItem = (props: Props) => {
|
||||
setConfirmOpen(false)
|
||||
}}
|
||||
/>
|
||||
{qrOpen && itemData.url && (
|
||||
<QrViewer
|
||||
open={true}
|
||||
value={`${itemData.url}${itemData.url.includes('?') ? '&' : '?'}name=${encodeURIComponent(name)}`}
|
||||
onClose={() => setQrOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Box, Dialog, DialogContent, DialogTitle } from '@mui/material'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
open: boolean
|
||||
value: string
|
||||
title?: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export const QrViewer = (props: Props) => {
|
||||
const { open, value, title, onClose } = props
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xs">
|
||||
<DialogTitle>{title ?? t('profiles.modals.qrViewer.title')}</DialogTitle>
|
||||
<DialogContent sx={{ pb: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
p: 2,
|
||||
bgcolor: '#fff',
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<QRCodeSVG value={value} size={256} level="M" />
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Home",
|
||||
"select": "اختيار",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "تعديل المعلومات",
|
||||
"editFile": "تعديل الملف",
|
||||
"editRules": "تعديل القواعد",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "وحدة التحكم للسكريبت"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Startseite",
|
||||
"select": "Verwenden",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "Informationen bearbeiten",
|
||||
"editFile": "Datei bearbeiten",
|
||||
"editRules": "Regeln bearbeiten",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "Skript-Konsole-Ausgabe"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Home",
|
||||
"select": "Select",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "Edit Info",
|
||||
"editFile": "Edit File",
|
||||
"editRules": "Edit Rules",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "Script Console"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Hogar",
|
||||
"select": "Usar",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "Editar información",
|
||||
"editFile": "Editar archivo",
|
||||
"editRules": "Editar reglas",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "Salida de la consola del script"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Home",
|
||||
"select": "انتخاب",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "ویرایش اطلاعات",
|
||||
"editFile": "ویرایش فایل",
|
||||
"editRules": "ویرایش قوانین",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "کنسول اسکریپت"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Home",
|
||||
"select": "Pilih",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "Ubah Info",
|
||||
"editFile": "Ubah Berkas",
|
||||
"editRules": "Ubah Aturan",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "Konsol Skrip"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "ホーム",
|
||||
"select": "使用する",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "情報を編集",
|
||||
"editFile": "ファイルを編集",
|
||||
"editRules": "ルールを編集",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "スクリプトコンソール出力"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "홈",
|
||||
"select": "선택",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "정보 편집",
|
||||
"editFile": "파일 편집",
|
||||
"editRules": "규칙 편집",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "스크립트 콘솔"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Главная",
|
||||
"select": "Выбрать",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "Изменить информацию",
|
||||
"editFile": "Изменить файл",
|
||||
"editRules": "Редактировать правила",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "Консоль скрипта"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Ana Sayfa",
|
||||
"select": "Seç",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "Bilgileri Düzenle",
|
||||
"editFile": "Dosyayı Düzenle",
|
||||
"editRules": "Kuralları Düzenle",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "Betik Konsolu"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "Home",
|
||||
"select": "Сайлау",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "Мәгълүматны үзгәртү",
|
||||
"editFile": "Файлны үзгәртү",
|
||||
"editRules": "Кагыйдәләрне үзгәртү",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "Скрипт консоле"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "首 页",
|
||||
"select": "使用",
|
||||
"shareQrCode": "分享二维码",
|
||||
"editInfo": "编辑信息",
|
||||
"editFile": "编辑文件",
|
||||
"editRules": "编辑规则",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "脚本控制台输出"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "订阅二维码"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"menu": {
|
||||
"home": "首 頁",
|
||||
"select": "使用",
|
||||
"shareQrCode": "Share QR Code",
|
||||
"editInfo": "編輯資訊",
|
||||
"editFile": "編輯檔案",
|
||||
"editRules": "編輯規則",
|
||||
@@ -177,6 +178,9 @@
|
||||
},
|
||||
"logViewer": {
|
||||
"title": "指令碼控制台輸出"
|
||||
},
|
||||
"qrViewer": {
|
||||
"title": "Subscription QR Code"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ export const translationKeys = [
|
||||
'profiles.components.fileInput.chooseFile',
|
||||
'profiles.components.menu.home',
|
||||
'profiles.components.menu.select',
|
||||
'profiles.components.menu.shareQrCode',
|
||||
'profiles.components.menu.editInfo',
|
||||
'profiles.components.menu.editFile',
|
||||
'profiles.components.menu.editRules',
|
||||
@@ -210,6 +211,7 @@ export const translationKeys = [
|
||||
'profiles.modals.confirmDelete.title',
|
||||
'profiles.modals.confirmDelete.message',
|
||||
'profiles.modals.logViewer.title',
|
||||
'profiles.modals.qrViewer.title',
|
||||
'proxies.page.modes.rule',
|
||||
'proxies.page.modes.global',
|
||||
'proxies.page.modes.direct',
|
||||
|
||||
@@ -226,6 +226,7 @@ export interface TranslationResources {
|
||||
home: string
|
||||
openFile: string
|
||||
select: string
|
||||
shareQrCode: string
|
||||
update: string
|
||||
updateViaProxy: string
|
||||
}
|
||||
@@ -337,6 +338,9 @@ export interface TranslationResources {
|
||||
}
|
||||
title: string
|
||||
}
|
||||
qrViewer: {
|
||||
title: string
|
||||
}
|
||||
}
|
||||
page: {
|
||||
actions: {
|
||||
|
||||
Reference in New Issue
Block a user