From 7db0fe589afa3281894afb0a0d3bd6b9983c9d33 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 29 Mar 2021 19:04:08 -0400
Subject: still cleaning up all the active/selection code.

---
 src/client/views/collections/CollectionView.tsx | 190 ++++++------------------
 1 file changed, 44 insertions(+), 146 deletions(-)

(limited to 'src/client/views/collections/CollectionView.tsx')

diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f8c846f7f..28099a995 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,26 +2,23 @@ import { action, computed, observable } from 'mobx';
 import { observer } from "mobx-react";
 import * as React from 'react';
 import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
-import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { documentSchema } from '../../../fields/documentSchemas';
 import { Id } from '../../../fields/FieldSymbols';
-import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
+import { makeInterface } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { Cast, ScriptCast, StrCast, DateCast } from '../../../fields/Types';
-import { denormalizeEmail, distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
-import { returnFalse } from '../../../Utils';
-import { Docs, DocUtils } from '../../documents/Documents';
-import { BranchTask, BranchCreate } from '../../documents/Gitlike';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { TraceMobx } from '../../../fields/util';
+import { DocUtils } from '../../documents/Documents';
+import { BranchCreate, BranchTask } from '../../documents/Gitlike';
 import { CurrentUserUtils } from '../../util/CurrentUserUtils';
 import { ImageUtils } from '../../util/Import & Export/ImageUtils';
 import { InteractionUtils } from '../../util/InteractionUtils';
-import { UndoManager } from '../../util/UndoManager';
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from '../ContextMenuItem';
+import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
-import { Touchable } from '../Touchable';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from "./CollectionDockingView";
@@ -64,7 +61,6 @@ export enum CollectionViewType {
 export interface CollectionViewProps extends FieldViewProps {
     isAnnotationOverlay?: boolean;  // is the collection an annotation overlay (eg an overlay on an image/video/etc)
     layoutEngine?: () => string;
-    filterAddDocument?: (doc: 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)
     setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
 
     // property overrides for child documents
@@ -81,19 +77,20 @@ export interface CollectionViewProps extends FieldViewProps {
     childClickScript?: ScriptField;
     childDoubleClickScript?: ScriptField;
 }
+
+type CollectionDocument = makeInterface<[typeof documentSchema]>;
+const CollectionDocument = makeInterface(documentSchema);
 @observer
-export class CollectionView extends Touchable<CollectionViewProps> {
+export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps, CollectionDocument>(CollectionDocument, "") {
     public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
 
-    _isChildActive = false;   //TODO should this be observable?
-    @observable private _curLightboxImg = 0;
     @observable private static _safeMode = false;
     public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
 
     protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
 
     get collectionViewType(): CollectionViewType | undefined {
-        const viewField = StrCast(this.props.Document._viewType);
+        const viewField = StrCast(this.layoutDoc._viewType);
         if (CollectionView._safeMode) {
             switch (viewField) {
                 case CollectionViewType.Freeform:
@@ -104,121 +101,24 @@ export class CollectionView extends Touchable<CollectionViewProps> {
         return viewField as any as CollectionViewType;
     }
 
-    active = (outsideReaction?: boolean) => {
-        return this.props.renderDepth === 0 ||
-            this.props.isSelected(outsideReaction) ||
-            this.props.rootSelected(outsideReaction) ||
-            (this.props.layerProvider?.(this.props.Document) !== false && (this.props.Document.forceActive || this.props.Document._isGroup)) ||
-            this._isChildActive ?
-            true :
-            false;
-    }
-
-    whenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isChildActive = isActive);
-
-    @action.bound
-    addDocument = (doc: Doc | Doc[]): boolean => {
-        if (this.props.filterAddDocument?.(doc) === false) {
-            return false;
-        }
-
-        const docs = doc instanceof Doc ? [doc] : doc;
-
-        if (docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document))) return false;
-        const targetDataDoc = this.props.Document[DataSym];
-        const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
-        const added = docs.filter(d => !docList.includes(d));
-        const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
-
-        if (added.length) {
-            if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
-                return false;
-            }
-            else {
-                if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym])) {
-                    added.forEach(d => {
-                        for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
-                            if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true);
-                            //else if (this.props.Document[key] === SharingPermissions.Admin) distributeAcls(key, SharingPermissions.Add, d, true);
-                            //else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true);
-                        }
-                    });
-                }
-
-                if (effectiveAcl === AclAddonly) {
-                    added.map(doc => {
-                        this.props.layerProvider?.(doc, true);// assigns layer values to the newly added document... testing the utility of this
-                        Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc);
-                        doc.context = this.props.Document;
-                    });
-                }
-                else {
-                    added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => {  // only make a pushpin if we have acl's to edit the document
-                        DocUtils.LeavePushpin(doc);
-                        doc._stayInCollection = undefined;
-                        doc.context = this.props.Document;
-                    });
-                    added.map(doc => this.props.layerProvider?.(doc, true));// assigns layer values to the newly added document... testing the utility of this
-                    (targetDataDoc[this.props.fieldKey] as List<Doc>).push(...added);
-                    targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()));
-                }
-            }
-        }
-        return true;
-    }
-
-    @action.bound
-    removeDocument = (doc: any): boolean => {
-        const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
-        const indocs = doc instanceof Doc ? [doc] : doc as Doc[];
-        const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin);
-        if (docs.length) {
-            const targetDataDoc = this.props.Document[DataSym];
-            const value = DocListCast(targetDataDoc[this.props.fieldKey]);
-            const toRemove = value.filter(v => docs.includes(v));
-            if (toRemove.length !== 0) {
-                const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc;
-                toRemove.forEach(doc => {
-                    const ind = (targetDataDoc[this.props.fieldKey] as List<Doc>).indexOf(doc);
-                    if (ind !== -1) {
-                        Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc);
-                        doc.context = undefined;
-                        recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
-                    }
-                });
-                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[]) => boolean): boolean => {
-        if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
-            return true;
-        }
-        const first = doc instanceof Doc ? doc : doc[0];
-        if (!first?._stayInCollection && addDocument !== returnFalse) {
-            return UndoManager.RunInTempBatch(() => this.removeDocument(doc) && addDocument(doc));
-        }
-        return false;
-    }
-
     showIsTagged = () => {
         return (null);
         // this section would display an icon in the bototm right of a collection to indicate that all
         // photos had been processed through Google's content analysis API and Google's tags had been
         // assigned to the documents googlePhotosTags field.
-        // const children = DocListCast(this.props.Document[this.props.fieldKey]);
+        // const children = DocListCast(this.rootDoc[this.props.fieldKey]);
         // const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto);
         // const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags);
         // return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />;
     }
 
