import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; import { returnFalse } from '../../Utils'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util'; import { DocumentType } from '../documents/DocumentTypes'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { ObservableReactComponent } from './ObservableReactComponent'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentView } from './nodes/DocumentView'; import { Transform } from '../util/Transform'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) export interface DocComponentProps { Document: Doc; LayoutTemplate?: () => Opt; LayoutTemplateString?: string; ScreenToLocalTransform: () => Transform; } export function DocComponent

() { class Component extends ObservableReactComponent> { constructor(props: any) { super(props); makeObservable(this); } ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform(); //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then get Document() { return this._props.Document; } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info @computed get layoutDoc() { return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.()); } // This is the data part of a document -- ie, the data that is constant across all views of the document @computed get dataDoc() { return this._props.Document[DocData] as Doc; } } return Component; } /// FieldViewBoxProps - a generic base class for field views that are not annotatable (e.g. InkingStroke, ColorBox) interface ViewBoxBaseProps { Document: Doc; TemplateDataDocument?: Doc; DocumentView?: () => DocumentView; fieldKey: string; isSelected: () => boolean; isContentActive: () => boolean | undefined; ScreenToLocalTransform: () => Transform; renderDepth: number; } export function ViewBoxBaseComponent

() { class Component extends ObservableReactComponent> { ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform(); //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then get Document() { return this._props.Document; } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info @computed get layoutDoc() { return Doc.Layout(this.Document); } // This is the data part of a document -- ie, the data that is constant across all views of the document @computed get dataDoc() { return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData]; } // key where data is stored get fieldKey() { return this._props.fieldKey; } } return Component; } /// DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image) export interface ViewBoxAnnotatableProps { Document: Doc; TemplateDataDocument?: Doc; fieldKey: string; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) isContentActive: () => boolean | undefined; select: (isCtrlPressed: boolean) => void; whenChildContentsActiveChanged: (isActive: boolean) => void; ScreenToLocalTransform: () => Transform; isSelected: () => boolean; renderDepth: number; isAnnotationOverlay?: boolean; } export function ViewBoxAnnotatableComponent

() { class Component extends ObservableReactComponent> { constructor(props: any) { super(props); makeObservable(this); } ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform(); @observable _annotationKeySuffix = () => 'annotations'; @observable _isAnyChildContentActive = false; //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document() { return this._props.Document; } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info @computed get layoutDoc() { return Doc.Layout(this.Document); } // This is the data part of a document -- ie, the data that is constant across all views of the document @computed get dataDoc() { return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData]; } // key where data is stored @computed get fieldKey() { return this._props.fieldKey; } isAnyChildContentActive = () => this._isAnyChildContentActive; @computed public get annotationKey() { return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : ''); } @action.bound removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean { const effectiveAcl = GetEffectiveAcl(this.dataDoc); const indocs = doc instanceof Doc ? [doc] : doc; const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin); // docs.forEach(doc => doc.annotationOn === this.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true)); const targetDataDoc = this.dataDoc; const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { const recent = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined; toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); Doc.RemoveDocFromList(Doc.GetProto(doc), 'proto_embeddings', doc); doc.embedContainer = undefined; if (recent) { doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); } }); if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) { (DocumentManager.Instance.getFirstDocumentView(targetDataDoc)?.ComponentView as CollectionFreeFormView)?.promoteCollection(); } else { this.isAnyChildContentActive() && this._props.select(false); } return true; } return false; } // this is called with the document that was dragged and the collection to move it into. // if the target collection is the same as this collection, then the move will be allowed. // otherwise, the document being moved must be able to be removed from its container before // moving it into the target. @action.bound moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => { if (Doc.AreProtosEqual(this._props.Document, targetCollection)) { return true; } const first = doc instanceof Doc ? doc : doc[0]; if (!first?._dragOnlyWithinContainer && addDocument !== returnFalse) { return this.removeDocument(doc, annotationKey, false) && addDocument(doc, annotationKey); } return false; }; @action.bound addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => { const docs = doc instanceof Doc ? [doc] : doc; if (this._props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.Document))) { return false; } const targetDataDoc = this.dataDoc; const effectiveAcl = GetEffectiveAcl(targetDataDoc); if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { return false; } const added = docs; if (added.length) { if ([AclAugment, AclEdit, AclAdmin].includes(effectiveAcl)) { added.forEach(doc => { doc._dragOnlyWithinContainer = undefined; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.Document; else Doc.GetProto(doc).annotationOn = undefined; Doc.SetContainer(doc, this.Document); inheritParentAcls(targetDataDoc, doc, true); }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add))); else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField(); } } return true; }; whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); } return Component; }