web/front/composables/usePythonBridge.ts

131 lines
3.6 KiB
TypeScript

// composables/usePythonBridge.ts
declare global {
interface Window {
QWebChannel?: typeof QWebChannel
qt?: {
webChannelTransport?: any
}
}
const QWebChannel: {
new(transport: any, ready: (channel: any) => void): void
}
}
export function usePythonBridge() {
const pyjs = useState<any>('bridge_pyjs', () => null)
const isReady = useState<boolean>('bridge_ready', () => false)
const isAvailableOnPlatform = useState<boolean>('bridge_av', () => false)
const loading = useState<boolean>('bridge_loading', () => false)
// Хранилище обработчиков событий
const pythonEventHandlers = new Map<string, Array<(data: any) => void>>()
function receiveFromPython(eventType: string, data: any) {
console.warn("🟢 Event from Python:", eventType, data)
const handlers = pythonEventHandlers.get(eventType) || []
handlers.forEach(handler => handler(data))
}
function setupQWebChannel() {
if (window.QWebChannel && window.qt && window.qt.webChannelTransport) {
new window.QWebChannel(window.qt.webChannelTransport, function (channel) {
isAvailableOnPlatform.value = true
isReady.value = true
pyjs.value = channel.objects.pyjs
// Получаем доступ к эмиттеру событий
const eventEmitter = channel.objects.pyjs_events
// Подписываемся на событие один раз
if (eventEmitter && eventEmitter.onEvent) {
eventEmitter.onEvent.connect(receiveFromPython)
}
})
} else {
isAvailableOnPlatform.value = false
console.error('Qt WebChannel недоступен')
}
}
async function sendCommandToPython<T = any>(
command: string,
data: Record<string, any> = {}
): Promise<T> {
if (!isReady.value) {
throw new Error('Мост еще не готов')
}
if (!pyjs.value || !pyjs.value.callFromJS) {
throw new Error('Python недоступен')
}
loading.value = true
return new Promise<T>((resolve, reject) => {
setTimeout(() => {
try {
pyjs.value.callFromJS(command, JSON.stringify(data), (result: string) => {
try {
const parsed = JSON.parse(result)
resolve(parsed)
} catch (e) {
reject(e)
} finally {
loading.value = false
}
})
} catch (err) {
reject(err)
loading.value = false
}
}, 300)
})
}
// Регистрация обработчика событий по типу
function onPythonEvent(eventType: string, handler: (data: any) => void) {
if (!pythonEventHandlers.has(eventType)) {
pythonEventHandlers.set(eventType, [])
}
pythonEventHandlers.get(eventType)?.push(handler)
}
// Отписка от события
function offPythonEvent(eventType: string, handler: (data: any) => void) {
const handlers = pythonEventHandlers.get(eventType)
if (handlers) {
const index = handlers.indexOf(handler)
if (index > -1) {
handlers.splice(index, 1)
}
}
}
const checkBridge = () => {
if (import.meta.client && window.QWebChannel && window.qt?.webChannelTransport) {
setupQWebChannel()
}
if (pyjs.value) {
console.warn('✅ pyjs готов')
} else {
setTimeout(checkBridge, 500)
}
}
// Вызываем один раз при первом монтировании
if (!isReady.value) {
checkBridge()
}
return {
pyjs,
isReady,
isAvailableOnPlatform,
loading,
sendCommandToPython,
onPythonEvent,
offPythonEvent,
}
}