/* eslint-disable react/require-default-props */ import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as XRegExp from 'xregexp'; import { OmitKeys } from '../../../ClientUtils'; import { Without } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { AclPrivate, DocData } from '../../../fields/DocSymbols'; import { ScriptField } from '../../../fields/ScriptField'; import { Cast, DocCast, StrCast } from '../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { ObservableReactComponent, ObserverJsxParser } from '../ObservableReactComponent'; import './DocumentView.scss'; import { FieldViewProps } from './FieldView'; type BindingProps = Without; export interface JsxBindings { props: BindingProps; } interface HTMLtagProps { Document: Doc; htmltag: string; onClick?: ScriptField; onInput?: ScriptField; scaling: number; children?: JSX.Element[]; } // " {this.title}" // " // // // {this.title} // // " @observer export class HTMLtag extends React.Component { click = () => { const clickScript = this.props.onClick as Opt; clickScript?.script.run({ this: this.props.Document, scale: this.props.scaling }); }; onInput = (e: React.FormEvent) => { const onInputScript = this.props.onInput as Opt; onInputScript?.script.run({ this: this.props.Document, value: (e.target as HTMLElement).textContent }); }; render() { const style: { [key: string]: unknown } = {}; const divKeys = OmitKeys(this.props, [ 'children', // 'dragStarting', 'dragEnding', 'htmltag', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__', ]).omit; const replacer = (match: string, expr: string) => // bcz: this executes a script to convert a property expression string: { script } into a value (ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: this.props.Document, scale: this.props.scaling }).result as string) || ''; Object.keys(divKeys).forEach((prop: string) => { const p = (this.props as unknown as { [key: string]: string })[prop] as string; style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer); }); const Tag = this.props.htmltag as keyof JSX.IntrinsicElements; return ( {this.props.children} ); } } export interface DocumentContentsViewProps extends FieldViewProps { layoutFieldKey: string; } @observer export class DocumentContentsView extends ObservableReactComponent { private static DefaultLayoutString: string; /** * Set of all available rendering componets for Docs (e.g., ImageBox, CollectionFreeFormView, etc) */ private static Components: { [key: string]: unknown }; public static Init(defaultLayoutString: string, components: { [key: string]: unknown }) { DocumentContentsView.DefaultLayoutString = defaultLayoutString; DocumentContentsView.Components = components; } constructor(props: DocumentContentsViewProps) { super(props); makeObservable(this); } @computed get layout(): string { TraceMobx(); if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString; if (!this.layoutDoc) return '

awaiting layout

'; if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, DocumentContentsView.DefaultLayoutString); const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]); const layoutDoc = tempLayout ?? this.layoutDoc; const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(layoutDoc.layout_fieldKey, 'layout')], 'string'); if (layout === undefined) return this._props.Document.data ? "" : DocumentContentsView.DefaultLayoutString; if (typeof layout === 'string') return layout; return '

Loading layout

'; } get layoutDoc() { const template: Doc = this._props.LayoutTemplate?.() || (this._props.LayoutTemplateString && this._props.Document) || (this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) || Doc.Layout(this._props.Document, this._props.layoutFieldKey ? Cast(this._props.Document[this._props.layoutFieldKey], Doc, null) : undefined); return Doc.expandTemplateLayout(template, this._props.Document); } CreateBindings(onClick: Opt, onInput: Opt): JsxBindings { const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews 'hideResizeHandles', 'hideTitle', 'bringToFront', 'childContentPointerEvents', 'LayoutTemplateString', 'LayoutTemplate', 'layoutFieldKey', 'dontCenter', 'DataTransition', 'contextMenuItems', // 'onClick', // don't need to omit this since it will be set 'onDoubleClickScript', 'onPointerDownScript', 'onPointerUpScript', ]; const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined); const list: BindingProps & React.DetailedHTMLProps, HTMLDivElement> = { ...this._props, Document: this.layoutDoc ?? this._props.Document, TemplateDataDocument: templateDataDoc instanceof Promise ? undefined : templateDataDoc, onClick: onClick as unknown as React.MouseEventHandler, // pass onClick script as if it were a real function -- it will be interpreted properly in the HTMLtag onInput: onInput as unknown as React.FormEventHandler, }; return { props: { ...OmitKeys(list, [...docOnlyProps], '').omit, } as BindingProps, }; } // componentWillUpdate(oldProps: any, newState: any) { // // console.log("willupdate", oldProps, this._props); // bcz: if you get a message saying something invalidated because reactive props changed, then this method allows you to figure out which prop changed // } @computed get renderData() { TraceMobx(); let layoutFrame = this.layout; // replace code content with a script >{content}< as in {this.title} const replacer = (match: string, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix; layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer); // replace HTML with corresponding HTML tag as in: becomes const replacer2 = (match: string, p1: string) => ` with as in: becomes const replacer3 = (/* match: any, p1: string, offset: any, string: any */) => ` { const splits = layoutFrame.split(`${func}=`); if (splits.length > 1) { const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] }); layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1); const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts return ScriptField.MakeScript(script, { this: Doc.name, scale: 'number', value: 'string' }); } return undefined; // add input function to props }; const onClick = makeFuncProp('onClick'); const onInput = makeFuncProp('onInput'); const bindings = this.CreateBindings(onClick, onInput); return { bindings, layoutFrame }; } blacklistedAttrs = []; render() { TraceMobx(); const { bindings, layoutFrame } = this.renderData; return this._props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : ( { console.log('DocumentContentsView:' + test, bindings, layoutFrame); }} /> ); } }