UNPKG

@omnicajs/vue-remote

Version:

Proxy renderer for Vue.js based on @remote-ui

206 lines (179 loc) 4.6 kB
import type { Component, Ref, ShallowRef, } from 'vue' import type { Receiver, Received, ReceivedChild, ReceivedComment, ReceivedComponent, ReceivedRoot, ReceivedText, } from '@/dom/host' import type { HostedChild, HostedComment, HostedComponent, HostedRoot, HostedText, } from './tree' import { ref, shallowRef, } from 'vue' import { isFunction } from '@/dom/host' import { KIND_COMMENT, KIND_COMPONENT, KIND_ROOT, KIND_TEXT, } from '@/dom/host' export default function (receiver: Receiver): HostedRoot { const root = receiver.tree.root const children = useChildren(receiver, root) return { id: root.id, kind: KIND_ROOT, children: children.ref, update () { children.load(root, true) }, release: fuse([ receiver.tree.updatable<ReceivedRoot>(root, children.load), () => children.ref.value.forEach(c => c.release()), ]), } } export function useComment ( receiver: Receiver, node: ReceivedComment ): HostedComment { const text = ref(node.text) return { id: node.id, kind: KIND_COMMENT, text, update () { const n = receiver.tree.get<ReceivedComment>(node) if (n) { text.value = n.text } }, release: receiver.tree.updatable(node, (n: ReceivedComment) => { text.value = n.text }), } } export function useComponent (receiver: Receiver, node: ReceivedComponent): HostedComponent { const _ref = ref<Component<NonNullable<unknown>> | Element | null>(null) const properties = ref(node.properties) const children = useChildren(receiver, node) const load = (node: ReceivedComponent) => { properties.value = node.properties children.load(node) } const release = fuse([ receiver.tree.updatable<ReceivedComponent>(node, load), receiver.tree.invokable<ReceivedComponent>(node, useInvokeHandler(node, _ref)), () => children.ref.value.forEach(c => c.release()), ]) return { id: node.id, kind: KIND_COMPONENT, type: node.type, ref: _ref, properties, children: children.ref, update: () => { const n = receiver.tree.get<ReceivedComponent>(node) if (n) { load(n) } }, release, } } export function useText ( receiver: Receiver, node: ReceivedText ): HostedText { const text = ref(node.text) return { id: node.id, kind: KIND_TEXT, text, update () { const c = receiver.tree.get<ReceivedText>(node) if (c) { text.value = c.text } }, release: receiver.tree.updatable(node, (n: ReceivedText) => { text.value = n.text }), } } export function useChild ( receiver: Receiver, child: ReceivedChild ): HostedChild { switch (child.kind) { case KIND_COMMENT: return useComment(receiver, child) case KIND_COMPONENT: return useComponent(receiver, child) case KIND_TEXT: return useText(receiver, child) } } export function useChildren <T extends ReceivedComponent | ReceivedRoot>( receiver: Receiver, node: T ):{ ref: ShallowRef<HostedChild[]>, load: (node: T, force?: boolean) => void} { const ref = shallowRef(node.children.map(c => useChild(receiver, c))) const load = (node: T, force = false) => { ref.value.forEach(_old => { if (!node.children.some(_new => _new.id === _old.id)) { _old.release() } }) ref.value = node.children.map(_new => { const foundOld = ref.value.find(_old => _old.id === _new.id) if (force && foundOld) { foundOld.update() } return foundOld ?? useChild(receiver, _new) }) } return { ref, load } } export function useInvokeHandler ( node: ReceivedComponent, ref: Ref<Component<NonNullable<unknown>> | Element | null> ) { return (method: string, payload: unknown[]) => { const el = ref.value if (el == null) { throw new Error(`${print(node)} not mounted to host environment yet`) } if (!(method in el)) { throw new Error(`${print(node)} doesn't support method ${method}`) } const callable = el[method as keyof typeof el] as unknown if (!isFunction(callable)) { throw new Error(`${print(node)} doesn't support method ${method}`) } return callable.call(el, ...payload) } } function print <T extends Received>(node: T) { const { id, kind } = node return 'type' in node ? `Node [ID=${id}, KIND=${kind}, TYPE=${node.type}]` : `Node [ID=${id}, KIND=${kind}]` } function fuse (handlers: Array<() => void>) { return () => handlers.forEach(fn => fn()) }