mirror of
https://github.com/ILoveBingLu/CipherTalk.git
synced 2026-05-15 08:28:52 +08:00
feat: 增强图片查看器功能,支持高清图升级及相关参数传递
This commit is contained in:
+21
-5
@@ -728,7 +728,11 @@ function createPurchaseWindow() {
|
||||
/**
|
||||
* 创建独立的图片查看窗口
|
||||
*/
|
||||
function createImageViewerWindow(imagePath: string, liveVideoPath?: string) {
|
||||
function createImageViewerWindow(
|
||||
imagePath: string,
|
||||
liveVideoPath?: string,
|
||||
options?: { sessionId?: string; imageMd5?: string; imageDatName?: string }
|
||||
) {
|
||||
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||
const iconPath = isDev
|
||||
? join(__dirname, '../public/icon.ico')
|
||||
@@ -764,7 +768,10 @@ function createImageViewerWindow(imagePath: string, liveVideoPath?: string) {
|
||||
const themeParams = getThemeQueryParams()
|
||||
const imageParam = `imagePath=${encodeURIComponent(imagePath)}`
|
||||
const liveVideoParam = liveVideoPath ? `&liveVideoPath=${encodeURIComponent(liveVideoPath)}` : ''
|
||||
const queryParams = `${themeParams}&${imageParam}${liveVideoParam}`
|
||||
const sessionParam = options?.sessionId ? `&sessionId=${encodeURIComponent(options.sessionId)}` : ''
|
||||
const imageMd5Param = options?.imageMd5 ? `&imageMd5=${encodeURIComponent(options.imageMd5)}` : ''
|
||||
const imageDatNameParam = options?.imageDatName ? `&imageDatName=${encodeURIComponent(options.imageDatName)}` : ''
|
||||
const queryParams = `${themeParams}&${imageParam}${liveVideoParam}${sessionParam}${imageMd5Param}${imageDatNameParam}`
|
||||
|
||||
if (process.env.VITE_DEV_SERVER_URL) {
|
||||
win.loadURL(`${process.env.VITE_DEV_SERVER_URL}#/image-viewer-window?${queryParams}`)
|
||||
@@ -1288,8 +1295,16 @@ function registerIpcHandlers() {
|
||||
})
|
||||
|
||||
// 打开图片查看窗口
|
||||
ipcMain.handle('window:openImageViewerWindow', (_, imagePath: string, liveVideoPath?: string, imageList?: Array<{ imagePath: string; liveVideoPath?: string }>) => {
|
||||
const win = createImageViewerWindow(imagePath, liveVideoPath)
|
||||
ipcMain.handle(
|
||||
'window:openImageViewerWindow',
|
||||
(
|
||||
_,
|
||||
imagePath: string,
|
||||
liveVideoPath?: string,
|
||||
imageList?: Array<{ imagePath: string; liveVideoPath?: string }>,
|
||||
options?: { sessionId?: string; imageMd5?: string; imageDatName?: string }
|
||||
) => {
|
||||
const win = createImageViewerWindow(imagePath, liveVideoPath, options)
|
||||
if (imageList && imageList.length > 1) {
|
||||
const currentIndex = imageList.findIndex(item => item.imagePath === imagePath)
|
||||
win.webContents.once('did-finish-load', () => {
|
||||
@@ -1301,7 +1316,8 @@ function registerIpcHandlers() {
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// 打开视频播放窗口
|
||||
ipcMain.handle('window:openVideoPlayerWindow', (_, videoPath: string, videoWidth?: number, videoHeight?: number) => {
|
||||
|
||||
+6
-1
@@ -88,7 +88,12 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
isChatWindowOpen: () => ipcRenderer.invoke('window:isChatWindowOpen'),
|
||||
closeChatWindow: () => ipcRenderer.invoke('window:closeChatWindow'),
|
||||
setTitleBarOverlay: (options: { symbolColor: string }) => ipcRenderer.send('window:setTitleBarOverlay', options),
|
||||
openImageViewerWindow: (imagePath: string, liveVideoPath?: string, imageList?: Array<{ imagePath: string; liveVideoPath?: string }>) => ipcRenderer.invoke('window:openImageViewerWindow', imagePath, liveVideoPath, imageList),
|
||||
openImageViewerWindow: (
|
||||
imagePath: string,
|
||||
liveVideoPath?: string,
|
||||
imageList?: Array<{ imagePath: string; liveVideoPath?: string }>,
|
||||
options?: { sessionId?: string; imageMd5?: string; imageDatName?: string }
|
||||
) => ipcRenderer.invoke('window:openImageViewerWindow', imagePath, liveVideoPath, imageList, options),
|
||||
openVideoPlayerWindow: (videoPath: string, videoWidth?: number, videoHeight?: number) => ipcRenderer.invoke('window:openVideoPlayerWindow', videoPath, videoWidth, videoHeight),
|
||||
openBrowserWindow: (url: string, title?: string) => ipcRenderer.invoke('window:openBrowserWindow', url, title),
|
||||
openAISummaryWindow: (sessionId: string, sessionName: string) => ipcRenderer.invoke('window:openAISummaryWindow', sessionId, sessionName),
|
||||
|
||||
@@ -197,6 +197,7 @@ export class ImageDecryptService {
|
||||
this.cacheResolvedPaths(cacheKey, payload.imageMd5, payload.imageDatName, datPath)
|
||||
const localPath = this.filePathToUrl(datPath)
|
||||
const isThumb = this.isThumbnailPath(datPath)
|
||||
this.emitCacheResolved(payload, cacheKey, localPath)
|
||||
return { success: true, localPath, isThumb, liveVideoPath: !isThumb ? this.checkLiveVideoCache(datPath) : undefined }
|
||||
}
|
||||
|
||||
@@ -209,6 +210,7 @@ export class ImageDecryptService {
|
||||
this.cacheResolvedPaths(cacheKey, payload.imageMd5, payload.imageDatName, existing)
|
||||
const localPath = this.filePathToUrl(existing)
|
||||
const isThumb = this.isThumbnailPath(existing)
|
||||
this.emitCacheResolved(payload, cacheKey, localPath)
|
||||
return { success: true, localPath, isThumb, liveVideoPath: !isThumb ? this.checkLiveVideoCache(existing) : undefined }
|
||||
}
|
||||
}
|
||||
@@ -276,6 +278,7 @@ export class ImageDecryptService {
|
||||
this.cacheResolvedPaths(cacheKey, payload.imageMd5, payload.imageDatName, outputPath)
|
||||
if (!isThumb) {
|
||||
this.clearUpdateFlags(cacheKey, payload.imageMd5, payload.imageDatName)
|
||||
this.deleteThumbnailByKeys(this.getCacheKeys(payload))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +293,7 @@ export class ImageDecryptService {
|
||||
}
|
||||
|
||||
const localPath = this.filePathToUrl(outputPath)
|
||||
this.emitCacheResolved(payload, cacheKey, localPath)
|
||||
|
||||
return { success: true, localPath, isThumb, liveVideoPath }
|
||||
} catch (e) {
|
||||
@@ -1328,6 +1332,65 @@ export class ImageDecryptService {
|
||||
if (imageDatName) this.updateFlags.delete(imageDatName)
|
||||
}
|
||||
|
||||
private deleteThumbnailByKeys(keys: string[]): number {
|
||||
if (keys.length === 0) return 0
|
||||
|
||||
const normalizedKeys = Array.from(new Set(
|
||||
keys
|
||||
.map(k => this.normalizeDatBase(k.toLowerCase()))
|
||||
.filter(Boolean)
|
||||
))
|
||||
if (normalizedKeys.length === 0) return 0
|
||||
|
||||
let deleted = 0
|
||||
const roots = this.getAllCacheRoots()
|
||||
|
||||
const isMatchThumbFile = (filePath: string): boolean => {
|
||||
const lower = filePath.toLowerCase()
|
||||
if (!this.isThumbnailPath(lower)) return false
|
||||
const baseName = basename(lower)
|
||||
return normalizedKeys.some(key => baseName.startsWith(`${key}_thumb.`))
|
||||
}
|
||||
|
||||
const walk = (dir: string) => {
|
||||
let entries: any[]
|
||||
try {
|
||||
entries = readdirSync(dir, { withFileTypes: true })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const full = join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
walk(full)
|
||||
} else if (isMatchThumbFile(full)) {
|
||||
try {
|
||||
unlinkSync(full)
|
||||
deleted++
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const root of roots) {
|
||||
if (existsSync(root)) {
|
||||
walk(root)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, resolvedPath] of this.resolvedCache.entries()) {
|
||||
if (!isMatchThumbFile(resolvedPath)) continue
|
||||
const lowerKey = key.toLowerCase()
|
||||
const normalizedKey = this.normalizeDatBase(lowerKey)
|
||||
if (normalizedKeys.includes(normalizedKey) || normalizedKeys.includes(lowerKey)) {
|
||||
this.resolvedCache.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
return deleted
|
||||
}
|
||||
|
||||
private getCachedDatDir(accountDir: string, imageDatName?: string, imageMd5?: string): string | null {
|
||||
const keys = [
|
||||
imageDatName ? `${accountDir}|${imageDatName}` : null,
|
||||
|
||||
+153
-7
@@ -2621,6 +2621,8 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h
|
||||
const [imageClicked, setImageClicked] = useState(false)
|
||||
const imageUpdateCheckedRef = useRef<string | null>(null)
|
||||
const imageClickTimerRef = useRef<number | null>(null)
|
||||
const imageRecoveringRef = useRef(false)
|
||||
const lastRecoverTriedPathRef = useRef<string | null>(null)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const imageContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
@@ -2784,7 +2786,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h
|
||||
})
|
||||
},
|
||||
{
|
||||
rootMargin: '200px 0px', // 提前 200px 开始加载
|
||||
rootMargin: '1200px 0px', // 提前加载,减少滚动到位后的等待
|
||||
threshold: 0
|
||||
}
|
||||
)
|
||||
@@ -3230,6 +3232,137 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h
|
||||
}
|
||||
}, [isImage, message.imageMd5, message.imageDatName, isVisible, imageCacheKey, imageLocalPath, session.username, syncVersion])
|
||||
|
||||
// 若已显示缩略图且检测到高清图可用,循环尝试升级(防止首轮时机过早)
|
||||
useEffect(() => {
|
||||
if (!isImage) return
|
||||
if (!isVisible) return
|
||||
if (!imageLocalPath) return
|
||||
if (!imageLocalPath.toLowerCase().includes('_thumb')) return
|
||||
if (!imageHasUpdate) return
|
||||
const timer = window.setInterval(() => {
|
||||
if (!imageLoading) {
|
||||
void requestImageDecrypt(true)
|
||||
}
|
||||
}, 6000)
|
||||
|
||||
if (!imageLoading) {
|
||||
void requestImageDecrypt(true)
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.clearInterval(timer)
|
||||
}
|
||||
}, [isImage, isVisible, imageLocalPath, imageHasUpdate, imageLoading, requestImageDecrypt])
|
||||
|
||||
const handleOpenImage = useCallback(async () => {
|
||||
if (!imageLocalPath) return
|
||||
|
||||
let openPath = imageLocalPath
|
||||
let openLiveVideoPath = imageLiveVideoPath
|
||||
|
||||
if (imageHasUpdate && !imageLoading) {
|
||||
try {
|
||||
const result = await window.electronAPI.image.decrypt({
|
||||
sessionId: session.username,
|
||||
imageMd5: message.imageMd5 || undefined,
|
||||
imageDatName: message.imageDatName,
|
||||
force: true
|
||||
})
|
||||
if (result.success && result.localPath) {
|
||||
imageDataUrlCache.set(imageCacheKey, result.localPath)
|
||||
setImageLocalPath(result.localPath)
|
||||
setImageHasUpdate(Boolean((result as { isThumb?: boolean }).isThumb))
|
||||
openPath = result.localPath
|
||||
if ((result as any).liveVideoPath) {
|
||||
setImageLiveVideoPath((result as any).liveVideoPath)
|
||||
openLiveVideoPath = (result as any).liveVideoPath
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore and fallback to current path
|
||||
}
|
||||
}
|
||||
|
||||
window.electronAPI.window.openImageViewerWindow(openPath, openLiveVideoPath, undefined, {
|
||||
sessionId: session.username,
|
||||
imageMd5: message.imageMd5 || undefined,
|
||||
imageDatName: message.imageDatName
|
||||
})
|
||||
}, [
|
||||
imageLocalPath,
|
||||
imageLiveVideoPath,
|
||||
imageHasUpdate,
|
||||
imageLoading,
|
||||
session.username,
|
||||
message.imageMd5,
|
||||
message.imageDatName,
|
||||
imageCacheKey,
|
||||
])
|
||||
|
||||
const recoverBrokenImagePath = useCallback(async () => {
|
||||
if (!isImage) return
|
||||
if ((!message.imageMd5 && !message.imageDatName) || !session.username) return
|
||||
if (imageRecoveringRef.current) return
|
||||
|
||||
const failedPath = imageLocalPath || '__empty__'
|
||||
if (lastRecoverTriedPathRef.current === failedPath && !imageHasUpdate) {
|
||||
return
|
||||
}
|
||||
lastRecoverTriedPathRef.current = failedPath
|
||||
imageRecoveringRef.current = true
|
||||
setImageLoading(true)
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
sessionId: session.username,
|
||||
imageMd5: message.imageMd5 || undefined,
|
||||
imageDatName: message.imageDatName
|
||||
}
|
||||
|
||||
try {
|
||||
const cached = await window.electronAPI.image.resolveCache(payload)
|
||||
if (cached.success && cached.localPath && cached.localPath !== imageLocalPath) {
|
||||
imageDataUrlCache.set(imageCacheKey, cached.localPath)
|
||||
setImageLocalPath(cached.localPath)
|
||||
setImageHasUpdate(cached.localPath.toLowerCase().includes('_thumb'))
|
||||
setImageError(false)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// continue to force decrypt
|
||||
}
|
||||
|
||||
try {
|
||||
const refreshed = await window.electronAPI.image.decrypt({ ...payload, force: true })
|
||||
if (refreshed.success && refreshed.localPath) {
|
||||
imageDataUrlCache.set(imageCacheKey, refreshed.localPath)
|
||||
setImageLocalPath(refreshed.localPath)
|
||||
setImageHasUpdate(Boolean((refreshed as { isThumb?: boolean }).isThumb))
|
||||
if ((refreshed as any).liveVideoPath) {
|
||||
setImageLiveVideoPath((refreshed as any).liveVideoPath)
|
||||
}
|
||||
setImageError(false)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// keep error state
|
||||
}
|
||||
|
||||
setImageError(true)
|
||||
} finally {
|
||||
setImageLoading(false)
|
||||
imageRecoveringRef.current = false
|
||||
}
|
||||
}, [
|
||||
isImage,
|
||||
message.imageMd5,
|
||||
message.imageDatName,
|
||||
session.username,
|
||||
imageLocalPath,
|
||||
imageHasUpdate,
|
||||
imageCacheKey
|
||||
])
|
||||
|
||||
// 自动检查转写缓存
|
||||
useEffect(() => {
|
||||
if (!isVoice || sttTranscript || sttLoading) return
|
||||
@@ -3277,6 +3410,7 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h
|
||||
if (matchesCacheKey) {
|
||||
imageDataUrlCache.set(imageCacheKey, payload.localPath)
|
||||
setImageLocalPath(payload.localPath)
|
||||
setImageHasUpdate(payload.localPath.toLowerCase().includes('_thumb'))
|
||||
setImageError(false)
|
||||
}
|
||||
})
|
||||
@@ -3436,14 +3570,26 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, h
|
||||
src={imageLocalPath}
|
||||
alt="图片"
|
||||
className="image-message"
|
||||
onClick={() => {
|
||||
if (imageLocalPath) {
|
||||
window.electronAPI.window.openImageViewerWindow(imageLocalPath, imageLiveVideoPath)
|
||||
}
|
||||
}}
|
||||
onClick={() => { void handleOpenImage() }}
|
||||
onLoad={() => setImageError(false)}
|
||||
onError={() => setImageError(true)}
|
||||
onError={() => {
|
||||
setImageError(true)
|
||||
void recoverBrokenImagePath()
|
||||
}}
|
||||
/>
|
||||
{imageHasUpdate && (
|
||||
<button
|
||||
type="button"
|
||||
className="image-update-button"
|
||||
title="检测到高清图,点击更新"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
void requestImageDecrypt(true)
|
||||
}}
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
</button>
|
||||
)}
|
||||
{imageLiveVideoPath && (
|
||||
<div className="media-badge live">
|
||||
<LivePhotoIcon size={14} />
|
||||
|
||||
@@ -9,6 +9,9 @@ export default function ImageWindow() {
|
||||
const [searchParams] = useSearchParams()
|
||||
const imagePath = searchParams.get('imagePath')
|
||||
const liveVideoPath = searchParams.get('liveVideoPath')
|
||||
const sessionId = searchParams.get('sessionId') || undefined
|
||||
const imageMd5 = searchParams.get('imageMd5') || undefined
|
||||
const imageDatName = searchParams.get('imageDatName') || undefined
|
||||
|
||||
// 图片列表导航状态
|
||||
const [imageList, setImageList] = useState<Array<{ imagePath: string; liveVideoPath?: string }>>([])
|
||||
@@ -18,6 +21,11 @@ export default function ImageWindow() {
|
||||
const currentImagePath = activeImage?.imagePath || imagePath
|
||||
// 多图模式下只用列表中的 liveVideoPath,不回退到 URL 参数,避免非实况图也显示实况按钮
|
||||
const currentLiveVideoPath = imageList.length > 0 ? activeImage?.liveVideoPath : liveVideoPath
|
||||
const [hdImagePath, setHdImagePath] = useState<string | null>(null)
|
||||
const [hdLiveVideoPath, setHdLiveVideoPath] = useState<string | undefined>(undefined)
|
||||
const upgradeTriedRef = useRef<string | null>(null)
|
||||
const effectiveImagePath = hdImagePath || currentImagePath
|
||||
const effectiveLiveVideoPath = hdLiveVideoPath ?? currentLiveVideoPath
|
||||
|
||||
const [scale, setScale] = useState(1)
|
||||
const [rotation, setRotation] = useState(0)
|
||||
@@ -42,6 +50,45 @@ export default function ImageWindow() {
|
||||
const [naturalSize, setNaturalSize] = useState({ width: 0, height: 0 })
|
||||
const [viewportSize, setViewportSize] = useState({ width: 0, height: 0 })
|
||||
|
||||
useEffect(() => {
|
||||
setHdImagePath(null)
|
||||
setHdLiveVideoPath(undefined)
|
||||
upgradeTriedRef.current = null
|
||||
}, [currentImagePath])
|
||||
|
||||
// 在图片查看器中再次尝试强制升级高清图
|
||||
useEffect(() => {
|
||||
if (!currentImagePath) return
|
||||
if (!sessionId) return
|
||||
if (!imageMd5 && !imageDatName) return
|
||||
|
||||
const upgradeKey = `${sessionId}|${imageMd5 || ''}|${imageDatName || ''}`
|
||||
if (upgradeTriedRef.current === upgradeKey) return
|
||||
upgradeTriedRef.current = upgradeKey
|
||||
|
||||
let cancelled = false
|
||||
window.electronAPI.image.decrypt({
|
||||
sessionId,
|
||||
imageMd5,
|
||||
imageDatName,
|
||||
force: true
|
||||
}).then((result) => {
|
||||
if (cancelled) return
|
||||
if (result.success && result.localPath) {
|
||||
setHdImagePath(result.localPath)
|
||||
if ((result as any).liveVideoPath) {
|
||||
setHdLiveVideoPath((result as any).liveVideoPath)
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
// ignore
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [currentImagePath, sessionId, imageMd5, imageDatName])
|
||||
|
||||
const handleZoomIn = () => setScale(prev => Math.min(prev + 0.25, 10))
|
||||
const handleZoomOut = () => setScale(prev => Math.max(prev - 0.25, 0.1))
|
||||
const handleRotate = () => setRotation(prev => (prev + 90) % 360)
|
||||
@@ -58,7 +105,7 @@ export default function ImageWindow() {
|
||||
|
||||
// 播放 Live Photo
|
||||
const handlePlayLiveVideo = useCallback(() => {
|
||||
if (currentLiveVideoPath && !isPlayingLive) {
|
||||
if (effectiveLiveVideoPath && !isPlayingLive) {
|
||||
setIsPlayingLive(true)
|
||||
// 播放视频
|
||||
if (videoRef.current) {
|
||||
@@ -66,7 +113,7 @@ export default function ImageWindow() {
|
||||
videoRef.current.play()
|
||||
}
|
||||
}
|
||||
}, [currentLiveVideoPath, isPlayingLive])
|
||||
}, [effectiveLiveVideoPath, isPlayingLive])
|
||||
|
||||
// 视频真正开始播放(画面就绪)
|
||||
const handleVideoPlaying = useCallback(() => {
|
||||
@@ -353,7 +400,7 @@ export default function ImageWindow() {
|
||||
if (e.key === '-') handleZoomOut()
|
||||
if (e.key === 'r' || e.key === 'R') handleRotate()
|
||||
if (e.key === '0') handleReset()
|
||||
if (e.key === ' ' && currentLiveVideoPath) {
|
||||
if (e.key === ' ' && effectiveLiveVideoPath) {
|
||||
e.preventDefault()
|
||||
handlePlayLiveVideo()
|
||||
}
|
||||
@@ -362,11 +409,11 @@ export default function ImageWindow() {
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||
}, [handleReset, currentLiveVideoPath, isPlayingLive, handlePlayLiveVideo, goPrev, goNext])
|
||||
}, [handleReset, effectiveLiveVideoPath, isPlayingLive, handlePlayLiveVideo, goPrev, goNext])
|
||||
|
||||
const hasLiveVideo = !!currentLiveVideoPath
|
||||
const hasLiveVideo = !!effectiveLiveVideoPath
|
||||
|
||||
if (!currentImagePath) {
|
||||
if (!effectiveImagePath) {
|
||||
return (
|
||||
<div className="image-window-empty">
|
||||
<span>无效的图片路径</span>
|
||||
@@ -431,7 +478,7 @@ export default function ImageWindow() {
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={currentImagePath}
|
||||
src={effectiveImagePath}
|
||||
alt="Preview"
|
||||
className={isPannable ? 'pannable' : ''}
|
||||
onLoad={handleImageLoad}
|
||||
@@ -441,7 +488,7 @@ export default function ImageWindow() {
|
||||
{hasLiveVideo && isPlayingLive && (
|
||||
<video
|
||||
ref={videoRef}
|
||||
src={currentLiveVideoPath || ''}
|
||||
src={effectiveLiveVideoPath || ''}
|
||||
className={`live-video ${isVideoVisible ? 'visible' : ''}`}
|
||||
autoPlay
|
||||
// muted={false} // Default is unmuted, explicit false for clarity
|
||||
|
||||
Vendored
+12
-1
@@ -6,6 +6,12 @@ export interface ImageListItem {
|
||||
liveVideoPath?: string
|
||||
}
|
||||
|
||||
export interface ImageViewerOpenOptions {
|
||||
sessionId?: string
|
||||
imageMd5?: string
|
||||
imageDatName?: string
|
||||
}
|
||||
|
||||
export interface ElectronAPI {
|
||||
window: {
|
||||
minimize: () => void
|
||||
@@ -25,7 +31,12 @@ export interface ElectronAPI {
|
||||
isChatWindowOpen: () => Promise<boolean>
|
||||
closeChatWindow: () => Promise<boolean>
|
||||
setTitleBarOverlay: (options: { symbolColor: string }) => void
|
||||
openImageViewerWindow: (imagePath: string, liveVideoPath?: string, imageList?: ImageListItem[]) => Promise<void>
|
||||
openImageViewerWindow: (
|
||||
imagePath: string,
|
||||
liveVideoPath?: string,
|
||||
imageList?: ImageListItem[],
|
||||
options?: ImageViewerOpenOptions
|
||||
) => Promise<void>
|
||||
openVideoPlayerWindow: (videoPath: string, videoWidth?: number, videoHeight?: number) => Promise<void>
|
||||
openBrowserWindow: (url: string, title?: string) => Promise<void>
|
||||
resizeToFitVideo: (videoWidth: number, videoHeight: number) => Promise<void>
|
||||
|
||||
Reference in New Issue
Block a user