diff --git a/src/renderer/src/components/Popups/UpdateDialogPopup.tsx b/src/renderer/src/components/Popups/UpdateDialogPopup.tsx new file mode 100644 index 000000000..29afcc0d2 --- /dev/null +++ b/src/renderer/src/components/Popups/UpdateDialogPopup.tsx @@ -0,0 +1,205 @@ +import { loggerService } from '@logger' +import { TopView } from '@renderer/components/TopView' +import { handleSaveData } from '@renderer/store' +import { Button, Modal } from 'antd' +import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import Markdown from 'react-markdown' +import styled from 'styled-components' + +const logger = loggerService.withContext('UpdateDialog') + +interface ShowParams { + releaseInfo: UpdateInfo | null +} + +interface Props extends ShowParams { + resolve: (data: any) => void +} + +const PopupContainer: React.FC = ({ releaseInfo, resolve }) => { + const { t } = useTranslation() + const [open, setOpen] = useState(true) + const [isInstalling, setIsInstalling] = useState(false) + + useEffect(() => { + if (releaseInfo) { + logger.info('Update dialog opened', { version: releaseInfo.version }) + } + }, [releaseInfo]) + + const handleInstall = async () => { + setIsInstalling(true) + try { + await handleSaveData() + await window.api.quitAndInstall() + setOpen(false) + } catch (error) { + logger.error('Failed to save data before update', error as Error) + setIsInstalling(false) + window.toast.error(t('update.saveDataError')) + } + } + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + UpdateDialogPopup.hide = onCancel + + const releaseNotes = releaseInfo?.releaseNotes + + return ( + +

{t('update.title')}

+

{t('update.message').replace('{{version}}', releaseInfo?.version || '')}

+ + } + open={open} + onCancel={onCancel} + afterClose={onClose} + transitionName="animation-move-down" + centered + width={720} + footer={[ + , + + ]}> + + + + {typeof releaseNotes === 'string' + ? releaseNotes + : Array.isArray(releaseNotes) + ? releaseNotes + .map((note: ReleaseNoteInfo) => note.note) + .filter(Boolean) + .join('\n\n') + : t('update.noReleaseNotes')} + + + +
+ ) +} + +const TopViewKey = 'UpdateDialogPopup' + +export default class UpdateDialogPopup { + static topviewId = 0 + static hide() { + TopView.hide(TopViewKey) + } + static show(props: ShowParams) { + return new Promise((resolve) => { + TopView.show( + { + resolve(v) + TopView.hide(TopViewKey) + }} + />, + TopViewKey + ) + }) + } +} + +const ModalHeaderWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + + h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--color-text-1); + } + + p { + margin: 0; + font-size: 14px; + color: var(--color-text-2); + } +` + +const ModalBodyWrapper = styled.div` + max-height: 450px; + overflow-y: auto; + padding: 12px 0; +` + +const ReleaseNotesWrapper = styled.div` + background-color: var(--color-bg-2); + border-radius: 8px; + + p { + margin: 0 0 12px 0; + color: var(--color-text-2); + font-size: 14px; + line-height: 1.6; + + &:last-child { + margin-bottom: 0; + } + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin: 16px 0 8px 0; + color: var(--color-text-1); + font-weight: 600; + + &:first-child { + margin-top: 0; + } + } + + ul, + ol { + margin: 8px 0; + padding-left: 24px; + color: var(--color-text-2); + } + + li { + margin: 4px 0; + } + + code { + padding: 2px 6px; + background-color: var(--color-bg-3); + border-radius: 4px; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 13px; + } + + pre { + padding: 12px; + background-color: var(--color-bg-3); + border-radius: 6px; + overflow-x: auto; + + code { + padding: 0; + background-color: transparent; + } + } +` diff --git a/src/renderer/src/components/UpdateDialog.tsx b/src/renderer/src/components/UpdateDialog.tsx deleted file mode 100644 index ee87265c0..000000000 --- a/src/renderer/src/components/UpdateDialog.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ScrollShadow } from '@heroui/react' -import { loggerService } from '@logger' -import { handleSaveData } from '@renderer/store' -import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime' -import React, { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import Markdown from 'react-markdown' - -const logger = loggerService.withContext('UpdateDialog') - -interface UpdateDialogProps { - isOpen: boolean - onClose: () => void - releaseInfo: UpdateInfo | null -} - -const UpdateDialog: React.FC = ({ isOpen, onClose, releaseInfo }) => { - const { t } = useTranslation() - const [isInstalling, setIsInstalling] = useState(false) - - useEffect(() => { - if (isOpen && releaseInfo) { - logger.info('Update dialog opened', { version: releaseInfo.version }) - } - }, [isOpen, releaseInfo]) - - const handleInstall = async () => { - setIsInstalling(true) - try { - await handleSaveData() - await window.api.quitAndInstall() - } catch (error) { - logger.error('Failed to save data before update', error as Error) - setIsInstalling(false) - window.toast.error(t('update.saveDataError')) - } - } - - const releaseNotes = releaseInfo?.releaseNotes - - return ( - - - {(onModalClose) => ( - <> - -

{t('update.title')}

-

- {t('update.message').replace('{{version}}', releaseInfo?.version || '')} -

-
- - - -
- - {typeof releaseNotes === 'string' - ? releaseNotes - : Array.isArray(releaseNotes) - ? releaseNotes - .map((note: ReleaseNoteInfo) => note.note) - .filter(Boolean) - .join('\n\n') - : t('update.noReleaseNotes')} - -
-
-
- - - - - - - - )} -
-
- ) -} - -export default UpdateDialog diff --git a/src/renderer/src/pages/home/components/UpdateAppButton.tsx b/src/renderer/src/pages/home/components/UpdateAppButton.tsx index afd553109..72c46c9fa 100644 --- a/src/renderer/src/pages/home/components/UpdateAppButton.tsx +++ b/src/renderer/src/pages/home/components/UpdateAppButton.tsx @@ -1,6 +1,5 @@ import { SyncOutlined } from '@ant-design/icons' -import { useDisclosure } from '@heroui/react' -import UpdateDialog from '@renderer/components/UpdateDialog' +import UpdateDialogPopup from '@renderer/components/Popups/UpdateDialogPopup' import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' import { Button } from 'antd' @@ -12,7 +11,6 @@ const UpdateAppButton: FC = () => { const { update } = useRuntime() const { autoCheckUpdate } = useSettings() const { t } = useTranslation() - const { isOpen, onOpen, onClose } = useDisclosure() if (!update) { return null @@ -22,19 +20,21 @@ const UpdateAppButton: FC = () => { return null } + const handleOpenUpdateDialog = () => { + UpdateDialogPopup.show({ releaseInfo: update.info || null }) + } + return ( } color="orange" variant="outlined" size="small"> {t('button.update_available')} - - ) } diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index ef8ecc11e..5feb2fa5b 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -1,8 +1,7 @@ import { GithubOutlined } from '@ant-design/icons' -import { useDisclosure } from '@heroui/react' import IndicatorLight from '@renderer/components/IndicatorLight' import { HStack } from '@renderer/components/Layout' -import UpdateDialog from '@renderer/components/UpdateDialog' +import UpdateDialogPopup from '@renderer/components/Popups/UpdateDialogPopup' import { APP_NAME, AppLogo } from '@renderer/config/env' import { useTheme } from '@renderer/context/ThemeProvider' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' @@ -15,7 +14,6 @@ import { ThemeMode } from '@renderer/types' import { runAsyncFunction } from '@renderer/utils' import { UpgradeChannel } from '@shared/config/constant' import { Avatar, Button, Progress, Radio, Row, Switch, Tag, Tooltip } from 'antd' -import type { UpdateInfo } from 'builder-util-runtime' import { debounce } from 'lodash' import { Bug, Building2, Github, Globe, Mail, Rss } from 'lucide-react' import { BadgeQuestionMark } from 'lucide-react' @@ -31,8 +29,6 @@ import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitl const AboutSettings: FC = () => { const [version, setVersion] = useState('') const [isPortable, setIsPortable] = useState(false) - const [updateDialogInfo, setUpdateDialogInfo] = useState(null) - const { isOpen, onOpen, onClose } = useDisclosure() const { t } = useTranslation() const { autoCheckUpdate, setAutoCheckUpdate, testPlan, setTestPlan, testChannel, setTestChannel } = useSettings() const { theme } = useTheme() @@ -48,8 +44,7 @@ const AboutSettings: FC = () => { if (update.downloaded) { // Open update dialog directly in renderer - setUpdateDialogInfo(update.info || null) - onOpen() + UpdateDialogPopup.show({ releaseInfo: update.info || null }) return } @@ -342,9 +337,6 @@ const AboutSettings: FC = () => { - - {/* Update Dialog */} - ) }