From ca63e2936a8518c033b45f732e3dc09c5a289cd4 Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Sun, 9 Jul 2023 22:19:55 +0100 Subject: [PATCH] experiments(dui3): super wip work --- .../dui3/lib/accounts/composables/setup.ts | 8 +++- packages/dui3/lib/bridge/cefSharp.ts | 28 +++++++++++++ packages/dui3/lib/bridge/sketchup.ts | 1 + packages/dui3/lib/bridge/webview.ts | 14 +++++++ packages/dui3/lib/sketchup/bindings.ts | 30 -------------- packages/dui3/package.json | 1 + packages/dui3/pages/index.vue | 3 +- packages/dui3/plugins/00.bindings.ts | 16 ++++++-- packages/dui3/types/index.ts | 39 +++++++++++++++++-- 9 files changed, 101 insertions(+), 39 deletions(-) create mode 100644 packages/dui3/lib/bridge/cefSharp.ts create mode 100644 packages/dui3/lib/bridge/sketchup.ts delete mode 100644 packages/dui3/lib/sketchup/bindings.ts diff --git a/packages/dui3/lib/accounts/composables/setup.ts b/packages/dui3/lib/accounts/composables/setup.ts index 3bf7aa86a..d3c45e092 100644 --- a/packages/dui3/lib/accounts/composables/setup.ts +++ b/packages/dui3/lib/accounts/composables/setup.ts @@ -29,7 +29,7 @@ export async function useAccountsSetup() { // Matches local accounts coming from the host app to app state. const refreshAccounts = async () => { - const accs = JSON.parse(await $bindings.getAccounts()) as Account[] + const accs = await $bindings.getAccounts() const newAccs = [] as DUIAccount[] for (const acc of accs) { const existing = accounts.value.find((a) => a.accountInfo.id === acc.id) @@ -53,10 +53,16 @@ export async function useAccountsSetup() { accounts.value = newAccs } + // Call this one first to initialize the account state await refreshAccounts() + const defaultAccount = computed(() => + accounts.value.find((acc) => acc.accountInfo.isDefault) + ) + const accState = { accounts, + defaultAccount, refreshAccounts } diff --git a/packages/dui3/lib/bridge/cefSharp.ts b/packages/dui3/lib/bridge/cefSharp.ts new file mode 100644 index 000000000..2d480de24 --- /dev/null +++ b/packages/dui3/lib/bridge/cefSharp.ts @@ -0,0 +1,28 @@ +import { IWebUiBinding } from '~/types' +import { createNanoEvents, Emitter } from 'nanoevents' +import { HostAppEvents } from '~/types' + +export class CefSharpBridge { + private emitter: Emitter + + constructor(bindingObject: IWebUiBinding) { + const hoistTarget = this as unknown as Record + const hoistSource = bindingObject as unknown as Record + + for (const key in bindingObject) { + hoistTarget[key] = hoistSource[key] + } + + this.emitter = createNanoEvents() + this.emitter.emit('start', 'polo pasta') + } + + on(event: E, callback: HostAppEvents[E]) { + return this.emitter.on(event, callback) + } + + emit(eventName: string, payload: string) { + const parsedPayload = JSON.parse(payload) as unknown + this.emitter.emit(eventName, parsedPayload) + } +} diff --git a/packages/dui3/lib/bridge/sketchup.ts b/packages/dui3/lib/bridge/sketchup.ts new file mode 100644 index 000000000..70b786d12 --- /dev/null +++ b/packages/dui3/lib/bridge/sketchup.ts @@ -0,0 +1 @@ +// TODO diff --git a/packages/dui3/lib/bridge/webview.ts b/packages/dui3/lib/bridge/webview.ts index d16d1c023..0817c6edf 100644 --- a/packages/dui3/lib/bridge/webview.ts +++ b/packages/dui3/lib/bridge/webview.ts @@ -1,4 +1,6 @@ // github.com/johot/WebView2-better-bridge/blob/master/web-ui/src/betterBridge.ts +import { createNanoEvents, Emitter } from 'nanoevents' +import { HostAppEvents } from '~/types' type IWebView2 = { webview: { @@ -17,6 +19,7 @@ declare let chrome: IWebView2 export class WebView2Bridge { private webViewBridge: IRawBridge + private emitter: Emitter constructor(bridgeName: string) { this.webViewBridge = chrome.webview.hostObjects[bridgeName] @@ -33,6 +36,8 @@ export class WebView2Bridge { hoistTarget[lowercasedMethodName] = (...args: unknown[]) => this.runMethod(methodName, args) } + + this.emitter = createNanoEvents() } private async runMethod(methodName: string, args: unknown[]): Promise { @@ -45,6 +50,15 @@ export class WebView2Bridge { return JSON.parse(result) as unknown } + + on(event: E, callback: HostAppEvents[E]) { + return this.emitter.on(event, callback) + } + + emit(eventName: string, payload: string) { + const parsedPayload = JSON.parse(payload) as unknown + this.emitter.emit(eventName, parsedPayload) + } } const lowercaseMethodName = (name: string) => diff --git a/packages/dui3/lib/sketchup/bindings.ts b/packages/dui3/lib/sketchup/bindings.ts deleted file mode 100644 index 4cba29c33..000000000 --- a/packages/dui3/lib/sketchup/bindings.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IWebUiBinding } from '~/types' - -export class SketchupBindings implements IWebUiBinding { - requestId = 0 - requests = {} as Record - - SketchupBindings() { - // todo - } - - getAccounts() { - this.requestId++ - this.requests[this.requestId] = new Promise((resolve, reject) => resolve('[]')) - - return this.requests[this.requestId] as Promise - } - - async sayHi(name: string) { - return `Hi ${name} from (sketchup mocked bindings)!` - } - - getSourceAppName() { - return new Promise((resolve, reject) => resolve('sketchup')) as Promise - } - - async openDevTools() { - // eslint-disable-next-line no-alert - window.alert('Please right click and select show dev tools.') - } -} diff --git a/packages/dui3/package.json b/packages/dui3/package.json index 6832566ad..012c5695a 100644 --- a/packages/dui3/package.json +++ b/packages/dui3/package.json @@ -34,6 +34,7 @@ "graphql": "^16.6.0", "graphql-tag": "^2.12.6", "lodash-es": "^4.17.21", + "nanoevents": "^8.0.0", "portal-vue": "^3.0.0", "subscriptions-transport-ws": "^0.11.0" }, diff --git a/packages/dui3/pages/index.vue b/packages/dui3/pages/index.vue index 9e367c6f1..2af7b8dbc 100644 --- a/packages/dui3/pages/index.vue +++ b/packages/dui3/pages/index.vue @@ -18,6 +18,7 @@ {{ acc.accountInfo.serverInfo.name }} +
Your default account is {{ defaultAccount?.accountInfo }}
{{ clientId }}: @@ -38,7 +39,7 @@ import { ServerInfoTestQuery } from '~/lib/common/generated/gql/graphql' const { $bindings } = useNuxtApp() const appName = await $bindings.getSourceAppName() -const { accounts, refreshAccounts } = await useAccountsSetup() +const { accounts, refreshAccounts, defaultAccount } = await useAccountsSetup() const versionQuery = graphql(` query ServerInfoTest { diff --git a/packages/dui3/plugins/00.bindings.ts b/packages/dui3/plugins/00.bindings.ts index 84b636630..91d0547c7 100644 --- a/packages/dui3/plugins/00.bindings.ts +++ b/packages/dui3/plugins/00.bindings.ts @@ -1,5 +1,6 @@ import { IWebUiBinding, MockedBindings } from '~/types' import { WebView2Bridge } from '~/lib/bridge/webview' +import { CefSharpBridge } from '~/lib/bridge/cefSharp' interface ICefSharp { BindObjectAsync: (arg: string) => Promise @@ -11,10 +12,14 @@ interface IWebView2 { declare let CefSharp: ICefSharp declare let chrome: IWebView2 -declare let sketchup: Record // +declare let sketchup: Record declare let WebUIBinding: IWebUiBinding +// Tries to find the correct host application binding. The sequence is: +// - CEFSharp (.NET) +// - WebView2 (.NET) +// - Sketchup (Ruby) - NOT IMPLEMENTED export default defineNuxtPlugin(async () => { let bindings: IWebUiBinding | undefined = undefined @@ -22,7 +27,7 @@ export default defineNuxtPlugin(async () => { if (!CefSharp) throw new Error('No global CefSharp object found.') await CefSharp.BindObjectAsync('WebUIBinding') console.info('Bound WebUIBinding object for CefSharp.') - bindings = WebUIBinding + bindings = new CefSharpBridge(WebUIBinding) as unknown as IWebUiBinding } catch (e) { console.warn('Failed to bind CefSharp.') console.warn(e) @@ -32,10 +37,9 @@ export default defineNuxtPlugin(async () => { if (!chrome.webview) throw new Error('No global Webview2 object found.') bindings = new WebView2Bridge('WebUIBinding') as unknown as IWebUiBinding console.info('Bound WebUIBinding object for Webview2.') + const res = await bindings.sayHi('Test') console.log(res) - - // TODO: Wrap the motherfucking wv2 bindings up } catch (e) { console.warn('Failed to bind Webview2.') console.warn(e) @@ -57,6 +61,10 @@ export default defineNuxtPlugin(async () => { ;(globalThis as Record).bindings = bindings + bindings.on('test', (args) => { + console.log(args) + }) + return { provide: { bindings diff --git a/packages/dui3/types/index.ts b/packages/dui3/types/index.ts index 47dc1807c..29e43589b 100644 --- a/packages/dui3/types/index.ts +++ b/packages/dui3/types/index.ts @@ -1,3 +1,4 @@ +import { createNanoEvents, Emitter } from 'nanoevents' /* eslint-disable @typescript-eslint/require-await */ export type Account = { id: string @@ -30,17 +31,46 @@ type ModelCard = { // settings: Record??? // report: Record??? // progress: Record??? + status: 'idle' | 'inprogress' | 'error' | 'warning' | 'disabled' } -export type IWebUiBinding = { +type TestData = { + foo: number + bar: string + baz: boolean +} + +export interface HostAppEvents { + test: (data: TestData) => void + documentChanged: () => void + selectionChanged: () => void + documentClosed: () => void + updateModelCardState: () => void + displayToastNotification: () => void // bla bla bla +} + +export interface IWebUiBinding { sayHi: (name: string) => Promise openDevTools: () => Promise getAccounts: () => Promise getSourceAppName: () => Promise - // etc. - getFileState: () => Promise + // getFileState: () => Promise + // addModelCard(string modelId, string projectId), removeModelCard(...) // etc. etc. + /** + * Subscribe to messages from the host application. + * @param event + * @param callback + */ + on: (event: E, callback: HostAppEvents[E]) => void + /** + * Used by the host application to notify/send data to the web app. Do not use from the frontend. + * @param eventName + * @param args + */ + emit?: (eventName: string, args: Record) => void } +const mockedEmitter = createNanoEvents() export const MockedBindings: IWebUiBinding = { async sayHi(name: string) { return `Hi ${name} from (mocked bindings)!` @@ -57,5 +87,8 @@ export const MockedBindings: IWebUiBinding = { }, async getFileState() { return { models: [] } + }, + on(event: E, callback: HostAppEvents[E]) { + return mockedEmitter.on(event, callback) } }