diff --git a/message.html b/message.html new file mode 100644 index 0000000..25daf00 --- /dev/null +++ b/message.html @@ -0,0 +1,38 @@ + + + + + + open pose editor + + + +
+ +
+ +
+ + + + + + + + + + + + + + + +
+ + diff --git a/src/environments/online/App.tsx b/src/environments/online/App.tsx index a565ce6..6fe5bcf 100644 --- a/src/environments/online/App.tsx +++ b/src/environments/online/App.tsx @@ -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 (
= (...args: ARGS) => R + +const useEventCallback = (fn: Fn): Fn => { + const ref = useRef>(fn) + useLayoutEffect(() => { + ref.current = fn + }) + return useMemo( + () => + (...args: A): R => { + const { current } = ref + return current(...args) + }, + [] + ) +} + +export default useEventCallback diff --git a/src/hooks/useMessageDispatch.ts b/src/hooks/useMessageDispatch.ts new file mode 100644 index 0000000..091e3a0 --- /dev/null +++ b/src/hooks/useMessageDispatch.ts @@ -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 any> +) { + const originRef = useRef() + const sourceRef = useRef(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 } +} diff --git a/src/message-test.ts b/src/message-test.ts new file mode 100644 index 0000000..e3989c2 --- /dev/null +++ b/src/message-test.ts @@ -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 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') diff --git a/vite.config.ts b/vite.config.ts index 120bd08..bc80a97 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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: {