@j2inn/app-react
Version:
React implementation of the j2inn-app framework
119 lines (118 loc) • 4.74 kB
JavaScript
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 }));
}
};