feat: minapp show&copy current REAL url and can open it

This commit is contained in:
fullex 2025-03-31 12:15:57 +08:00 committed by 亢奋猫
parent 72c5de3b81
commit 5223a3c5a6
7 changed files with 84 additions and 17 deletions

View File

@ -1,6 +1,7 @@
import { import {
CloseOutlined, CloseOutlined,
CodeOutlined, CodeOutlined,
CopyOutlined,
ExportOutlined, ExportOutlined,
MinusOutlined, MinusOutlined,
PushpinOutlined, PushpinOutlined,
@ -42,6 +43,9 @@ const MinappPopupContainer: React.FC = () => {
const [isPopupShow, setIsPopupShow] = useState(true) const [isPopupShow, setIsPopupShow] = useState(true)
/** whether the current minapp is ready */ /** whether the current minapp is ready */
const [isReady, setIsReady] = useState(false) const [isReady, setIsReady] = useState(false)
/** the current REAL url of the minapp
* different from the app preset url, because user may navigate in minapp */
const [currentUrl, setCurrentUrl] = useState<string | null>(null)
/** store the last minapp id and show status */ /** store the last minapp id and show status */
const lastMinappId = useRef<string | null>(null) const lastMinappId = useRef<string | null>(null)
@ -59,6 +63,11 @@ const MinappPopupContainer: React.FC = () => {
/** set the popup display status */ /** set the popup display status */
useEffect(() => { useEffect(() => {
if (minappShow) { if (minappShow) {
// init the current url
if (currentMinappId && currentAppInfo) {
setCurrentUrl(currentAppInfo.url)
}
setIsPopupShow(true) setIsPopupShow(true)
if (webviewLoadedRefs.current.get(currentMinappId)) { if (webviewLoadedRefs.current.get(currentMinappId)) {
@ -168,6 +177,13 @@ const MinappPopupContainer: React.FC = () => {
} }
} }
/** the callback function to handle the webview navigate to new url */
const handleWebviewNavigate = (appid: string, url: string) => {
if (appid === currentMinappId) {
setCurrentUrl(url)
}
}
/** will open the devtools of the minapp */ /** will open the devtools of the minapp */
const handleOpenDevTools = (appid: string) => { const handleOpenDevTools = (appid: string) => {
const webview = webviewRefs.current.get(appid) const webview = webviewRefs.current.get(appid)
@ -187,12 +203,9 @@ const MinappPopupContainer: React.FC = () => {
} }
} }
/** only open the current url */ /** open the giving url in browser */
const handleOpenLink = (appid: string) => { const handleOpenLink = (url: string) => {
const webview = webviewRefs.current.get(appid) window.api.openWebsite(url)
if (webview) {
window.api.openWebsite(webview.getURL())
}
} }
/** toggle the pin status of the minapp */ /** toggle the pin status of the minapp */
@ -205,11 +218,41 @@ const MinappPopupContainer: React.FC = () => {
} }
/** Title bar of the popup */ /** Title bar of the popup */
const Title = ({ appInfo }: { appInfo: AppInfo | null }) => { const Title = ({ appInfo, url }: { appInfo: AppInfo | null; url: string | null }) => {
if (!appInfo) return null if (!appInfo) return null
const handleCopyUrl = (event: any, url: string) => {
//don't show app-wide context menu
event.preventDefault()
navigator.clipboard
.writeText(url)
.then(() => {
window.message.success('URL ' + t('message.copy.success'))
})
.catch(() => {
window.message.error('URL ' + t('message.copy.failed'))
})
}
return ( return (
<TitleContainer style={{ justifyContent: 'space-between' }}> <TitleContainer style={{ justifyContent: 'space-between' }}>
<TitleText>{appInfo.name}</TitleText> <Tooltip
title={
<TitleTextTooltip>
{url ?? appInfo.url} <br />
<CopyOutlined className="icon-copy" />
{t('minapp.popup.rightclick_copyurl')}
</TitleTextTooltip>
}
mouseEnterDelay={0.8}
placement="rightBottom"
styles={{
root: {
maxWidth: '400px'
}
}}>
<TitleText onContextMenu={(e) => handleCopyUrl(e, url ?? appInfo.url)}>{appInfo.name}</TitleText>
</Tooltip>
<ButtonsGroup className={isWindows ? 'windows' : ''}> <ButtonsGroup className={isWindows ? 'windows' : ''}>
<Tooltip title={t('minapp.popup.refresh')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip title={t('minapp.popup.refresh')} mouseEnterDelay={0.8} placement="bottom">
<Button onClick={() => handleReload(appInfo.id)}> <Button onClick={() => handleReload(appInfo.id)}>
@ -228,7 +271,7 @@ const MinappPopupContainer: React.FC = () => {
)} )}
{appInfo.canOpenExternalLink && ( {appInfo.canOpenExternalLink && (
<Tooltip title={t('minapp.popup.openExternal')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip title={t('minapp.popup.openExternal')} mouseEnterDelay={0.8} placement="bottom">
<Button onClick={() => handleOpenLink(appInfo.id)}> <Button onClick={() => handleOpenLink(url ?? appInfo.url)}>
<ExportOutlined /> <ExportOutlined />
</Button> </Button>
</Tooltip> </Tooltip>
@ -266,6 +309,7 @@ const MinappPopupContainer: React.FC = () => {
url={app.url} url={app.url}
onSetRefCallback={handleWebviewSetRef} onSetRefCallback={handleWebviewSetRef}
onLoadedCallback={handleWebviewLoaded} onLoadedCallback={handleWebviewLoaded}
onNavigateCallback={handleWebviewNavigate}
/> />
)) ))
@ -275,7 +319,7 @@ const MinappPopupContainer: React.FC = () => {
return ( return (
<Drawer <Drawer
title={<Title appInfo={currentAppInfo} />} title={<Title appInfo={currentAppInfo} url={currentUrl} />}
placement="bottom" placement="bottom"
onClose={handlePopupMinimize} onClose={handlePopupMinimize}
open={isPopupShow} open={isPopupShow}
@ -321,8 +365,18 @@ const TitleText = styled.div`
font-size: 14px; font-size: 14px;
color: var(--color-text-1); color: var(--color-text-1);
margin-right: 10px; margin-right: 10px;
user-select: none; -webkit-app-region: no-drag;
` `
const TitleTextTooltip = styled.span`
font-size: 0.8rem;
.icon-copy {
font-size: 0.7rem;
padding-right: 5px;
}
`
const ButtonsGroup = styled.div` const ButtonsGroup = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -11,12 +11,14 @@ const WebviewContainer = memo(
appid, appid,
url, url,
onSetRefCallback, onSetRefCallback,
onLoadedCallback onLoadedCallback,
onNavigateCallback
}: { }: {
appid: string appid: string
url: string url: string
onSetRefCallback: (appid: string, element: WebviewTag | null) => void onSetRefCallback: (appid: string, element: WebviewTag | null) => void
onLoadedCallback: (appid: string) => void onLoadedCallback: (appid: string) => void
onNavigateCallback: (appid: string, url: string) => void
}) => { }) => {
const webviewRef = useRef<WebviewTag | null>(null) const webviewRef = useRef<WebviewTag | null>(null)
@ -47,8 +49,13 @@ const WebviewContainer = memo(
onLoadedCallback(appid) onLoadedCallback(appid)
} }
const handleNavigate = (event: any) => {
onNavigateCallback(appid, event.url)
}
webviewRef.current.addEventListener('new-window', handleNewWindow) webviewRef.current.addEventListener('new-window', handleNewWindow)
webviewRef.current.addEventListener('did-finish-load', handleLoaded) webviewRef.current.addEventListener('did-finish-load', handleLoaded)
webviewRef.current.addEventListener('did-navigate-in-page', handleNavigate)
// we set the url when the webview is ready // we set the url when the webview is ready
webviewRef.current.src = url webviewRef.current.src = url
@ -56,6 +63,7 @@ const WebviewContainer = memo(
return () => { return () => {
webviewRef.current?.removeEventListener('new-window', handleNewWindow) webviewRef.current?.removeEventListener('new-window', handleNewWindow)
webviewRef.current?.removeEventListener('did-finish-load', handleLoaded) webviewRef.current?.removeEventListener('did-finish-load', handleLoaded)
webviewRef.current?.removeEventListener('did-navigate-in-page', handleNavigate)
} }
// because the appid and url are enough, no need to add onLoadedCallback // because the appid and url are enough, no need to add onLoadedCallback
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -547,7 +547,8 @@
"close": "Close MinApp", "close": "Close MinApp",
"minimize": "Minimize MinApp", "minimize": "Minimize MinApp",
"devtools": "Developer Tools", "devtools": "Developer Tools",
"openExternal": "Open in Browser" "openExternal": "Open in Browser",
"rightclick_copyurl": "Right-click to copy URL"
}, },
"sidebar.add.title": "Add to sidebar", "sidebar.add.title": "Add to sidebar",
"sidebar.remove.title": "Remove from sidebar", "sidebar.remove.title": "Remove from sidebar",

View File

@ -547,7 +547,8 @@
"close": "ミニアプリを閉じる", "close": "ミニアプリを閉じる",
"minimize": "ミニアプリを最小化", "minimize": "ミニアプリを最小化",
"devtools": "開発者ツール", "devtools": "開発者ツール",
"openExternal": "ブラウザで開く" "openExternal": "ブラウザで開く",
"rightclick_copyurl": "右クリックでURLをコピー"
}, },
"sidebar.add.title": "サイドバーに追加", "sidebar.add.title": "サイドバーに追加",
"sidebar.remove.title": "サイドバーから削除", "sidebar.remove.title": "サイドバーから削除",

View File

@ -547,7 +547,8 @@
"close": "Закрыть встроенное приложение", "close": "Закрыть встроенное приложение",
"minimize": "Свернуть встроенное приложение", "minimize": "Свернуть встроенное приложение",
"devtools": "Инструменты разработчика", "devtools": "Инструменты разработчика",
"openExternal": "Открыть в браузере" "openExternal": "Открыть в браузере",
"rightclick_copyurl": "ПКМ → Копировать URL"
}, },
"sidebar.add.title": "Добавить в боковую панель", "sidebar.add.title": "Добавить в боковую панель",
"sidebar.remove.title": "Удалить из боковой панели", "sidebar.remove.title": "Удалить из боковой панели",

View File

@ -547,7 +547,8 @@
"close": "关闭小程序", "close": "关闭小程序",
"minimize": "最小化小程序", "minimize": "最小化小程序",
"devtools": "开发者工具", "devtools": "开发者工具",
"openExternal": "在浏览器中打开" "openExternal": "在浏览器中打开",
"rightclick_copyurl": "右键复制URL"
}, },
"sidebar.add.title": "添加到侧边栏", "sidebar.add.title": "添加到侧边栏",
"sidebar.remove.title": "从侧边栏移除", "sidebar.remove.title": "从侧边栏移除",

View File

@ -547,7 +547,8 @@
"close": "關閉小工具", "close": "關閉小工具",
"minimize": "最小化小工具", "minimize": "最小化小工具",
"devtools": "開發者工具", "devtools": "開發者工具",
"openExternal": "在瀏覽器中開啟" "openExternal": "在瀏覽器中開啟",
"rightclick_copyurl": "右鍵複製URL"
}, },
"sidebar.add.title": "新增到側邊欄", "sidebar.add.title": "新增到側邊欄",
"sidebar.remove.title": "從側邊欄移除", "sidebar.remove.title": "從側邊欄移除",