aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/DocComponent.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/DocComponent.tsx')
-rw-r--r--src/client/views/DocComponent.tsx145
1 files changed, 95 insertions, 50 deletions
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index dfc298840..73fa6709c 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -2,7 +2,7 @@ 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 { Doc, DocListCast, Field, 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';
@@ -11,25 +11,68 @@ 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';
+import { FieldViewProps, FocusViewOptions } from './nodes/FieldView';
+import { DocumentView, OpenWhere } from './nodes/DocumentView';
+import { PinProps } from './nodes/trails';
+import { RefField } from '../../fields/RefField';
+import { DragManager } from '../util/DragManager';
-/// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView)
+/**
+ * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc)
+ * Many of these methods only make sense for specific viewBox'es, but they should be written to
+ * be as general as possible
+ */
+export interface ViewBoxInterface {
+ fieldKey?: string;
+ annotationKey?: string;
+ updateIcon?: () => void; // updates the icon representation of the document
+ getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
+ restoreView?: (viewSpec: Doc) => boolean;
+ scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt<number>; // returns the duration of the focus
+ brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
+ getView?: (doc: Doc, options: FocusViewOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
+ addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
+ addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
+ select?: (ctrlKey: boolean, shiftKey: boolean) => void;
+ focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
+ isAnyChildContentActive?: () => boolean; // is any child content of the document active
+ onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
+ getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+ setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+ playFrom?: (time: number, endTime?: number) => void;
+ Pause?: () => void; // pause a media document (eg, audio/video)
+ IsPlaying?: () => boolean; // is a media document playing
+ TogglePause?: (keep?: boolean) => void; // toggle media document playing state
+ setFocus?: () => void; // sets input focus to the componentView
+ setData?: (data: Field | Promise<RefField | undefined>) => boolean;
+ componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
+ dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
+ dragConfig?: (dragData: DragManager.DocumentDragData) => void;
+ incrementalRendering?: () => void;
+ infoUI?: () => JSX.Element | null;
+ screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
+ ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
+ ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
+ snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
+ search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
+}
+/**
+ * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's
+ * (note: this should not be used for the 'Box' views that render the contents of Doc views)
+ * Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal)
+ * */
export interface DocComponentProps {
Document: Doc;
LayoutTemplate?: () => Opt<Doc>;
LayoutTemplateString?: string;
- ScreenToLocalTransform: () => Transform;
}
export function DocComponent<P extends DocComponentProps>() {
class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
- constructor(props: any) {
+ constructor(props: P) {
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;
@@ -40,27 +83,30 @@ export function DocComponent<P extends DocComponentProps>() {
}
// 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 this.Document[DocData];
}
}
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<P extends ViewBoxBaseProps>() {
+/**
+ * base class for non-annotatable views that render the interior contents of a DocumentView.
+ * this unpacks the Document/layout/data docs as well as the fieldKey being rendered,
+ * and provides accessors for DocumentView and ScreenToLocalBoxXf
+ * Example views include: InkingStroke, FontIconBox, EquationBox, etc
+ */
+export function ViewBoxBaseComponent<P extends FieldViewProps>() {
class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
+ constructor(props: P) {
+ super(props);
+ makeObservable(this);
+ }
+
ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
+ get DocumentView() {
+ return this._props.DocumentView;
+ }
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
get Document() {
return this._props.Document;
@@ -81,31 +127,31 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps>() {
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<P extends ViewBoxAnnotatableProps>() {
+/**
+ * base class for annotatable views that render the interior contents of a DocumentView
+ * This does what ViewBoxBaseComponent does and additionally provides accessor for the
+ * field key where annotations are stored as well as add/move/remove methods for handing
+ * annotations.
+ * This also provides methods to determine when the contents should be interactive
+ * (respond to pointerEvents) such as when the DocumentView container is selected or a
+ * peer child of the container is selected
+ * Example views include: PDFBox, ImageBox, MapBox, etc
+ */
+export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
class Component extends ObservableReactComponent<React.PropsWithChildren<P>> {
- constructor(props: any) {
+ @observable _annotationKeySuffix = () => 'annotations';
+ @observable _isAnyChildContentActive = false;
+
+ constructor(props: P) {
super(props);
makeObservable(this);
}
ScreenToLocalBoxXf = () => this._props.ScreenToLocalTransform();
- @observable _annotationKeySuffix = () => 'annotations';
- @observable _isAnyChildContentActive = false;
+ get DocumentView() {
+ return this._props.DocumentView;
+ }
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document() {
return this._props.Document;
@@ -123,15 +169,12 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
@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 {
+ removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: 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);
@@ -145,10 +188,10 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
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.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
+ Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc, true);
doc.embedContainer = undefined;
- if (recent) {
+ if (recent && !dontAddToRemoved) {
doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
}
});
@@ -173,7 +216,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
}
const first = doc instanceof Doc ? doc : doc[0];
if (!first?._dragOnlyWithinContainer && addDocument !== returnFalse) {
- return this.removeDocument(doc, annotationKey, false) && addDocument(doc, annotationKey);
+ return this.removeDocument(doc, annotationKey, false, true) && addDocument(doc, annotationKey);
}
return false;
};
@@ -194,13 +237,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
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;
+ if (annotationKey ?? this._annotationKeySuffix()) doc[DocData].annotationOn = this.Document;
+ else doc[DocData].annotationOn = undefined;
Doc.SetContainer(doc, this.Document);
inheritParentAcls(targetDataDoc, doc, true);
});
- const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
+ const annoDocs = Doc.Get(targetDataDoc, annotationKey ?? this.annotationKey, true) as List<Doc>; // get the dataDoc directly ... when using templates there may be some default items already there, but we can't change them. maybe we should copy them over, though...
if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add)));
else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField();
@@ -209,6 +252,8 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
return true;
};
+ isAnyChildContentActive = () => this._isAnyChildContentActive;
+
whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)));
}
return Component;