mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 23:35:49 +00:00
feat: 解决了一些问题
This commit is contained in:
29
src/components/LivePhotoIcon.tsx
Normal file
29
src/components/LivePhotoIcon.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
interface LivePhotoIconProps {
|
||||
size?: number | string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const LivePhotoIcon: React.FC<LivePhotoIconProps> = ({ size = 24, className = '', style = {} }) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd" strokeLinecap="round" strokeLinejoin="round">
|
||||
<g stroke="currentColor" strokeWidth="2">
|
||||
<circle fill="currentColor" stroke="none" cx="12" cy="12" r="2.5"></circle>
|
||||
<circle cx="12" cy="12" r="5.5"></circle>
|
||||
<circle cx="12" cy="12" r="9" strokeDasharray="1 3.7"></circle>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
251
src/components/UpdateDialog.scss
Normal file
251
src/components/UpdateDialog.scss
Normal file
@@ -0,0 +1,251 @@
|
||||
.update-dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
|
||||
.update-dialog {
|
||||
width: 680px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
animation: slideUp 0.3s ease-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
/* Top Section (White/Gradient) */
|
||||
.dialog-header {
|
||||
background: #ffffff;
|
||||
padding: 40px 20px 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
/* Subtle radial gradient effect in top left as seen in image */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
left: -50px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, rgba(255, 235, 220, 0.4) 0%, rgba(255, 255, 255, 0) 70%);
|
||||
opacity: 0.8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.version-tag {
|
||||
background: #f0eee9;
|
||||
color: #8c7b6e;
|
||||
padding: 4px 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: #333333;
|
||||
margin: 0 0 12px;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 15px;
|
||||
color: #999999;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
/* Content Section (Light Gray) */
|
||||
.dialog-content {
|
||||
background: #f2f2f2;
|
||||
padding: 24px 40px 40px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.update-notes-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 20px 0;
|
||||
margin-bottom: 30px;
|
||||
|
||||
.icon-box {
|
||||
background: #fbfbfb; // Beige-ish white
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
color: #8c7b6e;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03);
|
||||
|
||||
svg {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.text-box {
|
||||
flex: 1;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #333333;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 8px 0 0 18px;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
color: #666666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
margin-bottom: 30px;
|
||||
|
||||
.progress-info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.progress-bar-bg {
|
||||
height: 6px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background: #000000;
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.btn-update {
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
padding: 16px 48px;
|
||||
border-radius: 20px; // Pill shape
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border: none;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
z-index: 10;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: #333;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
132
src/components/UpdateDialog.tsx
Normal file
132
src/components/UpdateDialog.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Quote, X } from 'lucide-react'
|
||||
import './UpdateDialog.scss'
|
||||
|
||||
interface UpdateInfo {
|
||||
version?: string
|
||||
releaseNotes?: string
|
||||
}
|
||||
|
||||
interface UpdateDialogProps {
|
||||
open: boolean
|
||||
updateInfo: UpdateInfo | null
|
||||
onClose: () => void
|
||||
onUpdate: () => void
|
||||
isDownloading: boolean
|
||||
progress: number | {
|
||||
percent: number
|
||||
bytesPerSecond?: number
|
||||
transferred?: number
|
||||
total?: number
|
||||
remaining?: number // seconds
|
||||
}
|
||||
}
|
||||
|
||||
const UpdateDialog: React.FC<UpdateDialogProps> = ({
|
||||
open,
|
||||
updateInfo,
|
||||
onClose,
|
||||
onUpdate,
|
||||
isDownloading,
|
||||
progress
|
||||
}) => {
|
||||
if (!open || !updateInfo) return null
|
||||
|
||||
// Safe normalize progress
|
||||
const safeProgress = typeof progress === 'number' ? { percent: progress } : (progress || { percent: 0 })
|
||||
const percent = safeProgress.percent || 0
|
||||
const bytesPerSecond = safeProgress.bytesPerSecond
|
||||
const total = safeProgress.total
|
||||
const transferred = safeProgress.transferred
|
||||
const remaining = safeProgress.remaining
|
||||
|
||||
// Format bytes
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (!Number.isFinite(bytes) || bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
const unitIndex = Math.max(0, Math.min(i, sizes.length - 1))
|
||||
return parseFloat((bytes / Math.pow(k, unitIndex)).toFixed(1)) + ' ' + sizes[unitIndex]
|
||||
}
|
||||
|
||||
// Format speed
|
||||
const formatSpeed = (bytesPerSecond: number) => {
|
||||
return `${formatBytes(bytesPerSecond)}/s`
|
||||
}
|
||||
|
||||
// Format time
|
||||
const formatTime = (seconds: number) => {
|
||||
if (!Number.isFinite(seconds)) return '计算中...'
|
||||
if (seconds < 60) return `${Math.ceil(seconds)} 秒`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = Math.ceil(seconds % 60)
|
||||
return `${minutes} 分 ${remainingSeconds} 秒`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="update-dialog-overlay">
|
||||
<div className="update-dialog">
|
||||
{!isDownloading && (
|
||||
<button className="close-btn" onClick={onClose}>
|
||||
<X size={20} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="dialog-header">
|
||||
<div className="version-tag">
|
||||
新版本 {updateInfo.version}
|
||||
</div>
|
||||
<h2>欢迎体验全新的 WeFlow</h2>
|
||||
<div className="subtitle">我们带来了一些改进</div>
|
||||
</div>
|
||||
|
||||
<div className="dialog-content">
|
||||
<div className="update-notes-container">
|
||||
<div className="icon-box">
|
||||
<Quote size={20} />
|
||||
</div>
|
||||
<div className="text-box">
|
||||
<h3>优化</h3>
|
||||
{updateInfo.releaseNotes ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: updateInfo.releaseNotes }} />
|
||||
) : (
|
||||
<p>修复了一些已知问题,提升了稳定性。</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isDownloading ? (
|
||||
<div className="progress-section">
|
||||
<div className="progress-info-row">
|
||||
<span>{bytesPerSecond ? formatSpeed(bytesPerSecond) : '下载中...'}</span>
|
||||
<span>{total ? `${formatBytes(transferred || 0)} / ${formatBytes(total)}` : `${percent.toFixed(1)}%`}</span>
|
||||
{remaining !== undefined && <span>剩余 {formatTime(remaining)}</span>}
|
||||
</div>
|
||||
|
||||
<div className="progress-bar-bg">
|
||||
<div
|
||||
className="progress-bar-fill"
|
||||
style={{ width: `${percent}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Fallback status text if detailed info is missing */}
|
||||
{(!bytesPerSecond && !total) && (
|
||||
<div className="status-text">{percent.toFixed(0)}% 已下载</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="actions">
|
||||
<button className="btn-update" onClick={onUpdate}>
|
||||
开启新旅程
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateDialog
|
||||
192
src/components/UpdateProgressCapsule.scss
Normal file
192
src/components/UpdateProgressCapsule.scss
Normal file
@@ -0,0 +1,192 @@
|
||||
.update-progress-capsule {
|
||||
position: fixed;
|
||||
top: 38px; // Just below title bar
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 9998;
|
||||
cursor: pointer;
|
||||
animation: capsuleSlideDown 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
.capsule-content {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.capsule-content {
|
||||
background: var(--bg-primary);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
padding: 8px 18px;
|
||||
border-radius: 24px;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
|
||||
.icon-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-primary);
|
||||
|
||||
.download-icon {
|
||||
animation: capsulePulse 2s infinite ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.info-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
z-index: 1;
|
||||
|
||||
.percent-text {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.speed-text {
|
||||
font-size: 13px;
|
||||
color: var(--text-tertiary);
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 15px;
|
||||
color: #ff4d4f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.available-text {
|
||||
font-size: 15px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--primary);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.capsule-close {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
margin-left: -4px;
|
||||
margin-right: -8px;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-secondary);
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// State Modifiers
|
||||
&.state-available {
|
||||
.capsule-content {
|
||||
background: var(--primary);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
|
||||
.icon-wrapper {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.info-wrapper {
|
||||
.available-text {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.capsule-close {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.state-downloading {
|
||||
.capsule-content {
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.state-error {
|
||||
.capsule-content {
|
||||
background: #fff1f0;
|
||||
border-color: #ffa39e;
|
||||
|
||||
.icon-wrapper {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.info-wrapper .error-text {
|
||||
color: #cf1322;
|
||||
}
|
||||
|
||||
.capsule-close {
|
||||
color: #cf1322;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes capsuleSlideDown {
|
||||
from {
|
||||
transform: translate(-50%, -40px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translate(-50%, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes capsulePulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(2px);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
118
src/components/UpdateProgressCapsule.tsx
Normal file
118
src/components/UpdateProgressCapsule.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React from 'react'
|
||||
import { useAppStore } from '../stores/appStore'
|
||||
import { Download, X, AlertCircle, Info } from 'lucide-react'
|
||||
import './UpdateProgressCapsule.scss'
|
||||
|
||||
const UpdateProgressCapsule: React.FC = () => {
|
||||
const {
|
||||
isDownloading,
|
||||
downloadProgress,
|
||||
showUpdateDialog,
|
||||
setShowUpdateDialog,
|
||||
updateInfo,
|
||||
setUpdateInfo,
|
||||
updateError,
|
||||
setUpdateError
|
||||
} = useAppStore()
|
||||
|
||||
// Control visibility
|
||||
// If dialog is open, we usually hide the capsule UNLESS we want it as a mini-indicator
|
||||
// For now, let's hide it if the dialog is open
|
||||
if (showUpdateDialog) return null
|
||||
|
||||
// State mapping
|
||||
const hasError = !!updateError
|
||||
const hasUpdate = !!updateInfo && updateInfo.hasUpdate
|
||||
|
||||
if (!hasError && !isDownloading && !hasUpdate) return null
|
||||
|
||||
// Safe normalize progress
|
||||
const safeProgress = typeof downloadProgress === 'number' ? { percent: downloadProgress } : (downloadProgress || { percent: 0 })
|
||||
const percent = safeProgress.percent || 0
|
||||
const bytesPerSecond = safeProgress.bytesPerSecond
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (!Number.isFinite(bytes) || bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
const unitIndex = Math.max(0, Math.min(i, sizes.length - 1))
|
||||
return parseFloat((bytes / Math.pow(k, unitIndex)).toFixed(1)) + ' ' + sizes[unitIndex]
|
||||
}
|
||||
|
||||
const formatSpeed = (bps: number) => {
|
||||
return `${formatBytes(bps)}/s`
|
||||
}
|
||||
|
||||
const handleClose = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (hasError) {
|
||||
setUpdateError(null)
|
||||
} else if (hasUpdate && !isDownloading) {
|
||||
setUpdateInfo(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine appearance class and content
|
||||
let capsuleClass = 'update-progress-capsule'
|
||||
let content = null
|
||||
|
||||
if (hasError) {
|
||||
capsuleClass += ' state-error'
|
||||
content = (
|
||||
<>
|
||||
<div className="icon-wrapper">
|
||||
<AlertCircle size={14} />
|
||||
</div>
|
||||
<div className="info-wrapper">
|
||||
<span className="error-text">更新失败: {updateError}</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
} else if (isDownloading) {
|
||||
capsuleClass += ' state-downloading'
|
||||
content = (
|
||||
<>
|
||||
<div className="icon-wrapper">
|
||||
<Download size={14} className="download-icon" />
|
||||
</div>
|
||||
<div className="info-wrapper">
|
||||
<span className="percent-text">{percent.toFixed(0)}%</span>
|
||||
{bytesPerSecond > 0 && (
|
||||
<span className="speed-text">{formatSpeed(bytesPerSecond)}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="progress-bg">
|
||||
<div className="progress-fill" style={{ width: `${percent}%` }} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
} else if (hasUpdate) {
|
||||
capsuleClass += ' state-available'
|
||||
content = (
|
||||
<>
|
||||
<div className="icon-wrapper">
|
||||
<Info size={14} />
|
||||
</div>
|
||||
<div className="info-wrapper">
|
||||
<span className="available-text">发现新版本 v{updateInfo?.version}</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={capsuleClass} onClick={() => setShowUpdateDialog(true)}>
|
||||
<div className="capsule-content">
|
||||
{content}
|
||||
{!isDownloading && (
|
||||
<button className="capsule-close" onClick={handleClose}>
|
||||
<X size={12} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateProgressCapsule
|
||||
Reference in New Issue
Block a user