aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/CollectionView.tsx')
-rw-r--r--src/client/views/collections/CollectionView.tsx200
1 files changed, 38 insertions, 162 deletions
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1e693f594..91f159d8e 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";
@@ -66,8 +63,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;
- parentActive: (outsideReaction: boolean) => boolean;
- 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
@@ -84,19 +79,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:
@@ -107,133 +103,16 @@ 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;
- }
-
- whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
-
- /**
- * Applies the collection/dashboard's current filter attributes to the doc being added
- */
- addFilterAttributes = (doc: Doc) => {
- Cast((FilterBox._filterScope === "Current Collection" ? this.props.Document : CurrentUserUtils.ActiveDashboard)._docFilters, listSpec("string"))?.forEach(attribute => {
- if (attribute.charAt(0).toUpperCase() === attribute.charAt(0)) {
- const fields = attribute.split(':');
- if (fields[2] === "check") doc[DataSym][fields[0]] = fields[1];
- else if (fields[2] === "x" && doc[DataSym][fields[0]] === fields[1]) doc[DataSym][fields[0]] = undefined;
- }
- });
- }
-
- @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;
- this.addFilterAttributes(doc); //
- });
- 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"} />;
+ this.isContentActive();
}
screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth());
@@ -268,7 +147,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" });
@@ -279,7 +157,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" });
@@ -289,9 +167,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;
@@ -299,34 +177,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");
}
@@ -335,15 +213,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" });
}
}
@@ -351,28 +229,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,
- whenActiveChanged: this.whenActiveChanged,
- parentActive: this.props.parentActive,
+ 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>);