+    isContentActive = (outsideReaction?: boolean) => {
+        return this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.renderDepth === 0 || this.props.rootSelected(outsideReaction) ||
+            (this.props.layerProvider?.(this.rootDoc) !== false && (this.rootDoc.forceActive || this.rootDoc._isGroup))
+            ?
+            true : false;
+    }
+
     screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
     private SubView = (type: CollectionViewType, props: SubCollectionViewProps) => {
         TraceMobx();
@@ -251,7 +151,6 @@ export class CollectionView extends Touchable<CollectionViewProps> {
         }
         subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" });
         subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" });
-        !Doc.UserDoc().noviceMode && subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking), icon: "ellipsis-v" });
         subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" });
         subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" });
         subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" });
@@ -262,7 +161,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
         !Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
         subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
 
-        if (!Doc.IsSystem(this.props.Document) && !this.props.Document.annotationOn) {
+        if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.annotationOn) {
             const existingVm = ContextMenu.Instance.findByDescription(category);
             const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
             catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" });
@@ -272,9 +171,9 @@ export class CollectionView extends Touchable<CollectionViewProps> {
 
     onContextMenu = (e: React.MouseEvent): void => {
         const cm = ContextMenu.Instance;
-        if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+        if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             this.setupViewTypes("UI Controls...", vtype => {
-                const newRendition = Doc.MakeAlias(this.props.Document);
+                const newRendition = Doc.MakeAlias(this.rootDoc);
                 newRendition._viewType = vtype;
                 this.props.addDocTab(newRendition, "add:right");
                 return newRendition;
@@ -282,34 +181,34 @@ export class CollectionView extends Touchable<CollectionViewProps> {
 
             const options = cm.findByDescription("Options...");
             const optionItems = options && "subitems" in options ? options.subitems : [];
-            !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }) : null;
-            if (this.props.Document.childLayout instanceof Doc) {
-                optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "add:right"), icon: "project-diagram" });
+            !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null;
+            if (this.rootDoc.childLayout instanceof Doc) {
+                optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" });
             }
-            if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
-                optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" });
+            if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) {
+                optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" });
             }
-            !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+            !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" });
 
             optionItems.push({
-                description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.props.Document), "add:right"), icon: "project-diagram"
+                description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram"
             });
             optionItems.push({
-                description: "Pull Master", event: () => BranchTask(this.props.Document, "pull"), icon: "project-diagram"
+                description: "Pull Master", event: () => BranchTask(this.rootDoc, "pull"), icon: "project-diagram"
             });
             optionItems.push({
-                description: "Merge Branches", event: () => BranchTask(this.props.Document, "merge"), icon: "project-diagram"
+                description: "Merge Branches", event: () => BranchTask(this.rootDoc, "merge"), icon: "project-diagram"
             });
 
             !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
 
