feat: support message from webui extension

main
Yu Zhu 2023-04-02 00:08:29 +08:00
parent 60ed854add
commit e928d7b1c5
6 changed files with 276 additions and 1 deletions

38
message.html Normal file
View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>open pose editor</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/message-test.ts"></script>
<div>
<iframe
id="open-pose-editor"
src="http://localhost:5173/open-pose-editor/"
style="resize: both"
width="600px"
height="900px"
></iframe>
</div>
<button id="GetAPIs">GetAPIs</button>
<button id="GetAppVersion">GetAppVersion</button>
<button id="MakeImages">MakeImages</button>
<button id="Pause">Pause</button>
<button id="Resume">Resume</button>
<button id="OutputWidth">OutputWidth</button>
<button id="OutputHeight">OutputHeight</button>
<button id="OnlyHand">OnlyHand</button>
<button id="MoveMode">MoveMode</button>
<button id="GetWidth">GetWidth</button>
<button id="GetHeight">GetHeight</button>
<button id="GetSceneData">GetSceneData</button>
<button id="LockView">LockView</button>
<button id="UnlockView">UnlockView</button>
<button id="RestoreView">RestoreView</button>
<div id="output" style="background-color: gray;"></div>
</body>
</html>

View File

@ -11,6 +11,7 @@ import {
ResumeIcon,
} from '@radix-ui/react-icons'
import { getCurrentTime } from '../../utils/time'
import useMessageDispatch from '../../hooks/useMessageDispatch'
const { app, threejsCanvas, gallery, background } = classes
@ -140,6 +141,43 @@ function App() {
}
}, [editor])
useMessageDispatch({
GetAppVersion: () => __APP_VERSION__,
MakeImages: () => editor?.MakeImages(),
Pause: () => editor?.pause(),
Resume: () => editor?.resume(),
OutputWidth: (value: number) => {
if (editor && typeof value === 'number') {
editor.OutputWidth = value
return true
} else return false
},
OutputHeight: (value: number) => {
if (editor && typeof value === 'number') {
editor.OutputHeight = value
return true
} else return false
},
OnlyHand(value: boolean) {
if (editor && typeof value === 'boolean') {
editor.OnlyHand = value
return true
} else return false
},
MoveMode(value: boolean) {
if (editor && typeof value === 'boolean') {
editor.MoveMode = value
return true
} else return false
},
GetWidth: () => editor?.Width,
GetHeight: () => editor?.Height,
GetSceneData: () => editor?.GetSceneData(),
LockView: () => editor?.LockView(),
UnlockView: () => editor?.UnlockView(),
RestoreView: () => editor?.RestoreView(),
})
return (
<div ref={backgroundRef} className={background}>
<canvas

22
src/hooks/useEventCall.ts Normal file
View File

@ -0,0 +1,22 @@
// https://github.com/Volune/use-event-callback/blob/master/src/index.ts
import { useLayoutEffect, useMemo, useRef } from 'react'
type Fn<ARGS extends any[], R> = (...args: ARGS) => R
const useEventCallback = <A extends any[], R>(fn: Fn<A, R>): Fn<A, R> => {
const ref = useRef<Fn<A, R>>(fn)
useLayoutEffect(() => {
ref.current = fn
})
return useMemo(
() =>
(...args: A): R => {
const { current } = ref
return current(...args)
},
[]
)
}
export default useEventCallback

View File

@ -0,0 +1,104 @@
// https://github.com/rottitime/react-hook-window-message-event/blob/main/src/useMessage.ts
import { useCallback, useEffect, useRef, useState } from 'react'
import useEventCallback from './useEventCall'
export type IPostMessage = {
method: string
type: 'call' | 'return'
payload: any
}
export type EventHandler = (
callback: (data: IPostMessage) => unknown,
payload: IPostMessage['payload']
) => void
const postMessage = (
data: IPostMessage & {
cmd?: string // Just avoid webui exception
},
target: MessageEvent['source'],
origin = '*'
) => {
console.log('return', { target, origin, data })
target?.postMessage(
{ cmd: 'openpose-3d', ...data },
{ targetOrigin: origin }
)
}
export default function useMessageDispatch(
dispatch: Record<string, (...args: any[]) => any>
) {
const originRef = useRef<string>()
const sourceRef = useRef<MessageEvent['source']>(null)
originRef.current = ''
sourceRef.current = null as MessageEvent['source']
const sendToSender = (data: IPostMessage) =>
postMessage(data, sourceRef.current, originRef.current)
const sendToParent = (data: IPostMessage) => {
const { opener } = window
if (!opener) throw new Error('Parent window has closed')
postMessage(data, opener)
}
const onWatchEventHandler = useEventCallback(
// tslint:disable-next-line: no-shadowed-variable
({ origin, source, data }: MessageEvent) => {
if (!data) return
const { method, payload, type } = data as IPostMessage
// It is invalid message, not from webui extension.
if (type != 'call') return
console.log('method', method, payload)
if (payload && Array.isArray(payload) === false) {
console.error('payload is not array')
return
}
sourceRef.current = source
originRef.current = origin
if (method in dispatch) {
const eventHandler = dispatch[method]
if (typeof eventHandler === 'function') {
const ret = eventHandler(...(payload ?? []))
if (ret instanceof Promise) {
ret.then((value) => {
sendToSender({
method,
type: 'return',
payload: value,
})
})
} else {
sendToSender({
method,
type: 'return',
payload: ret,
})
}
}
} else if (method === 'GetAPIs') {
sendToSender({
method,
type: 'return',
payload: Object.keys(dispatch),
})
}
}
)
useEffect(() => {
window.addEventListener('message', onWatchEventHandler)
return () => window.removeEventListener('message', onWatchEventHandler)
}, [onWatchEventHandler])
return { history, sendToParent }
}

73
src/message-test.ts Normal file
View File

@ -0,0 +1,73 @@
export type IPostMessage = {
method: string
type: 'call' | 'return'
payload: any
}
const iframe = document.getElementById('open-pose-editor') as HTMLIFrameElement
const poseMessage = (message: IPostMessage) => {
iframe.contentWindow?.postMessage(message)
}
const MessageReturnHandler: Record<string, (arg: any) => void> = {}
window.addEventListener('message', (event) => {
const { data } = event
if (data && data.cmd && data.cmd == 'openpose-3d' && data.method) {
const method = data.method
if (method in MessageReturnHandler) {
MessageReturnHandler[method]?.(data.payload)
}
}
})
function InvokeOnlineOpenPose3D(method: string, ...args: any[]) {
return new Promise((resolve, reject) => {
const id = setTimeout(() => {
delete MessageReturnHandler[method]
reject({
method,
status: 'Timeout',
})
}, 1000)
const onReutrn = (arg: any) => {
clearTimeout(id)
resolve(arg)
}
MessageReturnHandler[method] = onReutrn
poseMessage({
method,
type: 'call',
payload: args,
})
})
}
function CreateClick(name: string, ...args: any[]) {
const ele = document.getElementById(name)
ele?.addEventListener('click', async () => {
console.log(name)
const value = await InvokeOnlineOpenPose3D(name, ...args)
console.log('return', value)
})
}
CreateClick('GetAPIs')
CreateClick('GetAppVersion')
CreateClick('MakeImages')
CreateClick('Pause')
CreateClick('Resume')
CreateClick('OutputWidth', 512)
CreateClick('OutputHeight', 512)
CreateClick('OnlyHand', true)
CreateClick('MoveMode', true)
CreateClick('GetWidth')
CreateClick('GetHeight')
CreateClick('GetSceneData')
CreateClick('LockView')
CreateClick('UnlockView')
CreateClick('RestoreView')

View File

@ -42,7 +42,7 @@ const config: UserConfigFn = ({ command, mode, ssrBuild }) => {
base: mode === 'singlefile' ? './' : '/open-pose-editor/',
define: {
global: {},
__APP_VERSION__: JSON.stringify('v0.0.2'),
__APP_VERSION__: JSON.stringify('0.1.2'),
__APP_BUILD_TIME__: Date.now(),
},
build: {