mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
给密语的图片查看器搬过来了
This commit is contained in:
@@ -370,6 +370,66 @@ function createVideoPlayerWindow(videoPath: string, videoWidth?: number, videoHe
|
|||||||
return win
|
return win
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建独立的图片查看窗口
|
||||||
|
*/
|
||||||
|
function createImageViewerWindow(imagePath: string) {
|
||||||
|
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
||||||
|
const iconPath = isDev
|
||||||
|
? join(__dirname, '../public/icon.ico')
|
||||||
|
: join(process.resourcesPath, 'icon.ico')
|
||||||
|
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 900,
|
||||||
|
height: 700,
|
||||||
|
minWidth: 400,
|
||||||
|
minHeight: 300,
|
||||||
|
icon: iconPath,
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, 'preload.js'),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
webSecurity: false // 允许加载本地文件
|
||||||
|
},
|
||||||
|
titleBarStyle: 'hidden',
|
||||||
|
titleBarOverlay: {
|
||||||
|
color: '#00000000',
|
||||||
|
symbolColor: '#ffffff',
|
||||||
|
height: 40
|
||||||
|
},
|
||||||
|
show: false,
|
||||||
|
backgroundColor: '#000000',
|
||||||
|
autoHideMenuBar: true
|
||||||
|
})
|
||||||
|
|
||||||
|
win.once('ready-to-show', () => {
|
||||||
|
win.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
const imageParam = `imagePath=${encodeURIComponent(imagePath)}`
|
||||||
|
|
||||||
|
if (process.env.VITE_DEV_SERVER_URL) {
|
||||||
|
win.loadURL(`${process.env.VITE_DEV_SERVER_URL}#/image-viewer-window?${imageParam}`)
|
||||||
|
|
||||||
|
win.webContents.on('before-input-event', (event, input) => {
|
||||||
|
if (input.key === 'F12' || (input.control && input.shift && input.key === 'I')) {
|
||||||
|
if (win.webContents.isDevToolsOpened()) {
|
||||||
|
win.webContents.closeDevTools()
|
||||||
|
} else {
|
||||||
|
win.webContents.openDevTools()
|
||||||
|
}
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
win.loadFile(join(__dirname, '../dist/index.html'), {
|
||||||
|
hash: `/image-viewer-window?${imageParam}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return win
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建独立的聊天记录窗口
|
* 创建独立的聊天记录窗口
|
||||||
*/
|
*/
|
||||||
@@ -941,6 +1001,11 @@ function registerIpcHandlers() {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 打开图片查看窗口
|
||||||
|
ipcMain.handle('window:openImageViewerWindow', (_, imagePath: string) => {
|
||||||
|
createImageViewerWindow(imagePath)
|
||||||
|
})
|
||||||
|
|
||||||
// 完成引导,关闭引导窗口并显示主窗口
|
// 完成引导,关闭引导窗口并显示主窗口
|
||||||
ipcMain.handle('window:completeOnboarding', async () => {
|
ipcMain.handle('window:completeOnboarding', async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
ipcRenderer.invoke('window:openVideoPlayerWindow', videoPath, videoWidth, videoHeight),
|
ipcRenderer.invoke('window:openVideoPlayerWindow', videoPath, videoWidth, videoHeight),
|
||||||
resizeToFitVideo: (videoWidth: number, videoHeight: number) =>
|
resizeToFitVideo: (videoWidth: number, videoHeight: number) =>
|
||||||
ipcRenderer.invoke('window:resizeToFitVideo', videoWidth, videoHeight),
|
ipcRenderer.invoke('window:resizeToFitVideo', videoWidth, videoHeight),
|
||||||
|
openImageViewerWindow: (imagePath: string) =>
|
||||||
|
ipcRenderer.invoke('window:openImageViewerWindow', imagePath),
|
||||||
openChatHistoryWindow: (sessionId: string, messageId: number) =>
|
openChatHistoryWindow: (sessionId: string, messageId: number) =>
|
||||||
ipcRenderer.invoke('window:openChatHistoryWindow', sessionId, messageId)
|
ipcRenderer.invoke('window:openChatHistoryWindow', sessionId, messageId)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import GroupAnalyticsPage from './pages/GroupAnalyticsPage'
|
|||||||
import SettingsPage from './pages/SettingsPage'
|
import SettingsPage from './pages/SettingsPage'
|
||||||
import ExportPage from './pages/ExportPage'
|
import ExportPage from './pages/ExportPage'
|
||||||
import VideoWindow from './pages/VideoWindow'
|
import VideoWindow from './pages/VideoWindow'
|
||||||
|
import ImageWindow from './pages/ImageWindow'
|
||||||
import SnsPage from './pages/SnsPage'
|
import SnsPage from './pages/SnsPage'
|
||||||
import ContactsPage from './pages/ContactsPage'
|
import ContactsPage from './pages/ContactsPage'
|
||||||
import ChatHistoryPage from './pages/ChatHistoryPage'
|
import ChatHistoryPage from './pages/ChatHistoryPage'
|
||||||
@@ -318,6 +319,12 @@ function App() {
|
|||||||
return <VideoWindow />
|
return <VideoWindow />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 独立图片查看窗口
|
||||||
|
const isImageViewerWindow = location.pathname === '/image-viewer-window'
|
||||||
|
if (isImageViewerWindow) {
|
||||||
|
return <ImageWindow />
|
||||||
|
}
|
||||||
|
|
||||||
// 独立聊天记录窗口
|
// 独立聊天记录窗口
|
||||||
if (isChatHistoryWindow) {
|
if (isChatHistoryWindow) {
|
||||||
return <ChatHistoryPage />
|
return <ChatHistoryPage />
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { createPortal } from 'react-dom'
|
|||||||
import { useChatStore } from '../stores/chatStore'
|
import { useChatStore } from '../stores/chatStore'
|
||||||
import type { ChatSession, Message } from '../types/models'
|
import type { ChatSession, Message } from '../types/models'
|
||||||
import { getEmojiPath } from 'wechat-emojis'
|
import { getEmojiPath } from 'wechat-emojis'
|
||||||
import { ImagePreview } from '../components/ImagePreview'
|
|
||||||
import { VoiceTranscribeDialog } from '../components/VoiceTranscribeDialog'
|
import { VoiceTranscribeDialog } from '../components/VoiceTranscribeDialog'
|
||||||
import { AnimatedStreamingText } from '../components/AnimatedStreamingText'
|
import { AnimatedStreamingText } from '../components/AnimatedStreamingText'
|
||||||
import JumpToDateDialog from '../components/JumpToDateDialog'
|
import JumpToDateDialog from '../components/JumpToDateDialog'
|
||||||
@@ -1630,7 +1629,6 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
|||||||
const [voiceTranscriptLoading, setVoiceTranscriptLoading] = useState(false)
|
const [voiceTranscriptLoading, setVoiceTranscriptLoading] = useState(false)
|
||||||
const [voiceTranscriptError, setVoiceTranscriptError] = useState(false)
|
const [voiceTranscriptError, setVoiceTranscriptError] = useState(false)
|
||||||
const voiceTranscriptRequestedRef = useRef(false)
|
const voiceTranscriptRequestedRef = useRef(false)
|
||||||
const [showImagePreview, setShowImagePreview] = useState(false)
|
|
||||||
const [autoTranscribeVoice, setAutoTranscribeVoice] = useState(true)
|
const [autoTranscribeVoice, setAutoTranscribeVoice] = useState(true)
|
||||||
const [voiceCurrentTime, setVoiceCurrentTime] = useState(0)
|
const [voiceCurrentTime, setVoiceCurrentTime] = useState(0)
|
||||||
const [voiceDuration, setVoiceDuration] = useState(0)
|
const [voiceDuration, setVoiceDuration] = useState(0)
|
||||||
@@ -1996,11 +1994,11 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
|||||||
}, [isImage, imageHasUpdate, imageInView, imageCacheKey, triggerForceHd])
|
}, [isImage, imageHasUpdate, imageInView, imageCacheKey, triggerForceHd])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isImage || !showImagePreview || !imageHasUpdate) return
|
if (!isImage || !imageHasUpdate) return
|
||||||
if (imageAutoHdTriggered.current === imageCacheKey) return
|
if (imageAutoHdTriggered.current === imageCacheKey) return
|
||||||
imageAutoHdTriggered.current = imageCacheKey
|
imageAutoHdTriggered.current = imageCacheKey
|
||||||
triggerForceHd()
|
triggerForceHd()
|
||||||
}, [isImage, showImagePreview, imageHasUpdate, imageCacheKey, triggerForceHd])
|
}, [isImage, imageHasUpdate, imageCacheKey, triggerForceHd])
|
||||||
|
|
||||||
// 更激进:进入视野/打开预览时,无论 hasUpdate 与否都尝试强制高清
|
// 更激进:进入视野/打开预览时,无论 hasUpdate 与否都尝试强制高清
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -2008,11 +2006,6 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
|||||||
triggerForceHd()
|
triggerForceHd()
|
||||||
}, [isImage, imageInView, triggerForceHd])
|
}, [isImage, imageInView, triggerForceHd])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isImage || !showImagePreview) return
|
|
||||||
triggerForceHd()
|
|
||||||
}, [isImage, showImagePreview, triggerForceHd])
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isVoice) return
|
if (!isVoice) return
|
||||||
@@ -2363,15 +2356,12 @@ function MessageBubble({ message, session, showTime, myAvatarUrl, isGroupChat, o
|
|||||||
if (imageHasUpdate) {
|
if (imageHasUpdate) {
|
||||||
void requestImageDecrypt(true, true)
|
void requestImageDecrypt(true, true)
|
||||||
}
|
}
|
||||||
setShowImagePreview(true)
|
void window.electronAPI.window.openImageViewerWindow(imageLocalPath)
|
||||||
}}
|
}}
|
||||||
onLoad={() => setImageError(false)}
|
onLoad={() => setImageError(false)}
|
||||||
onError={() => setImageError(true)}
|
onError={() => setImageError(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showImagePreview && (
|
|
||||||
<ImagePreview src={imageLocalPath} onClose={() => setShowImagePreview(false)} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
99
src/pages/ImageWindow.scss
Normal file
99
src/pages/ImageWindow.scss
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
.image-window-container {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.title-bar {
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
padding-right: 140px; // 为原生窗口控件留出空间
|
||||||
|
|
||||||
|
.window-drag-area {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-bar-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
margin-right: 16px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-text {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 12px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
background: var(--border-color);
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-viewport {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
cursor: grab;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: none;
|
||||||
|
max-height: none;
|
||||||
|
object-fit: contain;
|
||||||
|
will-change: transform;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-window-empty {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
}
|
||||||
162
src/pages/ImageWindow.tsx
Normal file
162
src/pages/ImageWindow.tsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
|
||||||
|
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
|
import { ZoomIn, ZoomOut, RotateCw, RotateCcw } from 'lucide-react'
|
||||||
|
import './ImageWindow.scss'
|
||||||
|
|
||||||
|
export default function ImageWindow() {
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const imagePath = searchParams.get('imagePath')
|
||||||
|
const [scale, setScale] = useState(1)
|
||||||
|
const [rotation, setRotation] = useState(0)
|
||||||
|
const [position, setPosition] = useState({ x: 0, y: 0 })
|
||||||
|
const [initialScale, setInitialScale] = useState(1)
|
||||||
|
const viewportRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
// 使用 ref 存储拖动状态,避免闭包问题
|
||||||
|
const dragStateRef = useRef({
|
||||||
|
isDragging: false,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
startPosX: 0,
|
||||||
|
startPosY: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
const handleRotateCcw = () => setRotation(prev => (prev - 90 + 360) % 360)
|
||||||
|
|
||||||
|
// 重置视图
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
setScale(1)
|
||||||
|
setRotation(0)
|
||||||
|
setPosition({ x: 0, y: 0 })
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 图片加载完成后计算初始缩放
|
||||||
|
const handleImageLoad = useCallback((e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||||
|
const img = e.currentTarget
|
||||||
|
const naturalWidth = img.naturalWidth
|
||||||
|
const naturalHeight = img.naturalHeight
|
||||||
|
|
||||||
|
if (viewportRef.current) {
|
||||||
|
const viewportWidth = viewportRef.current.clientWidth * 0.9
|
||||||
|
const viewportHeight = viewportRef.current.clientHeight * 0.9
|
||||||
|
const scaleX = viewportWidth / naturalWidth
|
||||||
|
const scaleY = viewportHeight / naturalHeight
|
||||||
|
const fitScale = Math.min(scaleX, scaleY, 1)
|
||||||
|
setInitialScale(fitScale)
|
||||||
|
setScale(1)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 使用原生事件监听器处理拖动
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!dragStateRef.current.isDragging) return
|
||||||
|
|
||||||
|
const dx = e.clientX - dragStateRef.current.startX
|
||||||
|
const dy = e.clientY - dragStateRef.current.startY
|
||||||
|
|
||||||
|
setPosition({
|
||||||
|
x: dragStateRef.current.startPosX + dx,
|
||||||
|
y: dragStateRef.current.startPosY + dy
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
dragStateRef.current.isDragging = false
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
document.addEventListener('mouseup', handleMouseUp)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleMouseDown = (e: React.MouseEvent) => {
|
||||||
|
if (e.button !== 0) return
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
dragStateRef.current = {
|
||||||
|
isDragging: true,
|
||||||
|
startX: e.clientX,
|
||||||
|
startY: e.clientY,
|
||||||
|
startPosX: position.x,
|
||||||
|
startPosY: position.y
|
||||||
|
}
|
||||||
|
document.body.style.cursor = 'grabbing'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWheel = useCallback((e: React.WheelEvent) => {
|
||||||
|
const delta = -Math.sign(e.deltaY) * 0.15
|
||||||
|
setScale(prev => Math.min(Math.max(prev + delta, 0.1), 10))
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 双击重置
|
||||||
|
const handleDoubleClick = useCallback(() => {
|
||||||
|
handleReset()
|
||||||
|
}, [handleReset])
|
||||||
|
|
||||||
|
// 快捷键支持
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') window.electronAPI.window.close()
|
||||||
|
if (e.key === '=' || e.key === '+') handleZoomIn()
|
||||||
|
if (e.key === '-') handleZoomOut()
|
||||||
|
if (e.key === 'r' || e.key === 'R') handleRotate()
|
||||||
|
if (e.key === '0') handleReset()
|
||||||
|
}
|
||||||
|
window.addEventListener('keydown', handleKeyDown)
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
||||||
|
}, [handleReset])
|
||||||
|
|
||||||
|
if (!imagePath) {
|
||||||
|
return (
|
||||||
|
<div className="image-window-empty">
|
||||||
|
<span>无效的图片路径</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayScale = initialScale * scale
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="image-window-container">
|
||||||
|
<div className="title-bar">
|
||||||
|
<div className="window-drag-area"></div>
|
||||||
|
<div className="title-bar-controls">
|
||||||
|
<button onClick={handleZoomOut} title="缩小 (-)"><ZoomOut size={16} /></button>
|
||||||
|
<span className="scale-text">{Math.round(displayScale * 100)}%</span>
|
||||||
|
<button onClick={handleZoomIn} title="放大 (+)"><ZoomIn size={16} /></button>
|
||||||
|
<div className="divider"></div>
|
||||||
|
<button onClick={handleRotateCcw} title="逆时针旋转"><RotateCcw size={16} /></button>
|
||||||
|
<button onClick={handleRotate} title="顺时针旋转 (R)"><RotateCw size={16} /></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="image-viewport"
|
||||||
|
ref={viewportRef}
|
||||||
|
onWheel={handleWheel}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={imagePath}
|
||||||
|
alt="Preview"
|
||||||
|
style={{
|
||||||
|
transform: `translate(${position.x}px, ${position.y}px) scale(${displayScale}) rotate(${rotation}deg)`
|
||||||
|
}}
|
||||||
|
onLoad={handleImageLoad}
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
1
src/types/electron.d.ts
vendored
1
src/types/electron.d.ts
vendored
@@ -11,6 +11,7 @@ export interface ElectronAPI {
|
|||||||
setTitleBarOverlay: (options: { symbolColor: string }) => void
|
setTitleBarOverlay: (options: { symbolColor: string }) => void
|
||||||
openVideoPlayerWindow: (videoPath: string, videoWidth?: number, videoHeight?: number) => Promise<void>
|
openVideoPlayerWindow: (videoPath: string, videoWidth?: number, videoHeight?: number) => Promise<void>
|
||||||
resizeToFitVideo: (videoWidth: number, videoHeight: number) => Promise<void>
|
resizeToFitVideo: (videoWidth: number, videoHeight: number) => Promise<void>
|
||||||
|
openImageViewerWindow: (imagePath: string) => Promise<void>
|
||||||
openChatHistoryWindow: (sessionId: string, messageId: number) => Promise<boolean>
|
openChatHistoryWindow: (sessionId: string, messageId: number) => Promise<boolean>
|
||||||
}
|
}
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
Reference in New Issue
Block a user