-            if (!Doc.UserDoc().noviceMode && !this.props.Document.annotationOn) {
+            if (!Doc.UserDoc().noviceMode && !this.rootDoc.annotationOn) {
                 const existingOnClick = cm.findByDescription("OnClick...");
                 const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
                 const funcs = [{ key: "onChildClick", name: "On Child Clicked" }, { key: "onChildDoubleClick", name: "On Child Double Clicked" }];
                 funcs.map(func => onClicks.push({
                     description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
-                        const alias = Doc.MakeAlias(this.props.Document);
+                        const alias = Doc.MakeAlias(this.rootDoc);
                         DocUtils.makeCustomViewClicked(alias, undefined, func.key);
                         this.props.addDocTab(alias, "add:right");
                     }
@@ -318,15 +217,15 @@ export class CollectionView extends Touchable<CollectionViewProps> {
                     onClicks.push({
                         description: `Set child ${childClick.title}`,
                         icon: "edit",
-                        event: () => Doc.GetProto(this.props.Document)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
+                        event: () => Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
                     }));
-                !Doc.IsSystem(this.props.Document) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
+                !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
             }
 
             if (!Doc.UserDoc().noviceMode) {
                 const more = cm.findByDescription("More...");
                 const moreItems = more && "subitems" in more ? more.subitems : [];
-                moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
+                moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) });
                 !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
             }
         }
@@ -334,27 +233,26 @@ export class CollectionView extends Touchable<CollectionViewProps> {
 
     bodyPanelWidth = () => this.props.PanelWidth();
 
-    childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
-    @computed get childLayoutString() { return StrCast(this.props.Document.childLayoutString); }
+    childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
+    @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); }
 
     render() {
         TraceMobx();
         const props: SubCollectionViewProps = {
             ...this.props,
             addDocument: this.addDocument,
-            removeDocument: this.removeDocument,
             moveDocument: this.moveDocument,
-            active: this.active,
-            whenChildContentsActiveChanged: this.whenChildContentsActiveChanged,
+            removeDocument: this.removeDocument,
+            isContentActive: this.isContentActive,
             PanelWidth: this.bodyPanelWidth,
             PanelHeight: this.props.PanelHeight,
+            ScreenToLocalTransform: this.screenToLocalTransform,
             childLayoutTemplate: this.childLayoutTemplate,
             childLayoutString: this.childLayoutString,
-            ScreenToLocalTransform: this.screenToLocalTransform,
             CollectionView: this,
         };
         return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
-            style={{ pointerEvents: this.props.layerProvider?.(this.props.Document) === false ? "none" : undefined }}>
+            style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}>
             {this.showIsTagged()}
             {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
         </div>);
-- 
cgit v1.2.3-70-g09d2

