UNPKG

@j2inn/app-react

Version:

React implementation of the j2inn-app framework

119 lines (118 loc) 4.74 kB
import { jsx as _jsx } from "react/jsx-runtime"; /* * Copyright (c) 2022, J2 Innovations. All Rights Reserved */ import { RemoteApp, } from '@j2inn/app'; import { HInput } from '@j2inn/ui'; import { useClient } from 'haystack-react'; import { observer } from 'mobx-react-lite'; import { useMemo } from 'react'; import { useAppRootStore } from '../hooks'; import { RemoteAppStoreLoader } from './RemoteApp'; import { RemoteAppViewRenderer } from './RemoteApp/RemoteAppViewRenderer'; import { Suspense } from './Suspense'; /** * The default editor filter function. * * This function returns true which means the first remote editor found will be used. */ const DEFAULT_FILTER = () => true; function getFinUiDef(namespace) { return namespace.byName('finApp:finUi'); } /** * Returns an ordered set of super types. Each layer of super types are added to the * list so that the first type in the list is the most specific. * * @param tag The tag name to look up. * @param namespace The haystack namespace. * @returns An array of tag names ordered from most specific to least specific. */ function getOrderedSuperTypes(tag, namespace) { const types = [tag]; for (const type of types) { types.push(...namespace.superTypesOf(type).map((def) => def.defName)); } return types; } function findAppViewData(app, tag, namespace, filter) { const allData = []; const types = getOrderedSuperTypes(tag, namespace); for (const viewId of Object.keys(app.views ?? [])) { const view = app.views?.[viewId]; const viewDef = namespace.byName(`finUi:${viewId}`); if (view && !view.hidden && view.type === 'editor' && viewDef) { for (const association of namespace.associations(viewDef.defName, 'finUiOn')) { for (let i = 0; i < types.length; ++i) { if (types[i] === association.defName && filter(app, view)) { allData.splice(i, 0, { app, view }); } } } } } return allData[0]; } /** * Find an application and its associated for the specified tag name. * * This will use the defs namespace to look up any associated finUi and return * its details. * * @param tag The name of the tag. * @param namespace The defs namespace. * @param rootStore The application root store. * @return Application and view data or undefined if none can be found. */ export function findRemoteEditorAppView(tag, namespace, rootStore, filter) { const apps = [...RemoteApp.makeApps(namespace)]; const finUiDef = getFinUiDef(namespace); let data; if (finUiDef) { // Include the finUi app as a lot of editors aren't associated with // a configurable app. apps.unshift(new RemoteApp(finUiDef, namespace)); } for (const app of apps) { if (app.views && rootStore.hasAppAccess(app.id, 'read')) { data = findAppViewData(app, tag, namespace, filter); } if (data) { break; } } return data; } /** * An editor for editing a haystack value. * * @param props The tag editor properties. */ export const TagEditor = (props) => { const client = useClient(); const rootStore = useAppRootStore(); const { tag, filter } = props; let { readonly } = props; const remoteAppData = useMemo(() => findRemoteEditorAppView(tag, client.defs, rootStore, filter ?? DEFAULT_FILTER), [props.tag, client]); const RemoteEditor = useMemo(() => remoteAppData && observer((props) => (_jsx(RemoteAppViewRenderer, { view: remoteAppData.view, appProps: props }))), [remoteAppData]); // If readonly is undefined then look up to see if the def should always // be readonly by default. if (readonly === undefined && client.defs.byName(tag)?.get('readonly')?.value) { readonly = true; } if (remoteAppData && RemoteEditor) { // If readonly is undefined then look up the app permissions. if (readonly === undefined && !rootStore.hasAppAccess(remoteAppData.app.id, 'write')) { readonly = true; } // If we have a remote editor then use it. return (_jsx(RemoteAppStoreLoader, { app: remoteAppData.app, children: _jsx(Suspense, { children: _jsx(RemoteEditor, { ...props, readonly: readonly }) }) })); } else { // If there's no remote editor then fallback to the standard haystack editor. return (_jsx(HInput, { value: props.value, onChange: (value) => props.onChange?.({ value, valid: true }), editable: !readonly })); } };