import { computed } from 'mobx'; import { observer } from 'mobx-react'; import { AclPrivate, Doc, Opt } from '../../../fields/Doc'; import { ScriptField } from '../../../fields/ScriptField'; import { Cast, StrCast } from '../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { emptyPath, OmitKeys, Without } from '../../../Utils'; import { DirectoryImportBox } from '../../util/Import & Export/DirectoryImportBox'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView'; import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox'; import { CollectionView } from '../collections/CollectionView'; import { InkingStroke } from '../InkingStroke'; import { PresElementBox } from '../nodes/trails/PresElementBox'; import { SearchBox } from '../search/SearchBox'; import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; import { FontIconBox } from './button/FontIconBox'; import { ColorBox } from './ColorBox'; import { ComparisonBox } from './ComparisonBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; import { DocumentViewProps } from './DocumentView'; import './DocumentView.scss'; import { EquationBox } from './EquationBox'; import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { FunctionPlotBox } from './FunctionPlotBox'; import { ImageBox } from './ImageBox'; import { KeyValueBox } from './KeyValueBox'; import { LabelBox } from './LabelBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkBox } from './LinkBox'; import { LoadingBox } from './LoadingBox'; import { MapBox } from './MapBox/MapBox'; import { PDFBox } from './PDFBox'; import { RecordingBox } from './RecordingBox'; import { ScreenshotBox } from './ScreenshotBox'; import { ScriptingBox } from './ScriptingBox'; import { SliderBox } from './SliderBox'; import { PresBox } from './trails/PresBox'; import { VideoBox } from './VideoBox'; import { WebBox } from './WebBox'; import React = require('react'); import XRegExp = require('xregexp'); import { MapPushpinBox } from './MapBox/MapPushpinBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without; export interface JsxBindings { props: BindingProps; } class ObserverJsxParser1 extends JsxParser { constructor(props: any) { super(props); observer(this as any); } } export const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; interface HTMLtagProps { Document: Doc; RootDoc: Doc; htmltag: string; onClick?: ScriptField; onInput?: ScriptField; scaling: number; children?: JSX.Element[]; } //" {this.title}" //" // // // {this.title} // // " @observer export class HTMLtag extends React.Component { click = (e: React.MouseEvent) => { const clickScript = (this.props as any).onClick as Opt; clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, scale: this.props.scaling }); }; onInput = (e: React.FormEvent) => { const onInputScript = (this.props as any).onInput as Opt; onInputScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, value: (e.target as any).textContent }); }; render() { const style: { [key: string]: any } = {}; const divKeys = OmitKeys(this.props, ['children', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || ''; }; Object.keys(divKeys).map((prop: string) => { const p = (this.props as any)[prop] as string; style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer); }); const Tag = this.props.htmltag as keyof JSX.IntrinsicElements; return ( {this.props.children} ); } } @observer export class DocumentContentsView extends React.Component< DocumentViewProps & { isSelected: (outsideReaction: boolean) => boolean; select: (ctrl: boolean) => void; NativeDimScaling?: () => number; setHeight?: (height: number) => void; layout_fieldKey: string; } > { @computed get layout(): string { TraceMobx(); if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString; if (!this.layoutDoc) return '

awaiting layout

'; if (this.props.layout_fieldKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString()); const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layout_fieldKey ? this.props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string'); if (layout === undefined) return this.props.Document.data ? "" : KeyValueBox.LayoutString(); if (typeof layout === 'string') return layout; return '

Loading layout

'; } get dataDoc() { const proto = this.props.DataDoc || Doc.GetProto(this.props.Document); return proto instanceof Promise ? undefined : proto; } get layoutDoc() { // bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script // const template: Doc = this.props.LayoutTemplate?.() || Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], Doc, null) : undefined); const template: Doc = this.props.LayoutTemplate?.() || (this.props.LayoutTemplateString && this.props.Document) || (this.props.layout_fieldKey && StrCast(this.props.Document[this.props.layout_fieldKey]) && this.props.Document) || Doc.Layout(this.props.Document, this.props.layout_fieldKey ? Cast(this.props.Document[this.props.layout_fieldKey], 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', 'contentPointerEvents', 'radialMenu', 'LayoutTemplateString', 'LayoutTemplate', 'dontCenter', 'contextMenuItems', 'onClick', 'onDoubleClick', 'onPointerDown', 'onPointerUp', ]; const list = { ...OmitKeys(this.props, [...docOnlyProps], '').omit, RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc, Document: this.layoutDoc, DataDoc: this.dataDoc, onClick: onClick, onInput: onInput, }; return { props: list }; } // 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: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => { return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, 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: any, p1: string, offset: any, string: any) => { return ` with as in: becomes const replacer3 = (match: any, p1: string, offset: any, string: any) => { return ` { 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, self: 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 }; } 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); }} /> ); } }