Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/renderer/i18n/locales/en-us.json
Original file line number Diff line number Diff line change
Expand Up @@ -4434,7 +4434,7 @@
},
"generate_failed": "Failed to generate image",
"generated_image": "Generated Image",
"generating": "Generating...",
"generating": "Drawing in progress. Please do not leave this page.",
"go_to_settings": "Go to Settings",
"guidance_scale": "Guidance Scale",
"guidance_scale_tip": "Classifier Free Guidance ({{min}}-{{max}}). How close you want the model to stick to your prompt when looking for a related image to show you",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/i18n/locales/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -4434,7 +4434,7 @@
},
"generate_failed": "生成图像失败",
"generated_image": "生成图片",
"generating": "生成中...",
"generating": "绘图进行中,请不要离开页面",
"go_to_settings": "去设置",
"guidance_scale": "引导比例",
"guidance_scale_tip": "无分类器指导 ({{min}}-{{max}})。控制模型在寻找相关图像时对提示词的遵循程度",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/i18n/locales/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -4434,7 +4434,7 @@
},
"generate_failed": "無法生成圖片",
"generated_image": "產生的圖片",
"generating": "生成中...",
"generating": "繪圖進行中,請不要離開頁面",
"go_to_settings": "前往設定",
"guidance_scale": "引導比例",
"guidance_scale_tip": "無分類器指導 ({{min}}-{{max}})。控制模型在尋找相關影像時對提示詞的遵循程度",
Expand Down
34 changes: 16 additions & 18 deletions src/renderer/pages/paintings/components/Artboard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Button, Tooltip } from '@cherrystudio/ui'
import ImageViewer from '@renderer/components/ImageViewer'
import { ImageDown, ImageUp, RefreshCcw, RotateCcwSquare, RotateCwSquare, ZoomIn, ZoomOut } from 'lucide-react'
import { motion } from 'motion/react'
import {
type FC,
type PointerEvent,
Expand Down Expand Up @@ -40,27 +39,26 @@ export interface ArtboardProps {
loadText?: ReactNode
}

const LoadingStateCard: FC<{ text: ReactNode; onCancel: () => void; cancelLabel: string }> = ({
const InlineLoadingState: FC<{ text: ReactNode; onCancel: () => void; cancelLabel: string }> = ({
text,
onCancel,
cancelLabel
}) => {
Comment thread
kangfenmao marked this conversation as resolved.
const progressLabel = typeof text === 'string' ? text : undefined

return (
<div className="flex min-w-56 flex-col items-center gap-4 rounded-[18px] border border-border-subtle bg-card/96 px-10 py-10 shadow-2xl backdrop-blur-sm">
<div className="relative h-12 w-12">
<motion.div
className="absolute inset-0 rounded-full border-2 border-border"
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Number.POSITIVE_INFINITY, ease: 'linear' }}
/>
<motion.div
className="absolute inset-1 rounded-full border-2 border-primary border-r-transparent border-b-transparent"
animate={{ rotate: -360 }}
transition={{ duration: 1.5, repeat: Number.POSITIVE_INFINITY, ease: 'linear' }}
/>
<div
className="flex w-full max-w-90 flex-col items-center gap-3 rounded-md bg-card px-5 py-4 text-card-foreground"
role="status"
aria-live="polite">
<div className="text-center font-medium text-[13px] text-foreground leading-5">{text}</div>
<div
className="relative h-1.5 w-full overflow-hidden rounded-full bg-muted"
role="progressbar"
aria-label={progressLabel}>
<span className="animation-migration-backup-progress-indeterminate absolute inset-y-0 left-0 w-1/3 min-w-20 rounded-full bg-linear-to-r from-primary/0 via-primary to-primary/0" />
Comment thread
kangfenmao marked this conversation as resolved.
</div>
Comment thread
kangfenmao marked this conversation as resolved.
<div className="text-center font-medium text-[13px] text-foreground/85">{text}</div>
<Button variant="outline" size="sm" onClick={onCancel} className="mt-1 min-w-20">
<Button type="button" variant="outline" size="sm" onClick={onCancel} className="min-w-20">
{cancelLabel}
</Button>
</div>
Expand Down Expand Up @@ -253,8 +251,8 @@ const Artboard: FC<ArtboardProps> = ({ painting, isLoading, onCancel, imageCover
) : null}

{isLoading && (
<div className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 z-30">
<LoadingStateCard text={loadingText} onCancel={onCancel} cancelLabel={t('common.cancel')} />
<div className="-translate-y-1/2 absolute inset-x-4 top-1/2 z-30 flex justify-center">
<InlineLoadingState text={loadingText} onCancel={onCancel} cancelLabel={t('common.cancel')} />
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { beforeAll, describe, expect, it, vi } from 'vitest'
import type { PaintingData } from '../../model/types/paintingData'

vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string) => key })
useTranslation: () => ({
t: (key: string) => (key === 'paintings.generating' ? '绘图进行中,请不要离开页面' : key)
})
}))

vi.mock('@renderer/utils/image', () => ({
Expand Down Expand Up @@ -53,6 +55,31 @@ describe('Artboard', () => {
HTMLElement.prototype.hasPointerCapture ??= vi.fn(() => true)
})

it('renders an inline loading bar and cancel action while generating', () => {
const onCancel = vi.fn()

render(<Artboard painting={makePainting()} isLoading={true} onCancel={onCancel} />)

const loadingStatus = screen.getByRole('status')
expect(loadingStatus).toHaveTextContent('绘图进行中,请不要离开页面')
expect(loadingStatus.className).not.toContain('border')

Comment thread
kangfenmao marked this conversation as resolved.
const loadingBar = screen.getByRole('progressbar', { name: '绘图进行中,请不要离开页面' })
expect(loadingBar).toBeInTheDocument()
expect(loadingBar.firstElementChild).toHaveClass('animation-migration-backup-progress-indeterminate')
expect(loadingBar.firstElementChild).toHaveClass('bg-linear-to-r')

Comment thread
kangfenmao marked this conversation as resolved.
Comment thread
kangfenmao marked this conversation as resolved.
fireEvent.click(screen.getByRole('button', { name: 'common.cancel' }))

expect(onCancel).toHaveBeenCalledTimes(1)
})

it('does not render the loading bar when idle', () => {
render(<Artboard painting={makePainting()} isLoading={false} onCancel={vi.fn()} />)

expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
})

it('resets image transform when switching generated images', () => {
render(<Artboard painting={makePainting()} isLoading={false} onCancel={vi.fn()} />)

Expand Down
Loading