aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/AudioBox.tsx89
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx4
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx4
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx1
-rw-r--r--src/client/views/nodes/DocumentView.tsx2528
-rw-r--r--src/client/views/nodes/EquationBox.scss34
-rw-r--r--src/client/views/nodes/FilterBox.tsx33
-rw-r--r--src/client/views/nodes/LabelBox.tsx20
-rw-r--r--src/client/views/nodes/LinkDocPreview.scss6
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx11
-rw-r--r--src/client/views/nodes/PDFBox.tsx6
-rw-r--r--src/client/views/nodes/VideoBox.scss23
-rw-r--r--src/client/views/nodes/VideoBox.tsx5
-rw-r--r--src/client/views/nodes/WebBox.tsx2
-rw-r--r--src/client/views/nodes/WebBoxRenderer.js4
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx92
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx6
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx59
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx6
-rw-r--r--src/client/views/nodes/trails/PresBox.scss7
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx291
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss80
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx496
23 files changed, 2054 insertions, 1753 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index d97cb6f84..669622455 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -4,8 +4,6 @@ import { action, computed, IReactionDisposer, observable, runInAction } from "mo
import { observer } from "mobx-react";
import { DateField } from "../../../fields/DateField";
import { Doc, DocListCast } from "../../../fields/Doc";
-import { documentSchema } from "../../../fields/documentSchemas";
-import { makeInterface } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, DateCast, NumCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
@@ -84,12 +82,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@computed get miniPlayer() { return this.props.PanelHeight() < 50; } // used to collapse timeline when node is shrunk
@computed get links() { return DocListCast(this.dataDoc.links); }
@computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct recording time
- @computed get mediaState() { return this.layoutDoc.mediaState as media_state; }
+ @computed get mediaState() { return this.dataDoc.mediaState as media_state; }
@computed get path() { // returns the path of the audio file
const path = Cast(this.props.Document[this.fieldKey], AudioField, null)?.url.href || "";
return path === nullAudio ? "" : path;
}
- set mediaState(value) { this.layoutDoc.mediaState = value; }
+ set mediaState(value) { this.dataDoc.mediaState = value; }
@computed get timeline() { return this._stackedTimeline; } // returns CollectionStackedTimeline ref
@@ -237,9 +235,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this.dataDoc[this.fieldKey + "-recordingStart"] = new DateField();
DocUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
- console.log("Data available", e);
const [{ result }] = await Networking.UploadFilesToServer(e.data);
- console.log("Data result", result);
if (!(result instanceof Error)) {
this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client);
}
@@ -298,11 +294,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// button for starting and stopping the recording
- Record = (e: React.MouseEvent) => {
- if (e.button === 0 && !e.ctrlKey) {
+ Record = (e: React.PointerEvent) => {
+ e.button === 0 && !e.ctrlKey && setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
this._recorder ? this.stopRecording() : this.recordAudioAnnotation();
- e.stopPropagation();
- }
+ }), false);
}
// for play button
@@ -340,24 +335,28 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// for dictation button, creates a text document for dictation
onFile = (e: any) => {
- const newDoc = CurrentUserUtils.GetNewTextDoc(
- "",
- NumCast(this.rootDoc.x),
- NumCast(this.rootDoc.y) +
- NumCast(this.layoutDoc._height) +
- 10,
- NumCast(this.layoutDoc._width),
- 2 * NumCast(this.layoutDoc._height)
- );
- Doc.GetProto(newDoc).recordingSource = this.dataDoc;
- Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(
- `self.recordingSource["${this.fieldKey}-recordingStart"]`
- );
- Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(
- "self.recordingSource.mediaState"
- );
- this.props.addDocument?.(newDoc);
- e.stopPropagation();
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+ const newDoc = CurrentUserUtils.GetNewTextDoc(
+ "",
+ NumCast(this.rootDoc.x),
+ NumCast(this.rootDoc.y) +
+ NumCast(this.layoutDoc._height) +
+ 10,
+ NumCast(this.layoutDoc._width),
+ 2 * NumCast(this.layoutDoc._height)
+ );
+ Doc.GetProto(newDoc).recordingSource = this.dataDoc;
+ Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`);
+ Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
+ const overlayDoc = Doc.UserDoc().myOverlayDocs as Doc;
+ if (DocListCast(overlayDoc[Doc.LayoutFieldKey(overlayDoc)]).includes(this.rootDoc)) {
+ newDoc.x = this.rootDoc.x;
+ newDoc.y = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
+ Doc.AddDocToList(overlayDoc, undefined, newDoc);
+ } else {
+ this.props.addDocument?.(newDoc);
+ }
+ }), false);
}
@@ -370,21 +369,21 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// pause the time during recording phase
- @action
- recordPause = (e: React.MouseEvent) => {
- this._pauseStart = new Date().getTime();
- this._paused = true;
- this._recorder.pause();
- e.stopPropagation();
+ recordPause = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+ this._pauseStart = new Date().getTime();
+ this._paused = true;
+ this._recorder.pause();
+ }), false);
}
// continue the recording
- @action
- recordPlay = (e: React.MouseEvent) => {
- this._pauseEnd = new Date().getTime();
- this._paused = false;
- this._recorder.resume();
- e.stopPropagation();
+ recordPlay = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => {
+ this._pauseEnd = new Date().getTime();
+ this._paused = false;
+ this._recorder.resume();
+ }), false);
}
@@ -503,19 +502,19 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
// UI for recording, initially displayed when new audio created in Dash
@computed get recordingControls() {
return <div className="audiobox-recorder">
- <div className="audiobox-dictation" onClick={this.onFile}>
+ <div className="audiobox-dictation" onPointerDown={this.onFile}>
<FontAwesomeIcon
size="2x"
icon="file-alt" />
</div>
{[media_state.Recording, media_state.Playing].includes(this.mediaState) ?
<div className="recording-controls" onClick={e => e.stopPropagation()}>
- <div className="record-button" onClick={this.Record}>
+ <div className="record-button" onPointerDown={this.Record}>
<FontAwesomeIcon
size="2x"
icon="stop" />
</div>
- <div className="record-button" onClick={this._paused ? this.recordPlay : this.recordPause}>
+ <div className="record-button" onPointerDown={this._paused ? this.recordPlay : this.recordPause}>
<FontAwesomeIcon
size="2x"
icon={this._paused ? "play" : "pause"} />
@@ -525,7 +524,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
</div>
</div>
:
- <div className="audiobox-start-record">
+ <div className="audiobox-start-record" onPointerDown={this.Record}>
<FontAwesomeIcon icon="microphone" />
RECORD
</div>}
@@ -611,7 +610,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
renderDepth={this.props.renderDepth + 1}
startTag={"_timecodeToShow" /* audioStart */}
endTag={"_timecodeToHide" /* audioEnd */}
- focus={DocUtils.DefaultFocus}
bringToFront={emptyFunction}
CollectionView={undefined}
playFrom={this.playFrom}
@@ -653,7 +651,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
ref={this.setupTimelineDrop}
className="audiobox-container"
onContextMenu={this.specificContextMenu}
- onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined}
style={{ pointerEvents: this.layoutDoc._lockedPosition ? "none" : undefined }}
>
{!this.path ? this.recordingControls : this.playbackControls}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 5a0ab9110..bedc97575 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -6,7 +6,7 @@ import { listSpec } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { DashColor, numberRange } from "../../../Utils";
+import { DashColor, numberRange, OmitKeys } from "../../../Utils";
import { DocumentManager } from "../../util/DocumentManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
@@ -170,7 +170,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this.panelWidth(),
height: this.panelHeight(),
transform: this.transform,
- transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
+ transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
zIndex: this.ZInd,
mixBlendMode: mixBlendMode,
display: this.ZInd === -99 ? "none" : undefined
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index a9c998757..56de2d1fc 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -52,7 +52,7 @@ export class DocumentIconContainer extends React.Component {
};
},
getVars() {
- const docs = DocumentManager.Instance.DocumentViews;
+ const docs = Array.from(DocumentManager.Instance.DocumentViews);
const capturedVariables: { [name: string]: Field } = {};
usedDocuments.forEach(index => capturedVariables[`d${index}`] = docs[index].props.Document);
return { capturedVariables };
@@ -60,6 +60,6 @@ export class DocumentIconContainer extends React.Component {
};
}
render() {
- return DocumentManager.Instance.DocumentViews.map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
+ return Array.from(DocumentManager.Instance.DocumentViews).map((dv, i) => <DocumentIcon key={i} index={i} view={dv} />);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 7f69adf6c..78d35ab99 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -17,7 +17,6 @@ import { DocumentView } from "./DocumentView";
import { LinkDescriptionPopup } from "./LinkDescriptionPopup";
import { TaskCompletionBox } from "./TaskCompletedBox";
import React = require("react");
-import { DocumentType } from "../../documents/DocumentTypes";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ddadde52a..41a64d6a9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -14,7 +14,7 @@ import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from "../../.
import { AudioField } from "../../../fields/URLField";
import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
-import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnTrue, returnVal, simulateMouseClick, Utils } from "../../../Utils";
+import { emptyFunction, hasDescendantTarget, lightOrDark, OmitKeys, returnEmptyString, returnTrue, returnFalse, returnVal, simulateMouseClick, Utils } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
@@ -54,1347 +54,1351 @@ import { FieldViewProps } from "./FieldView";
const { Howl } = require('howler');
interface Window {
- MediaRecorder: MediaRecorder;
+ MediaRecorder: MediaRecorder;
}
declare class MediaRecorder {
- // whatever MediaRecorder has
- constructor(e: any);
+ // whatever MediaRecorder has
+ constructor(e: any);
}
export enum ViewAdjustment {
- resetView = 1,
- doNothing = 0
+ resetView = 1,
+ doNothing = 0
}
export const ViewSpecPrefix = "viewSpec"; // field prefix for anchor fields that are immediately copied over to the target document when link is followed. Other anchor properties will be copied over in the specific setViewSpec() method on their view (which allows for seting preview values instead of writing to the document)
export interface DocFocusOptions {
- originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
- willZoom?: boolean; // determines whether to zoom in on target document
- scale?: number; // percent of containing frame to zoom into document
- afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document
- docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
- instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
+ originalTarget?: Doc; // set in JumpToDocument, used by TabDocView to determine whether to fit contents to tab
+ willZoom?: boolean; // determines whether to zoom in on target document
+ scale?: number; // percent of containing frame to zoom into document
+ afterFocus?: DocAfterFocusFunc; // function to call after focusing on a document
+ docTransform?: Transform; // when a document can't be panned and zoomed within its own container (say a group), then we need to continue to move up the render hierarchy to find something that can pan and zoom. when this happens the docTransform must accumulate all the transforms of each level of the hierarchy
+ instant?: boolean; // whether focus should happen instantly (as opposed to smooth zoom)
}
export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>;
export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void;
export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any;
export interface DocComponentView {
- updateIcon?: () => void; // updates the icon representation of the document
- getAnchor?: () => 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)
- scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
- setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
- reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
- shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
- menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
- isAnyChildContentActive?: () => boolean; // is any child content of the document active
- 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;
- setFocus?: () => void;
- componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
- fieldKey?: string;
- annotationKey?: string;
- getTitle?: () => string;
- getScrollHeight?: () => number;
- getCenter?: (xf: Transform) => { 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;
+ updateIcon?: () => void; // updates the icon representation of the document
+ getAnchor?: () => 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)
+ scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus
+ setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
+ reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
+ shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views
+ menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
+ isAnyChildContentActive?: () => boolean; // is any child content of the document active
+ 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;
+ setFocus?: () => void;
+ componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
+ fieldKey?: string;
+ annotationKey?: string;
+ getTitle?: () => string;
+ getScrollHeight?: () => number;
+ getCenter?: (xf: Transform) => { 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;
}
// These props are passed to both FieldViews and DocumentViews
export interface DocumentViewSharedProps {
- fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
- DocumentView?: () => DocumentView;
- renderDepth: number;
- Document: Doc;
- DataDoc?: Doc;
- fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
- ContainingCollectionView: Opt<CollectionView>;
- ContainingCollectionDoc: Opt<Doc>;
- suppressSetHeight?: boolean;
- thumbShown?: () => boolean;
- isHovering?: () => boolean;
- setContentView?: (view: DocComponentView) => any;
- CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
- PanelWidth: () => number;
- PanelHeight: () => number;
- docViewPath: () => DocumentView[];
- childHideDecorationTitle?: () => boolean;
- childHideResizeHandles?: () => boolean;
- dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
- styleProvider: Opt<StyleProviderFunc>;
- focus: DocFocusFunc;
- fitWidth?: (doc: Doc) => boolean;
- docFilters: () => string[];
- docRangeFilters: () => string[];
- searchFilterDocs: () => Doc[];
- showTitle?: () => string;
- whenChildContentsActiveChanged: (isActive: boolean) => void;
- rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
- addDocTab: (doc: Doc, where: string) => boolean;
- 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)
- addDocument?: (doc: Doc | Doc[]) => boolean;
- removeDocument?: (doc: Doc | Doc[]) => boolean;
- moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
- pinToPres: (document: Doc) => void;
- ScreenToLocalTransform: () => Transform;
- bringToFront: (doc: Doc, sendToBack?: boolean) => void;
- dropAction?: dropActionType;
- dontRegisterView?: boolean;
- hideLinkButton?: boolean;
- hideCaptions?: boolean;
- ignoreAutoHeight?: boolean;
- forceAutoHeight?: boolean;
- disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
- pointerEvents?: () => Opt<string>;
- scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
- createNewFilterDoc?: () => void;
- updateFilterDoc?: (doc: Doc) => void;
+ fieldKey?: string; // only used by FieldViews but helpful here to allow styleProviders to access fieldKey of FieldViewProps. In priniciple, passing a fieldKey to a documentView could override or be the default fieldKey for fieldViews
+ DocumentView?: () => DocumentView;
+ renderDepth: number;
+ Document: Doc;
+ DataDoc?: Doc;
+ fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
+ ContainingCollectionView: Opt<CollectionView>;
+ ContainingCollectionDoc: Opt<Doc>;
+ suppressSetHeight?: boolean;
+ thumbShown?: () => boolean;
+ isHovering?: () => boolean;
+ setContentView?: (view: DocComponentView) => any;
+ CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ docViewPath: () => DocumentView[];
+ childHideDecorationTitle?: () => boolean;
+ childHideResizeHandles?: () => boolean;
+ dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt
+ styleProvider: Opt<StyleProviderFunc>;
+ focus: DocFocusFunc;
+ fitWidth?: (doc: Doc) => boolean;
+ docFilters: () => string[];
+ docRangeFilters: () => string[];
+ searchFilterDocs: () => Doc[];
+ showTitle?: () => string;
+ whenChildContentsActiveChanged: (isActive: boolean) => void;
+ rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected
+ addDocTab: (doc: Doc, where: string) => boolean;
+ 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)
+ addDocument?: (doc: Doc | Doc[]) => boolean;
+ removeDocument?: (doc: Doc | Doc[]) => boolean;
+ moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+ pinToPres: (document: Doc) => void;
+ ScreenToLocalTransform: () => Transform;
+ bringToFront: (doc: Doc, sendToBack?: boolean) => void;
+ dropAction?: dropActionType;
+ dontRegisterView?: boolean;
+ hideLinkButton?: boolean;
+ hideCaptions?: boolean;
+ ignoreAutoHeight?: boolean;
+ forceAutoHeight?: boolean;
+ disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+ pointerEvents?: () => Opt<string>;
+ scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+ createNewFilterDoc?: () => void;
+ updateFilterDoc?: (doc: Doc) => void;
}
// these props are specific to DocuentViews
export interface DocumentViewProps extends DocumentViewSharedProps {
- // properties specific to DocumentViews but not to FieldView
- freezeDimensions?: boolean;
- hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
- hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDocumentButtonBar?: boolean;
- hideOpenButton?: boolean;
- hideDeleteButton?: boolean;
- treeViewDoc?: Doc;
- isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
- isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
- contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
- radialMenu?: String[];
- LayoutTemplateString?: string;
- dontCenter?: "x" | "y" | "xy";
- dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
- ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
- NativeWidth?: () => number;
- NativeHeight?: () => number;
- LayoutTemplate?: () => Opt<Doc>;
- contextMenuItems?: () => { script: ScriptField, filter?: ScriptField, label: string, icon: string }[];
- onClick?: () => ScriptField;
- onDoubleClick?: () => ScriptField;
- onPointerDown?: () => ScriptField;
- onPointerUp?: () => ScriptField;
- onBrowseClick?: () => (ScriptField | undefined);
- onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined);
+ // properties specific to DocumentViews but not to FieldView
+ freezeDimensions?: boolean;
+ hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected
+ hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
+ hideDocumentButtonBar?: boolean;
+ hideOpenButton?: boolean;
+ hideDeleteButton?: boolean;
+ treeViewDoc?: Doc;
+ isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+ isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+ contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
+ radialMenu?: String[];
+ LayoutTemplateString?: string;
+ dontCenter?: "x" | "y" | "xy";
+ dontScaleFilter?: (doc: Doc) => boolean; // decides whether a document can be scaled to fit its container vs native size with scrolling
+ ContentScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ LayoutTemplate?: () => Opt<Doc>;
+ contextMenuItems?: () => { script: ScriptField, filter?: ScriptField, label: string, icon: string }[];
+ onClick?: () => ScriptField;
+ onDoubleClick?: () => ScriptField;
+ onPointerDown?: () => ScriptField;
+ onPointerUp?: () => ScriptField;
+ onBrowseClick?: () => (ScriptField | undefined);
+ onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined);
}
// these props are only available in DocumentViewIntenral
export interface DocumentViewInternalProps extends DocumentViewProps {
- NativeWidth: () => number;
- NativeHeight: () => number;
- isSelected: (outsideReaction?: boolean) => boolean;
- select: (ctrlPressed: boolean) => void;
- DocumentView: () => DocumentView;
- viewPath: () => DocumentView[];
+ NativeWidth: () => number;
+ NativeHeight: () => number;
+ isSelected: (outsideReaction?: boolean) => boolean;
+ select: (ctrlPressed: boolean) => void;
+ DocumentView: () => DocumentView;
+ viewPath: () => DocumentView[];
}
@observer
export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
- public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
- _animateScaleTime = 300; // milliseconds;
- @observable _animateScalingTo = 0;
- @observable _mediaState = 0;
- @observable _pendingDoubleClick = false;
- private _disposers: { [name: string]: IReactionDisposer } = {};
- private _downX: number = 0;
- private _downY: number = 0;
- private _firstX: number = -1;
- private _firstY: number = -1;
- private _lastTap: number = 0;
- private _doubleTap = false;
- private _mainCont = React.createRef<HTMLDivElement>();
- private _titleRef = React.createRef<EditableView>();
- private _timeout: NodeJS.Timeout | undefined;
- private _dropDisposer?: DragManager.DragDropDisposer;
- private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
- protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
- @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
-
- private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; }
- public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
- public get ContentDiv() { return this._mainCont.current; }
- public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); }
- @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
- @computed get ContentScale() { return this.props.ContentScaling?.() || 1; }
- @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); }
- @computed get hidden() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); }
- @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); }
- @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); }
- @computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); }
- @computed get hideLinkButton() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.props.isSelected() ? ":selected" : "")); }
- @computed get widgetDecorations() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ":selected" : "")); }
- @computed get backgroundColor() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); }
- @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
- @computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
- @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; }
- @computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
- @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
- @computed get nativeWidth() { return this.props.NativeWidth(); }
- @computed get nativeHeight() { return this.props.NativeHeight(); }
- @computed get onClickHandler() { return this.props.onClick?.() ?? (this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null))); }
- @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
- @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
- @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
-
-
- componentWillUnmount() { this.cleanupHandlers(true); }
- componentDidMount() { this.setupHandlers(); }
- //componentDidUpdate() { this.setupHandlers(); }
- setupHandlers() {
- this.cleanupHandlers(false);
- if (this._mainCont.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
- this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
- this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
- }
- }
- @action
- cleanupHandlers(unbrush: boolean) {
- this._dropDisposer?.();
- this._multiTouchDisposer?.();
- this._holdDisposer?.();
- unbrush && Doc.UnBrushDoc(this.props.Document);
- Object.values(this._disposers).forEach(disposer => disposer?.());
- }
-
- handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
- this.removeMoveListeners();
- this.removeEndListeners();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- if (RadialMenu.Instance._display === false) {
- this.addHoldMoveListeners();
- this.addHoldEndListeners();
- this.onRadialMenu(e, me);
- const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
- this._firstX = pt.pageX;
- this._firstY = pt.pageY;
- }
- }
-
- handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+ public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
+ _animateScaleTime = 300; // milliseconds;
+ @observable _animateScalingTo = 0;
+ @observable _mediaState = 0;
+ @observable _pendingDoubleClick = false;
+ private _disposers: { [name: string]: IReactionDisposer } = {};
+ private _downX: number = 0;
+ private _downY: number = 0;
+ private _firstX: number = -1;
+ private _firstY: number = -1;
+ private _lastTap: number = 0;
+ private _doubleTap = false;
+ private _mainCont = React.createRef<HTMLDivElement>();
+ private _titleRef = React.createRef<EditableView>();
+ private _timeout: NodeJS.Timeout | undefined;
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
+
+ private get topMost() { return this.props.renderDepth === 0 && !LightboxView.LightboxDoc; }
+ public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+ public get ContentDiv() { return this._mainCont.current; }
+ public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); }
+ @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
+ @computed get ContentScale() { return this.props.ContentScaling?.() || 1; }
+ @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); }
+ @computed get hidden() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); }
+ @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); }
+ @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); }
+ @computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); }
+ @computed get hideLinkButton() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.props.isSelected() ? ":selected" : "")); }
+ @computed get widgetDecorations() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ":selected" : "")); }
+ @computed get backgroundColor() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor); }
+ @computed get docContents() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents); }
+ @computed get headerMargin() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
+ @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; }
+ @computed get pointerEvents() { return this.props.styleProvider?.(this.Document, this.props, StyleProp.PointerEvents + (this.props.isSelected() ? ":selected" : "")); }
+ @computed get finalLayoutKey() { return StrCast(this.Document.layoutKey, "layout"); }
+ @computed get nativeWidth() { return this.props.NativeWidth(); }
+ @computed get nativeHeight() { return this.props.NativeHeight(); }
+ @computed get onClickHandler() { return this.props.onClick?.() ?? (this.props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null))); }
+ @computed get onDoubleClickHandler() { return this.props.onDoubleClick?.() ?? (Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick); }
+ @computed get onPointerDownHandler() { return this.props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown); }
+ @computed get onPointerUpHandler() { return this.props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp); }
+
+ componentWillUnmount() { this.cleanupHandlers(true); }
+ componentDidMount() { this.setupHandlers(); }
+ //componentDidUpdate() { this.setupHandlers(); }
+ setupHandlers() {
+ this.cleanupHandlers(false);
+ if (this._mainCont.current) {
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document);
+ this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this));
+ this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this));
+ }
+ }
+ @action
+ cleanupHandlers(unbrush: boolean) {
+ this._dropDisposer?.();
+ this._multiTouchDisposer?.();
+ this._holdDisposer?.();
+ unbrush && Doc.UnBrushDoc(this.props.Document);
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+ }
+
+ handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): any => {
+ this.removeMoveListeners();
+ this.removeEndListeners();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (RadialMenu.Instance._display === false) {
+ this.addHoldMoveListeners();
+ this.addHoldEndListeners();
+ this.onRadialMenu(e, me);
const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
-
- if (this._firstX === -1 || this._firstY === -1) {
- return;
- }
- if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
- this.handle1PointerHoldEnd(e, me);
- }
- }
-
- handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
- this.removeHoldMoveListeners();
- this.removeHoldEndListeners();
- RadialMenu.Instance.closeMenu();
- this._firstX = -1;
- this._firstY = -1;
- SelectionManager.DeselectAll();
- me.touchEvent.stopPropagation();
- me.touchEvent.preventDefault();
+ this._firstX = pt.pageX;
+ this._firstY = pt.pageY;
+ }
+ }
+
+ handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+ const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
+
+ if (this._firstX === -1 || this._firstY === -1) {
+ return;
+ }
+ if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
+ this.handle1PointerHoldEnd(e, me);
+ }
+ }
+
+ handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent<TouchEvent>): void => {
+ this.removeHoldMoveListeners();
+ this.removeHoldEndListeners();
+ RadialMenu.Instance.closeMenu();
+ this._firstX = -1;
+ this._firstY = -1;
+ SelectionManager.DeselectAll();
+ me.touchEvent.stopPropagation();
+ me.touchEvent.preventDefault();
+ e.stopPropagation();
+ if (RadialMenu.Instance.used) {
+ this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY);
+ }
+ }
+
+ handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
+ if (!e.nativeEvent.cancelBubble && !this.props.isSelected()) {
e.stopPropagation();
- if (RadialMenu.Instance.used) {
- this.onContextMenu(undefined, me.touches[0].pageX, me.touches[0].pageY);
- }
- }
+ e.preventDefault();
- handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble && !this.props.isSelected()) {
- e.stopPropagation();
- e.preventDefault();
-
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ }
+ }
+
+ handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
+ SelectionManager.DeselectAll();
+ if (this.Document.onPointerDown) return;
+ const touch = me.touchEvent.changedTouches.item(0);
+ if (touch) {
+ this._downX = touch.clientX;
+ this._downY = touch.clientY;
+ if (!e.nativeEvent.cancelBubble) {
+ if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation();
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ e.stopPropagation();
}
- }
+ }
+ }
- handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- SelectionManager.DeselectAll();
- if (this.Document.onPointerDown) return;
+ handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
+ if (e.cancelBubble && this.props.isDocumentActive?.()) {
+ this.removeMoveListeners();
+ }
+ else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
const touch = me.touchEvent.changedTouches.item(0);
- if (touch) {
- this._downX = touch.clientX;
- this._downY = touch.clientY;
- if (!e.nativeEvent.cancelBubble) {
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation();
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- e.stopPropagation();
- }
+ if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
+ if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
+ this.cleanUpInteractions();
+ this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
+ }
}
- }
-
- handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- if (e.cancelBubble && this.props.isDocumentActive?.()) {
- this.removeMoveListeners();
- }
- else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- const touch = me.touchEvent.changedTouches.item(0);
- if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
- if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
- this.cleanUpInteractions();
- this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined);
- }
- }
- e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- e.preventDefault();
- }
- }
-
- @action
- handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
- const oldPoint1 = this.prevPoints.get(pt1.identifier);
- const oldPoint2 = this.prevPoints.get(pt2.identifier);
- const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
- if (pinching !== 0 && oldPoint1 && oldPoint2) {
- const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
- const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
- const dX = -1 * Math.sign(dW);
- const dY = -1 * Math.sign(dH);
-
- if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = Document(this.props.Document);
- const layoutDoc = Document(Doc.Layout(this.props.Document));
- let nwidth = Doc.NativeWidth(layoutDoc);
- let nheight = Doc.NativeHeight(layoutDoc);
- const width = (layoutDoc._width || 0);
- const height = (layoutDoc._height || (nheight / nwidth * width));
- const scale = this.props.ScreenToLocalTransform().Scale * this.ContentScale;
- const actualdW = Math.max(width + (dW * scale), 20);
- const actualdH = Math.max(height + (dH * scale), 20);
- doc.x = (doc.x || 0) + dX * (actualdW - width);
- doc.y = (doc.y || 0) + dY * (actualdH - height);
- const fixedAspect = e.ctrlKey || (nwidth && nheight);
- if (fixedAspect && (!nwidth || !nheight)) {
- Doc.SetNativeWidth(layoutDoc, nwidth = layoutDoc._width || 0);
- Doc.SetNativeHeight(layoutDoc, nheight = layoutDoc._height || 0);
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
+ }
+ }
+
+ @action
+ handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
+ const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
+ const pt1 = myTouches[0];
+ const pt2 = myTouches[1];
+ const oldPoint1 = this.prevPoints.get(pt1.identifier);
+ const oldPoint2 = this.prevPoints.get(pt2.identifier);
+ const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
+ if (pinching !== 0 && oldPoint1 && oldPoint2) {
+ const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
+ const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
+ const dX = -1 * Math.sign(dW);
+ const dY = -1 * Math.sign(dH);
+
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+ const doc = Document(this.props.Document);
+ const layoutDoc = Document(Doc.Layout(this.props.Document));
+ let nwidth = Doc.NativeWidth(layoutDoc);
+ let nheight = Doc.NativeHeight(layoutDoc);
+ const width = (layoutDoc._width || 0);
+ const height = (layoutDoc._height || (nheight / nwidth * width));
+ const scale = this.props.ScreenToLocalTransform().Scale * this.ContentScale;
+ const actualdW = Math.max(width + (dW * scale), 20);
+ const actualdH = Math.max(height + (dH * scale), 20);
+ doc.x = (doc.x || 0) + dX * (actualdW - width);
+ doc.y = (doc.y || 0) + dY * (actualdH - height);
+ const fixedAspect = e.ctrlKey || (nwidth && nheight);
+ if (fixedAspect && (!nwidth || !nheight)) {
+ Doc.SetNativeWidth(layoutDoc, nwidth = layoutDoc._width || 0);
+ Doc.SetNativeHeight(layoutDoc, nheight = layoutDoc._height || 0);
+ }
+ if (nwidth > 0 && nheight > 0) {
+ if (Math.abs(dW) > Math.abs(dH)) {
+ if (!fixedAspect) {
+ Doc.SetNativeWidth(layoutDoc, actualdW / (layoutDoc._width || 1) * Doc.NativeWidth(layoutDoc));
}
- if (nwidth > 0 && nheight > 0) {
- if (Math.abs(dW) > Math.abs(dH)) {
- if (!fixedAspect) {
- Doc.SetNativeWidth(layoutDoc, actualdW / (layoutDoc._width || 1) * Doc.NativeWidth(layoutDoc));
- }
- layoutDoc._width = actualdW;
- if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
- else layoutDoc._height = actualdH;
- }
- else {
- if (!fixedAspect) {
- Doc.SetNativeHeight(layoutDoc, actualdH / (layoutDoc._height || 1) * Doc.NativeHeight(doc));
- }
- layoutDoc._height = actualdH;
- if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
- else layoutDoc._width = actualdW;
- }
- } else {
- dW && (layoutDoc._width = actualdW);
- dH && (layoutDoc._height = actualdH);
- dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
+ layoutDoc._width = actualdW;
+ if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+ else layoutDoc._height = actualdH;
+ }
+ else {
+ if (!fixedAspect) {
+ Doc.SetNativeHeight(layoutDoc, actualdH / (layoutDoc._height || 1) * Doc.NativeHeight(doc));
}
- }
- e.stopPropagation();
- e.preventDefault();
+ layoutDoc._height = actualdH;
+ if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+ else layoutDoc._width = actualdW;
+ }
+ } else {
+ dW && (layoutDoc._width = actualdW);
+ dH && (layoutDoc._height = actualdH);
+ dH && layoutDoc._autoHeight && (layoutDoc._autoHeight = false);
+ }
}
- }
-
- @action
- onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
- const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
- RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
-
- // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
- const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
- (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
- // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
-
- SelectionManager.DeselectAll();
- }
-
- startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
- if (this._mainCont.current) {
- const dragData = new DragManager.DocumentDragData([this.props.Document]);
- const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
- dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
- dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]);
- dragData.offset[1] = Math.min(this.rootDoc[HeightSym](), dragData.offset[1]);
- dragData.dropAction = dropAction;
- dragData.treeViewDoc = this.props.treeViewDoc;
- dragData.removeDocument = this.props.removeDocument;
- dragData.moveDocument = this.props.moveDocument;
- const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
- DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) },
- () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed.
- ffview?.setupDragLines(false);
- }
- }
-
- onKeyDown = (e: React.KeyboardEvent) => {
- if (e.altKey && !e.nativeEvent.cancelBubble) {
- e.stopPropagation();
- e.preventDefault();
- if (e.key === "†" || e.key === "t") {
- if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = "title";
- if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
- else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
- this._titleRef.current?.setIsFocused(false);
- this._componentView?.setFocus?.();
- }
- }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onRadialMenu = (e: Event, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>): void => {
+ const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1];
+ RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15);
+
+ // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "map-pin", selected: -1 });
+ const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]);
+ (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) && RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 });
+ // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 });
+
+ SelectionManager.DeselectAll();
+ }
+
+ startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
+ if (this._mainCont.current) {
+ const dragData = new DragManager.DocumentDragData([this.props.Document]);
+ const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0);
+ dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top);
+ dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]);
+ dragData.offset[1] = Math.min(this.rootDoc[HeightSym](), dragData.offset[1]);
+ dragData.dropAction = dropAction;
+ dragData.treeViewDoc = this.props.treeViewDoc;
+ dragData.removeDocument = this.props.removeDocument;
+ dragData.moveDocument = this.props.moveDocument;
+ const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView()));
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) },
+ () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed.
+ ffview?.setupDragLines(false);
+ }
+ }
+
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.altKey && !e.nativeEvent.cancelBubble) {
+ e.stopPropagation();
+ e.preventDefault();
+ if (e.key === "†" || e.key === "t") {
+ if (!StrCast(this.layoutDoc._showTitle)) this.layoutDoc._showTitle = "title";
+ if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
+ else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
+ this._titleRef.current?.setIsFocused(false);
+ this._componentView?.setFocus?.();
+ }
}
- }
-
- focus = (anchor: Doc, options?: DocFocusOptions) => {
- LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
- // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
- Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
- this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
- });
- // after a timeout, the right _componentView should have been created, so call it to update its view spec values
- setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
- const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
- const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
- this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
- ...options, afterFocus: (didFocus: boolean) =>
- new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
- });
-
- }
- onClick = action((e: React.MouseEvent | React.PointerEvent) => {
- if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
- (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
- let stopPropagate = true;
- let preventDefault = true;
- (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
- if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
- if (this._timeout) {
- clearTimeout(this._timeout);
- this._pendingDoubleClick = false;
- this._timeout = undefined;
- }
- if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
- const { clientX, clientY, shiftKey } = e;
- const func = () => this.onDoubleClickHandler.script.run({
- this: this.layoutDoc,
- self: this.rootDoc,
- scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView(),
- clientX, clientY, shiftKey
- }, console.log);
- UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click");
- } else if (!Doc.IsSystem(this.rootDoc)) {
- UndoManager.RunInBatch(() =>
- LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.())
- , "double tap");
- SelectionManager.DeselectAll();
- Doc.UnBrushDoc(this.props.Document);
- }
- } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
- const { clientX, clientY, shiftKey } = e;
- const func = () => this.onClickHandler.script.run({
- this: this.layoutDoc,
- self: this.rootDoc,
- _readOnly_: false,
- scriptContext: this.props.scriptContext,
- thisContainer: this.props.ContainingCollectionDoc,
- documentView: this.props.DocumentView(),
- clientX, clientY, shiftKey
- }, console.log).result?.select === true ? this.props.select(false) : "";
- const clickFunc = () => this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, "on click");
- if (this.onDoubleClickHandler) {
- runInAction(() => this._pendingDoubleClick = true);
- this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350);
- } else clickFunc();
- } else if (this.allLinks && this.Document.type !== DocumentType.LINK && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
- this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
- } else {
- if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
- stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
- } else {
- runInAction(() => this._pendingDoubleClick = true);
- this._timeout = setTimeout(action(() => { this._pendingDoubleClick = false; this._timeout = undefined; }), 350);
- this.props.select(e.ctrlKey || e.shiftKey);
- }
- preventDefault = false;
- }
- stopPropagate && e.stopPropagation();
- preventDefault && e.preventDefault();
+ }
+ }
+
+ focus = (anchor: Doc, options?: DocFocusOptions) => {
+ LightboxView.SetCookie(StrCast(anchor["cookies-set"]));
+ // copying over VIEW fields immediately allows the view type to switch to create the right _componentView
+ Array.from(Object.keys(Doc.GetProto(anchor))).filter(key => key.startsWith(ViewSpecPrefix)).forEach(spec => {
+ this.layoutDoc[spec.replace(ViewSpecPrefix, "")] = ((field) => field instanceof ObjectField ? ObjectField.MakeCopy(field) : field)(anchor[spec]);
+ });
+ // after a timeout, the right _componentView should have been created, so call it to update its view spec values
+ setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false));
+ const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here
+ const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing;
+ this.props.focus(options?.docTransform ? anchor : this.rootDoc, {
+ ...options, afterFocus: (didFocus: boolean) =>
+ new Promise<ViewAdjustment>(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0))
+ });
+
+ }
+ onClick = action((e: React.MouseEvent | React.PointerEvent) => {
+ if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 &&
+ (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ let stopPropagate = true;
+ let preventDefault = true;
+ const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name);
+ (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc);
+ if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ if (this._timeout) {
+ clearTimeout(this._timeout);
+ this._pendingDoubleClick = false;
+ this._timeout = undefined;
+ }
+ if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ const { clientX, clientY, shiftKey } = e;
+ const func = () => this.onDoubleClickHandler.script.run({
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ scriptContext: this.props.scriptContext,
+ thisContainer: this.props.ContainingCollectionDoc,
+ documentView: this.props.DocumentView(),
+ clientX, clientY, shiftKey
+ }, console.log);
+ UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click");
+ } else if (!Doc.IsSystem(this.rootDoc)) {
+ UndoManager.RunInBatch(() =>
+ LightboxView.AddDocTab(this.rootDoc, "lightbox", this.props.LayoutTemplate?.(), this.props.addDocTab)
+ , "double tap");
+ SelectionManager.DeselectAll();
+ Doc.UnBrushDoc(this.props.Document);
+ }
+ } else if (this.onClickHandler?.script && !isScriptBox()) { // bcz: hack? don't execute script if you're clicking on a scripting box itself
+ const { clientX, clientY, shiftKey } = e;
+ const func = () => this.onClickHandler.script.run({
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ _readOnly_: false,
+ scriptContext: this.props.scriptContext,
+ thisContainer: this.props.ContainingCollectionDoc,
+ documentView: this.props.DocumentView(),
+ clientX, clientY, shiftKey
+ }, console.log).result?.select === true ? this.props.select(false) : "";
+ const clickFunc = () => this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, "on click");
+ if (this.onDoubleClickHandler) {
+ runInAction(() => this._pendingDoubleClick = true);
+ this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350);
+ } else clickFunc();
+ } else if (this.allLinks && this.Document.type !== DocumentType.LINK && !isScriptBox() && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) {
+ this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey);
+ } else {
+ if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
+ stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
+ } else {
+ runInAction(() => this._pendingDoubleClick = true);
+ this._timeout = setTimeout(action(() => { this._pendingDoubleClick = false; this._timeout = undefined; }), 350);
+ this.props.select(e.ctrlKey || e.shiftKey);
+ }
+ preventDefault = false;
}
- });
-
- onPointerDown = (e: React.PointerEvent): void => {
- if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.SelectedTool === InkTool.Eraser) return;
- // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
- if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) {
- if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- e.stopPropagation();
- if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
- // TODO: check here for panning/inking
- }
- return;
+ stopPropagate && e.stopPropagation();
+ preventDefault && e.preventDefault();
+ }
+ });
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.SelectedTool === InkTool.Eraser) return;
+ // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document)
+ if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) {
+ if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ e.stopPropagation();
+ if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it
+ // TODO: check here for panning/inking
}
- this._downX = e.clientX;
- this._downY = e.clientY;
- if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.layoutDoc.onDragStart) &&
- // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
- !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
- !this.props.onBrowseClick?.() &&
- !this.Document.ignoreClick &&
- !e.ctrlKey &&
- (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
- !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- e.stopPropagation();
- // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though
- //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
- }
- if (this.props.isDocumentActive?.()) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- }
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointerup", this.onPointerUp);
+ return;
+ }
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.layoutDoc.onDragStart) &&
+ // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
+ !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
+ if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
+ !this.props.onBrowseClick?.() &&
+ !this.Document.ignoreClick &&
+ !e.ctrlKey &&
+ (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
+ !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+ e.stopPropagation();
+ // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though
+ //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault();
}
- }
-
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) return;
- if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return;
-
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType);
- }
- }
- e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- e.preventDefault();
+ if (this.props.isDocumentActive?.()) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
}
- }
-
- cleanupPointerEvents = () => {
- this.cleanUpInteractions();
- document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- }
-
- onPointerUp = (e: PointerEvent): void => {
- this.cleanupPointerEvents();
-
- if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
- } else {
- this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
- // bcz: this is a placeholder. documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
- if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now();// don't want to process the start of a double tap if the doucment is selected
- }
- }
-
- @undoBatch @action
- toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => {
- this.Document.ignoreClick = false;
- if (setPushpin) {
- this.Document.isPushpin = !this.Document.isPushpin;
- this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton;
- } else {
- this.Document._isLinkButton = !this.Document._isLinkButton;
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
+
+ onPointerMove = (e: PointerEvent): void => {
+ if (e.cancelBubble) return;
+ if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return;
+
+ if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && "alias") || (this.props.dropAction || this.Document.dropAction || undefined) as dropActionType);
+ }
}
- if (this.Document._isLinkButton && !this.onClickHandler) {
- zoom !== undefined && (this.Document.followLinkZoom = zoom);
- this.Document.followLinkLocation = location;
- } else if (this.Document._isLinkButton && this.onClickHandler) {
- this.Document._isLinkButton = false;
- this.Document["onClick-rawScript"] = this.dataDoc["onClick-rawScript"] = this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
- }
- }
- @undoBatch @action
- toggleTargetOnClick = (): void => {
- this.Document.ignoreClick = false;
- this.Document._isLinkButton = true;
- this.Document.isPushpin = true;
- }
- @undoBatch @action
- followLinkOnClick = (location: Opt<string>, zoom: boolean,): void => {
- this.Document.ignoreClick = false;
- this.Document._isLinkButton = true;
- this.Document.isPushpin = false;
- this.Document.followLinkZoom = zoom;
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
+ }
+ }
+
+ cleanupPointerEvents = () => {
+ this.cleanUpInteractions();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerUp = (e: PointerEvent): void => {
+ this.cleanupPointerEvents();
+
+ if (this.onPointerUpHandler?.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log);
+ } else {
+ this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
+ // bcz: this is a placeholder. documents, when selected, should stopPropagation on doubleClicks if they want to keep the DocumentView from getting them
+ if (!this.props.isSelected(true) || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this.rootDoc.type) as any)) this._lastTap = Date.now();// don't want to process the start of a double tap if the doucment is selected
+ }
+ }
+
+ @undoBatch @action
+ toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => {
+ this.Document.ignoreClick = false;
+ if (setPushpin) {
+ this.Document.isPushpin = !this.Document.isPushpin;
+ this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton;
+ } else {
+ this.Document._isLinkButton = !this.Document._isLinkButton;
+ }
+ if (this.Document._isLinkButton && !this.onClickHandler) {
+ zoom !== undefined && (this.Document.followLinkZoom = zoom);
this.Document.followLinkLocation = location;
- }
- @undoBatch @action
- selectOnClick = (): void => {
- this.Document.ignoreClick = false;
- this.Document._isLinkButton = false;
- this.Document.isPushpin = false;
- this.Document.onClick = this.layoutDoc.onClick = undefined;
- }
- @undoBatch
- noOnClick = (): void => {
- this.Document.ignoreClick = false;
+ } else if (this.Document._isLinkButton && this.onClickHandler) {
this.Document._isLinkButton = false;
- }
-
- @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
- @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace("layout_", "")}")`, { documentView: "any" });
-
- @undoBatch @action
- drop = async (e: Event, de: DragManager.DropEvent) => {
- if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
- if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
- alert((e.target as any)?.closest?.("*.lm_content") ?
- "You can't perform this move most likely because you don't have permission to modify the destination." :
- "Linking to document tabs not yet supported. Drop link on document content.");
- return;
+ this.Document["onClick-rawScript"] = this.dataDoc["onClick-rawScript"] = this.dataDoc.onClick = this.Document.onClick = this.layoutDoc.onClick = undefined;
+ }
+ }
+ @undoBatch @action
+ toggleTargetOnClick = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = true;
+ this.Document.isPushpin = true;
+ }
+ @undoBatch @action
+ followLinkOnClick = (location: Opt<string>, zoom: boolean,): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = true;
+ this.Document.isPushpin = false;
+ this.Document.followLinkZoom = zoom;
+ this.Document.followLinkLocation = location;
+ }
+ @undoBatch @action
+ selectOnClick = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = false;
+ this.Document.isPushpin = false;
+ this.Document.onClick = this.layoutDoc.onClick = undefined;
+ }
+ @undoBatch
+ noOnClick = (): void => {
+ this.Document.ignoreClick = false;
+ this.Document._isLinkButton = false;
+ }
+
+ @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
+ @undoBatch setToggleDetail = () => this.Document.onClick = ScriptField.MakeScript(`toggleDetail(documentView, "${StrCast(this.Document.layoutKey).replace("layout_", "")}")`, { documentView: "any" });
+
+ @undoBatch @action
+ drop = async (e: Event, de: DragManager.DropEvent) => {
+ if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return;
+ if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
+ alert((e.target as any)?.closest?.("*.lm_content") ?
+ "You can't perform this move most likely because you don't have permission to modify the destination." :
+ "Linking to document tabs not yet supported. Drop link on document content.");
+ return;
+ }
+ const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
+ if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
+ if (linkdrag?.linkSourceDoc) {
+ e.stopPropagation();
+ if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
+ de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
}
- const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
- if (linkdrag) linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
- if (linkdrag?.linkSourceDoc) {
- e.stopPropagation();
- if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
- de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
- }
- if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
- const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document;
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]);
- }
+ if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) {
+ const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document;
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]);
}
- }
-
- @undoBatch
- @action
- makeIntoPortal = async () => {
- const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
- if (!portalLink) {
- const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
- DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from");
+ }
+ }
+
+ @undoBatch
+ @action
+ makeIntoPortal = async () => {
+ const portalLink = this.allLinks.find(d => d.anchor1 === this.props.Document);
+ if (!portalLink) {
+ const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" });
+ DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from");
+ }
+ this.Document.followLinkLocation = "inPlace";
+ this.Document.followLinkZoom = true;
+ this.Document._isLinkButton = true;
+ }
+
+ @action
+ onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
+ if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
+ e.preventDefault();
+ e.stopPropagation();
+ //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+ }
+ // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
+ if (e) {
+ if (e.button === 0 && !e.ctrlKey || e.isDefaultPrevented()) {
+ e.preventDefault();
+ return;
}
- this.Document.followLinkLocation = "inPlace";
- this.Document.followLinkZoom = true;
- this.Document._isLinkButton = true;
- }
-
- @action
- onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
- if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) {
- e.preventDefault();
- e.stopPropagation();
- //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false);
+ e.preventDefault();
+ e.stopPropagation();
+ e.persist();
+
+ if (!navigator.userAgent.includes("Mozilla") && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
+ return;
}
- // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
- if (e) {
- if (e.button === 0 && !e.ctrlKey || e.isDefaultPrevented()) {
- e.preventDefault();
- return;
- }
- e.preventDefault();
- e.stopPropagation();
- e.persist();
-
- if (!navigator.userAgent.includes("Mozilla") && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) {
- return;
- }
+ }
+
+ const cm = ContextMenu.Instance;
+ if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
+
+ if (e && !(e.nativeEvent as any).dash) {
+ const onDisplay = () => setTimeout(() => {
+ DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
+ setTimeout(() => {
+ const ele = document.elementFromPoint(e.clientX, e.clientY);
+ simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
+ });
+ });
+ if (navigator.userAgent.includes("Macintosh")) {
+ cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
+ }
+ else {
+ onDisplay();
+ }
+ return;
+ }
+
+ const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
+ StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
+ cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
+ this.props.contextMenuItems?.().forEach(item =>
+ item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
+
+ if (!this.props.Document.isFolder) {
+ const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
+ const appearance = cm.findByDescription("UI Controls...");
+ const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
+ !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
+ !Doc.UserDoc().noviceMode && appearanceItems.push({
+ description: "Add a Field", event: () => {
+ const alias = Doc.MakeAlias(this.rootDoc);
+ alias.layout = FormattedTextBox.LayoutString("newfield");
+ alias.title = "newfield";
+ alias._height = 35;
+ alias._width = 100;
+ alias.syncLayoutFieldWithTitle = true;
+ alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
+ alias.y = NumCast(this.rootDoc.y);
+ this.props.addDocument?.(alias);
+ }, icon: "eye"
+ });
+ DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
+ !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
+
+ if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
+ !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
+ const existingOnClick = cm.findByDescription("OnClick...");
+ const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+
+ const zorders = cm.findByDescription("ZOrder...");
+ const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
+ if (this.props.bringToFront !== emptyFunction) {
+ zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
+ zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
+ zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
+ }
+ !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" });
+
+ !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
+ !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
+ this.props.CollectionFreeFormDocumentView && onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+
+ if (!this.Document.annotationOn) {
+ const options = cm.findByDescription("Options...");
+ const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+ !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
+
+ onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
+ onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
+ !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
+ onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
+ onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
+ onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
+ } else if (DocListCast(this.Document.links).length) {
+ onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" });
+ onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" });
+ onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
+ }
}
- const cm = ContextMenu.Instance;
- if (!cm || (e as any)?.nativeEvent?.SchemaHandled) return;
-
- if (e && !(e.nativeEvent as any).dash) {
- const onDisplay = () => setTimeout(() => {
- DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear.
- setTimeout(() => {
- const ele = document.elementFromPoint(e.clientX, e.clientY);
- simulateMouseClick(ele, e.clientX, e.clientY, e.screenX, e.screenY);
- });
- });
- if (navigator.userAgent.includes("Macintosh")) {
- cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, onDisplay);
- }
- else {
- onDisplay();
- }
- return;
+ const funcs: ContextMenuProps[] = [];
+ if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
+ funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
+ funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
+ funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
+ cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
}
- const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []);
- StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
- cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: "sticky-note" }));
- this.props.contextMenuItems?.().forEach(item =>
- item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.layoutDoc, scriptContext: this.props.scriptContext, self: this.rootDoc }), icon: item.icon as IconProp }));
-
- if (!this.props.Document.isFolder) {
- const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layoutKey)], Doc, null);
- const appearance = cm.findByDescription("UI Controls...");
- const appearanceItems: ContextMenuProps[] = appearance && "subitems" in appearance ? appearance.subitems : [];
- !Doc.UserDoc().noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" });
- !Doc.UserDoc().noviceMode && appearanceItems.push({
- description: "Add a Field", event: () => {
- const alias = Doc.MakeAlias(this.rootDoc);
- alias.layout = FormattedTextBox.LayoutString("newfield");
- alias.title = "newfield";
- alias._height = 35;
- alias._width = 100;
- alias.syncLayoutFieldWithTitle = true;
- alias.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc.width);
- alias.y = NumCast(this.rootDoc.y);
- this.props.addDocument?.(alias);
- }, icon: "eye"
- });
- DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" });
- !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" });
-
- if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) {
- !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" });
- const existingOnClick = cm.findByDescription("OnClick...");
- const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
-
- const zorders = cm.findByDescription("ZOrder...");
- const zorderItems: ContextMenuProps[] = zorders && "subitems" in zorders ? zorders.subitems : [];
- if (this.props.bringToFront !== emptyFunction) {
- zorderItems.push({ description: "Bring to Front", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, false)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" });
- zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" });
- }
- !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" });
-
- !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
- !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" });
- this.props.CollectionFreeFormDocumentView && onClicks.push({ description: (this.Document.followLinkZoom ? "Don't" : "") + " zoom following link", event: () => this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" });
-
- if (!this.Document.annotationOn) {
- const options = cm.findByDescription("Options...");
- const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
-
- onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
- onClicks.push({ description: this.Document.isLinkButton ? "Remove Follow Behavior" : "Follow Link in Place", event: () => this.toggleFollowLink("inPlace", true, false), icon: "link" });
- !this.Document.isLinkButton && onClicks.push({ description: "Follow Link on Right", event: () => this.toggleFollowLink("add:right", false, false), icon: "link" });
- onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? "Remove Click Behavior" : "Follow Link", event: () => this.toggleFollowLink(undefined, false, false), icon: "link" });
- onClicks.push({ description: (this.Document.isPushpin ? "Remove" : "Make") + " Pushpin", event: () => this.toggleFollowLink(undefined, false, true), icon: "map-pin" });
- onClicks.push({ description: "Edit onClick Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, "onClick"), "edit onClick"), icon: "terminal" });
- !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, noexpand: true, subitems: onClicks, icon: "mouse-pointer" });
- } else if (DocListCast(this.Document.links).length) {
- onClicks.push({ description: "Select on Click", event: () => this.selectOnClick(), icon: "link" });
- onClicks.push({ description: "Follow Link on Click", event: () => this.followLinkOnClick(undefined, false), icon: "link" });
- onClicks.push({ description: "Toggle Link Target on Click", event: () => this.toggleTargetOnClick(), icon: "map-pin" });
- !existingOnClick && cm.addItem({ description: "OnClick...", addDivider: true, subitems: onClicks, icon: "mouse-pointer" });
- }
- }
-
- const funcs: ContextMenuProps[] = [];
- if (!Doc.UserDoc().noviceMode && this.layoutDoc.onDragStart) {
- funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
- funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
- funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined });
- cm.addItem({ description: "OnDrag...", noexpand: true, subitems: funcs, icon: "asterisk" });
- }
-
- const more = cm.findByDescription("More...");
- const moreItems = more && "subitems" in more ? more.subitems : [];
- if (!Doc.IsSystem(this.rootDoc)) {
- (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
- if (!Doc.UserDoc().noviceMode) {
- moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
- moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
-
- if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
- moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
- moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
- }
- moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" });
- }
- }
-
- if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
- moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
- }
- !more && moreItems.length && cm.addItem({ description: "More...", subitems: moreItems, icon: "compass" });
-
- const help = cm.findByDescription("Help...");
- const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
- !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
- helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
- !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
- !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
- cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
+ const more = cm.findByDescription("More...");
+ const moreItems = more && "subitems" in more ? more.subitems : [];
+ if (!Doc.IsSystem(this.rootDoc)) {
+ (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
+ if (!Doc.UserDoc().noviceMode) {
+ moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
+ moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
+
+ if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
+ moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
+ moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
+ }
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: "fingerprint" });
+ }
}
- if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
- cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
- }
-
- collectionFilters = () => StrListCast(this.props.Document._docFilters);
- collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
- @computed get showFilterIcon() {
- return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
- this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined;
- }
- rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
- panelHeight = () => this.props.PanelHeight() - this.headerMargin;
- screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
- contentScaling = () => this.ContentScale;
- onClickFunc = () => this.onClickHandler;
- setHeight = (height: number) => this.layoutDoc._height = height;
- setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
- isContentActive = (outsideReaction?: boolean) => {
- return this.props.isContentActive() === false ? false : (
- CurrentUserUtils.SelectedTool !== InkTool.None ||
- SnappingManager.GetIsDragging() ||
- this.rootSelected() ||
- this.props.Document.forceActive ||
- this.props.isSelected(outsideReaction) ||
- this._componentView?.isAnyChildContentActive?.() ||
- this.props.isContentActive()) ? true : undefined;
- }
- @observable _retryThumb = 1;
- thumbShown = () => {
- return !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
- !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
- !Doc.isBrushedHighlightedDegree(this.props.Document) &&
- !this._componentView?.isAnyChildContentActive?.() ? true : false;
- }
- @computed get contents() {
- TraceMobx();
- const audioView = !this.layoutDoc._showAudio ? (null) :
- <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter} >
- <FontAwesomeIcon className="documentView-audioFont"
- style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }}
- icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
- </div>;
-
- return <div className="documentView-contentsView"
- style={{
- pointerEvents: this.props.pointerEvents?.() as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" :
- this.props.contentPointerEvents as any ? this.props.contentPointerEvents as any :
- this.rootDoc.type !== DocumentType.INK && this.isContentActive() ? "all" :
- "none",
- height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
- }}>
- {!this._retryThumb || !this.thumbShown() ? (null) :
- <img style={{ background: "white", top: 0, position: "relative" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
- width={this.props.PanelWidth()} height={this.props.PanelHeight()}
- onError={(e: any) => {
- setTimeout(action(() => this._retryThumb = 0), 0);
- setTimeout(action(() => this._retryThumb = 1), 150);
- }} />}
- <DocumentContentsView key={1}
- {...this.props}
- docViewPath={this.props.viewPath}
- thumbShown={this.thumbShown}
- isHovering={this.isHovering}
- setContentView={this.setContentView}
- scaling={this.contentScaling}
- PanelHeight={this.panelHeight}
- setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
- isContentActive={this.isContentActive}
- ScreenToLocalTransform={this.screenToLocal}
- rootSelected={this.rootSelected}
- onClick={this.onClickFunc}
- focus={this.focus}
- layoutKey={this.finalLayoutKey} />
- {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
- {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
- <DocumentLinksButton View={this.props.DocumentView()}
- ContentScaling={this.props.ContentScaling}
- Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} />
- }
- {audioView}
- </div>;
- }
-
- get indicatorIcon() {
- if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas";
- else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users";
- else return "user";
- }
-
- @undoBatch
- hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
- anchorPanelWidth = () => this.props.PanelWidth() || 1;
- anchorPanelHeight = () => this.props.PanelHeight() || 1;
- anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
- switch (property) {
- case StyleProp.ShowTitle: return "";
- case StyleProp.PointerEvents: return "none";
- case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox
- default: return this.props.styleProvider?.(doc, props, property);
+ if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && CurrentUserUtils.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
+ moreItems.push({ description: "Close", event: this.deleteClicked, icon: "times" });
}
- }
- // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
- // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g.,
- // - PDF text regions are rendered as an Annotations without generating a DocumentView, '
- // - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
- // - and links to PDF/Web docs at a certain scroll location never create an explicit view.
- // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
- @computed get directLinks() {
- TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(link =>
- Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
- Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
- ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
- ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
- );
- }
- @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
- @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links
- TraceMobx();
- if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
- if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
- return filtered.map((link, i) =>
- <div className="documentView-anchorCont" key={link[Id]}>
- <DocumentView {...this.props}
- Document={link}
- PanelWidth={this.anchorPanelWidth}
- PanelHeight={this.anchorPanelHeight}
- dontRegisterView={false}
- showTitle={returnEmptyString}
- hideCaptions={true}
- fitWidth={returnTrue}
- styleProvider={this.anchorStyleProvider}
- removeDocument={this.hideLinkAnchor}
- LayoutTemplate={undefined}
- LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)} />
- </div >);
- }
-
- @action
- onPointerEnter = () => {
- const self = this;
- const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]);
- if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
- const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
- anno.data instanceof AudioField && new Howl({
- src: [anno.data.url.href],
- format: ["mp3"],
- autoplay: true,
- loop: false,
- volume: 0.5,
- onend: function () {
- runInAction(() => self._mediaState = 0);
- }
- });
- this._mediaState = 1;
+ !more && moreItems.length && cm.addItem({ description: "More...", subitems: moreItems, icon: "compass" });
+
+ const help = cm.findByDescription("Help...");
+ const helpItems: ContextMenuProps[] = help && "subitems" in help ? help.subitems : [];
+ !Doc.UserDoc().novice && helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" });
+ helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" });
+ !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" });
+ cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" });
+ }
+
+ if (!this.topMost) e?.stopPropagation(); // DocumentViews should stop propagation of this event
+ cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15);
+ }
+
+ collectionFilters = () => StrListCast(this.props.Document._docFilters);
+ collectionRangeDocFilters = () => StrListCast(this.props.Document._docRangeFilters);
+ @computed get showFilterIcon() {
+ return this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" :
+ this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined;
+ }
+ rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false;
+ panelHeight = () => this.props.PanelHeight() - this.headerMargin;
+ screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+ contentScaling = () => this.ContentScale;
+ onClickFunc = () => this.onClickHandler;
+ setHeight = (height: number) => this.layoutDoc._height = height;
+ setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
+ isContentActive = (outsideReaction?: boolean) => {
+ return this.props.isContentActive() === false ? false : (
+ CurrentUserUtils.SelectedTool !== InkTool.None ||
+ SnappingManager.GetIsDragging() ||
+ this.rootSelected() ||
+ this.props.Document.forceActive ||
+ this.props.isSelected(outsideReaction) ||
+ this._componentView?.isAnyChildContentActive?.() ||
+ this.props.isContentActive()) ? true : undefined;
+ }
+ @observable _retryThumb = 1;
+ thumbShown = () => {
+ return !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb &&
+ !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) &&
+ !Doc.isBrushedHighlightedDegree(this.props.Document) &&
+ !this._componentView?.isAnyChildContentActive?.() ? true : false;
+ }
+ @computed get contents() {
+ TraceMobx();
+ const audioView = !this.layoutDoc._showAudio ? (null) :
+ <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter} >
+ <FontAwesomeIcon className="documentView-audioFont"
+ style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }}
+ icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
+ </div>;
+
+ return <div className="documentView-contentsView"
+ style={{
+ pointerEvents: this.props.pointerEvents?.() as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" :
+ this.props.contentPointerEvents as any ? this.props.contentPointerEvents as any :
+ this.rootDoc.type !== DocumentType.INK && this.isContentActive() ? "all" :
+ "none",
+ height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+ }}>
+ {!this._retryThumb || !this.thumbShown() ? (null) :
+ <img style={{ background: "white", top: 0, position: "relative" }} src={this.thumb} // + '?d=' + (new Date()).getTime()}
+ width={this.props.PanelWidth()} height={this.props.PanelHeight()}
+ onError={(e: any) => {
+ setTimeout(action(() => this._retryThumb = 0), 0);
+ setTimeout(action(() => this._retryThumb = 1), 150);
+ }} />}
+ <DocumentContentsView key={1}
+ {...this.props}
+ docViewPath={this.props.viewPath}
+ thumbShown={this.thumbShown}
+ isHovering={this.isHovering}
+ setContentView={this.setContentView}
+ scaling={this.contentScaling}
+ PanelHeight={this.panelHeight}
+ setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
+ isContentActive={this.isContentActive}
+ ScreenToLocalTransform={this.screenToLocal}
+ rootSelected={this.rootSelected}
+ onClick={this.onClickFunc}
+ focus={this.focus}
+ layoutKey={this.finalLayoutKey} />
+ {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
+ {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
+ <DocumentLinksButton View={this.props.DocumentView()}
+ ContentScaling={this.props.ContentScaling}
+ Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} />
}
- }
- recordAudioAnnotation = () => {
- let gumStream: any;
- let recorder: any;
- const self = this;
- navigator.mediaDevices.getUserMedia({
- audio: true
- }).then(function (stream) {
- gumStream = stream;
- recorder = new MediaRecorder(stream);
- recorder.ondataavailable = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer(e.data);
- if (!(result instanceof Error)) {
- const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 });
- audioDoc.treeViewExpandedView = "layout";
- const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc));
- if (audioAnnos === undefined) {
- self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"] = new List([audioDoc]);
- } else {
- audioAnnos.push(audioDoc);
- }
- }
- };
- runInAction(() => self._mediaState = 2);
- recorder.start();
- setTimeout(() => {
- recorder.stop();
- runInAction(() => self._mediaState = 0);
- gumStream.getAudioTracks()[0].stop();
- }, 5000);
+ {audioView}
+ </div>;
+ }
+
+ get indicatorIcon() {
+ if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas";
+ else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users";
+ else return "user";
+ }
+
+ @undoBatch
+ hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
+ anchorPanelWidth = () => this.props.PanelWidth() || 1;
+ anchorPanelHeight = () => this.props.PanelHeight() || 1;
+ anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => {
+ switch (property) {
+ case StyleProp.ShowTitle: return "";
+ case StyleProp.PointerEvents: return "none";
+ case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox
+ default: return this.props.styleProvider?.(doc, props, property);
+ }
+ }
+ // We need to use allrelatedLinks to get not just links to the document as a whole, but links to
+ // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g.,
+ // - PDF text regions are rendered as an Annotations without generating a DocumentView, '
+ // - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link
+ // - and links to PDF/Web docs at a certain scroll location never create an explicit view.
+ // For each of these, we create LinkAnchorBox's on the border of the DocumentView.
+ @computed get directLinks() {
+ TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc).filter(link =>
+ Doc.AreProtosEqual(link.anchor1 as Doc, this.rootDoc) ||
+ Doc.AreProtosEqual(link.anchor2 as Doc, this.rootDoc) ||
+ ((link.anchor1 as Doc).unrendered && Doc.AreProtosEqual((link.anchor1 as Doc).annotationOn as Doc, this.rootDoc)) ||
+ ((link.anchor2 as Doc).unrendered && Doc.AreProtosEqual((link.anchor2 as Doc).annotationOn as Doc, this.rootDoc))
+ );
+ }
+ @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
+ @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links
+ TraceMobx();
+ if (this.layoutDoc.unrendered || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
+ if (this.rootDoc.type=== DocumentType.PRES || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => !d.hidden);
+ return filtered.map((link, i) =>
+ <div className="documentView-anchorCont" key={link[Id]}>
+ <DocumentView {...this.props}
+ isContentActive={returnFalse}
+ Document={link}
+ PanelWidth={this.anchorPanelWidth}
+ PanelHeight={this.anchorPanelHeight}
+ dontRegisterView={false}
+ showTitle={returnEmptyString}
+ hideCaptions={true}
+ fitWidth={returnTrue}
+ styleProvider={this.anchorStyleProvider}
+ removeDocument={this.hideLinkAnchor}
+ LayoutTemplate={undefined}
+ LayoutTemplateString={LinkAnchorBox.LayoutString(`anchor${Doc.LinkEndpoint(link, this.rootDoc)}`)} />
+ </div >);
+ }
+
+ @action
+ onPointerEnter = () => {
+ const self = this;
+ const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]);
+ if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
+ const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
+ anno.data instanceof AudioField && new Howl({
+ src: [anno.data.url.href],
+ format: ["mp3"],
+ autoplay: true,
+ loop: false,
+ volume: 0.5,
+ onend: function () {
+ runInAction(() => self._mediaState = 0);
+ }
});
- }
-
- captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption");
- @computed get innards() {
- TraceMobx();
- const ffscale = () => (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1);
- const showTitle = this.ShowTitle?.split(":")[0];
- const showTitleHover = this.ShowTitle?.includes(":hover");
- const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
- const captionView = !showCaption ? (null) :
- <div className="documentView-captionWrapper"
- style={{
- pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
- minWidth: 50 * ffscale(),
- maxHeight: `max(100%, ${20 * ffscale()}px)`
- }}>
- <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
- yPadding={10}
- xPadding={10}
- fieldKey={showCaption}
- fontSize={12 * Math.max(1, 2 * ffscale() / 3)}
- styleProvider={this.captionStyleProvider}
- dontRegisterView={true}
- noSidebar={true}
- dontScale={true}
- isContentActive={this.isContentActive}
- onClick={this.onClickFunc}
- />
- </div>;
- const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
- const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor,
- Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
- const titleView = !showTitle ? (null) :
- <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
- position: this.headerMargin ? "relative" : "absolute",
- height: this.titleHeight,
- color: lightOrDark(background),
- background,
- pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
- }}>
- <EditableView ref={this._titleRef}
- contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
- display={"block"}
- fontSize={10}
- GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
- SetValue={undoBatch((input: string) => {
- if (input?.startsWith("#")) {
- if (this.props.showTitle) {
- this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined;
- } else {
- Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : "creationDate";
- }
- } else {
- var value = input.replace(new RegExp(showTitle + "="), "") as string | number;
- if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
- if (showTitle.includes("Date") || showTitle === "author") return true;
- Doc.SetInPlace(targetDoc, showTitle, value, true);
- }
- return true;
- })}
- />
- </div>;
- return this.props.hideTitle || (!showTitle && !showCaption) ?
- this.contents :
- <div className="documentView-styleWrapper" >
- {!this.headerMargin ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
- {captionView}
- </div>;
- }
- isHovering = () => this._isHovering;
- @observable _isHovering = false;
- @observable _: string = "";
- @computed get renderDoc() {
- TraceMobx();
- const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png");
- const isButton = this.props.Document.type === DocumentType.FONTICON;
- if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
- return this.docContents ??
- <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
- id={this.props.Document[Id]}
- onPointerEnter={action(() => this._isHovering = true)}
- onPointerLeave={action(() => this._isHovering = false)}
- style={{
- background: isButton || thumb ? undefined : this.backgroundColor,
- opacity: this.opacity,
- color: StrCast(this.layoutDoc.color, "inherit"),
- fontFamily: StrCast(this.Document._fontFamily, "inherit"),
- fontSize: Cast(this.Document._fontSize, "string", null),
- transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
- transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? "in" : "out"}`,
- }}>
-
- {this.innards}
- {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)}
- {this.widgetDecorations ?? null}
- </div>;
- }
- render() {
- TraceMobx();
- const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : 0) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
- const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange", "lightBlue"][highlightIndex];
- const highlightStyle = ["solid", "dashed", "solid", "solid", "solid"][highlightIndex];
- const excludeTypes = !this.props.treeViewDoc && highlightIndex < 3 ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
- let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
- highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
-
- const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
- const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
- const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
- this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
-
- // Return surrounding highlight
- return <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont}
- onContextMenu={this.onContextMenu}
- onKeyDown={this.onKeyDown}
- onPointerDown={this.onPointerDown}
- onClick={this.onClick}
- onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
- onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))}
- style={{
- display: this.hidden ? "inline" : undefined,
- borderRadius: this.borderRounding,
- pointerEvents: this.pointerEvents,
- outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px",
- border: highlighting && this.borderRounding && highlightStyle === "dashed" ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
- boxShadow,
- clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined
- }}>
- {!borderPath.path ? internal :
- <>
- {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}>
+ this._mediaState = 1;
+ }
+ }
+ recordAudioAnnotation = () => {
+ let gumStream: any;
+ let recorder: any;
+ const self = this;
+ navigator.mediaDevices.getUserMedia({
+ audio: true
+ }).then(function (stream) {
+ gumStream = stream;
+ recorder = new MediaRecorder(stream);
+ recorder.ondataavailable = async (e: any) => {
+ const [{ result }] = await Networking.UploadFilesToServer(e.data);
+ if (!(result instanceof Error)) {
+ const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: "audio test", _width: 200, _height: 32 });
+ audioDoc.treeViewExpandedView = "layout";
+ const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc));
+ if (audioAnnos === undefined) {
+ self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"] = new List([audioDoc]);
+ } else {
+ audioAnnos.push(audioDoc);
+ }
+ }
+ };
+ runInAction(() => self._mediaState = 2);
+ recorder.start();
+ setTimeout(() => {
+ recorder.stop();
+ runInAction(() => self._mediaState = 0);
+ gumStream.getAudioTracks()[0].stop();
+ }, 5000);
+ });
+ }
+
+ captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps>, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption");
+ @computed get innards() {
+ TraceMobx();
+ const ffscale = () => (this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1);
+ const showTitle = this.ShowTitle?.split(":")[0];
+ const showTitleHover = this.ShowTitle?.includes(":hover");
+ const showCaption = !this.props.hideCaptions && this.Document._viewType !== CollectionViewType.Carousel ? StrCast(this.layoutDoc._showCaption) : undefined;
+ const captionView = !showCaption ? (null) :
+ <div className="documentView-captionWrapper"
+ style={{
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
+ minWidth: 50 * ffscale(),
+ maxHeight: `max(100%, ${20 * ffscale()}px)`
+ }}>
+ <FormattedTextBox {...OmitKeys(this.props, ['children']).omit}
+ yPadding={10}
+ xPadding={10}
+ fieldKey={showCaption}
+ fontSize={12 * Math.max(1, 2 * ffscale() / 3)}
+ styleProvider={this.captionStyleProvider}
+ dontRegisterView={true}
+ noSidebar={true}
+ dontScale={true}
+ isContentActive={this.isContentActive}
+ onClick={this.onClickFunc}
+ />
+ </div>;
+ const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc);
+ const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor,
+ Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)");
+ const titleView = !showTitle ? (null) :
+ <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{
+ position: this.headerMargin ? "relative" : "absolute",
+ height: this.titleHeight,
+ color: lightOrDark(background),
+ background,
+ pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : this.isContentActive() || this.props.isDocumentActive?.() ? "all" : undefined,
+ }}>
+ <EditableView ref={this._titleRef}
+ contents={showTitle.split(";").map(field => field.trim()).map(field => targetDoc[field]?.toString()).join("\\")}
+ display={"block"}
+ fontSize={10}
+ GetValue={() => showTitle.split(";").length === 1 ? showTitle + "=" + Field.toString(targetDoc[showTitle.split(";")[0]] as any as Field) : "#" + showTitle}
+ SetValue={undoBatch((input: string) => {
+ if (input?.startsWith("#")) {
+ if (this.props.showTitle) {
+ this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined;
+ } else {
+ Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : "creationDate";
+ }
+ } else {
+ var value = input.replace(new RegExp(showTitle + "="), "") as string | number;
+ if (showTitle !== "title" && Number(value).toString() === value) value = Number(value);
+ if (showTitle.includes("Date") || showTitle === "author") return true;
+ Doc.SetInPlace(targetDoc, showTitle, value, true);
+ }
+ return true;
+ })}
+ />
+ </div>;
+ return this.props.hideTitle || (!showTitle && !showCaption) ?
+ this.contents :
+ <div className="documentView-styleWrapper" >
+ {!this.headerMargin ? <> {this.contents} {titleView} </> : <> {titleView} {this.contents} </>}
+ {captionView}
+ </div>;
+ }
+ isHovering = () => this._isHovering;
+ @observable _isHovering = false;
+ @observable _: string = "";
+ @computed get renderDoc() {
+ TraceMobx();
+ const thumb = ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png");
+ const isButton = this.props.Document.type === DocumentType.FONTICON;
+ if (!(this.props.Document instanceof Doc) || GetEffectiveAcl(this.props.Document[DataSym]) === AclPrivate || (this.hidden && !this.props.treeViewDoc)) return null;
+ return this.docContents ??
+ <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+ id={this.props.Document[Id]}
+ onPointerEnter={action(() => this._isHovering = true)}
+ onPointerLeave={action(() => this._isHovering = false)}
+ style={{
+ background: isButton || thumb ? undefined : this.backgroundColor,
+ opacity: this.opacity,
+ color: StrCast(this.layoutDoc.color, "inherit"),
+ fontFamily: StrCast(this.Document._fontFamily, "inherit"),
+ fontSize: Cast(this.Document._fontSize, "string", null),
+ transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
+ transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this._animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? "in" : "out"}`,
+ }}>
+
+ {this.innards}
+ {this.onClickHandler && this.props.ContainingCollectionView?.props.Document._viewType === CollectionViewType.Time ? <div className="documentView-contentBlocker" /> : (null)}
+ {this.widgetDecorations ?? null}
+ </div>;
+ }
+ render() {
+ TraceMobx();
+ const highlightIndex = this.props.LayoutTemplateString ? (Doc.IsHighlighted(this.props.Document) ? 6 : Doc.DocBrushStatus.unbrushed) : Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString
+ const highlightColor = ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "orange", "lightBlue"][highlightIndex];
+ const highlightStyle = ["solid", "dashed", "solid", "solid", "solid"][highlightIndex];
+ const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON];
+ let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear;
+ highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
+
+ const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath) || { path: undefined };
+ const internal = PresBox.EffectsProvider(this.layoutDoc, this.renderDoc) || this.renderDoc;
+ const boxShadow = this.props.treeViewDoc ? null : highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` :
+ this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined);
+
+ // Return surrounding highlight
+ return <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont}
+ onContextMenu={this.onContextMenu}
+ onKeyDown={this.onKeyDown}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ onPointerEnter={action(e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document))}
+ onPointerLeave={action(e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document))}
+ style={{
+ display: this.hidden ? "inline" : undefined,
+ borderRadius: this.borderRounding,
+ pointerEvents: this.pointerEvents,
+ outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px",
+ border: highlighting && this.borderRounding && highlightStyle === "dashed" ? `${highlightStyle} ${highlightColor} ${highlightIndex}px` : undefined,
+ boxShadow,
+ clipPath: borderPath.path ? `path('${borderPath.path}')` : undefined
+ }}>
+ {!borderPath.path ? internal :
+ <>
+ {/* <div style={{ clipPath: `path('${borderPath.fill}')` }}>
{internal}
</div> */}
- {internal}
- <div key="border2" className="documentView-customBorder" style={{ pointerEvents: "none" }} >
- <svg style={{ overflow: "visible" }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
- <path d={borderPath.path} style={{ stroke: "black", fill: "transparent", strokeWidth: borderPath.width }} />
- </svg>
- </div>
- </>
- }
- {this.showFilterIcon ?
- <FontAwesomeIcon icon={"filter"} size="lg"
- style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
- onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
- />
- : (null)}
- </div>;
- }
+ {internal}
+ <div key="border2" className="documentView-customBorder" style={{ pointerEvents: "none" }} >
+ <svg style={{ overflow: "visible" }} viewBox={`0 0 ${this.props.PanelWidth()} ${this.props.PanelHeight()}`}>
+ <path d={borderPath.path} style={{ stroke: "black", fill: "transparent", strokeWidth: borderPath.width }} />
+ </svg>
+ </div>
+ </>
+ }
+ {this.showFilterIcon ?
+ <FontAwesomeIcon icon={"filter"} size="lg"
+ style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
+ onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
+ />
+ : (null)}
+ </div>;
+ }
}
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
- public static ROOT_DIV = "documentView-effectsWrapper";
- public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
- public ContentRef = React.createRef<HTMLDivElement>();
- private _disposers: { [name: string]: IReactionDisposer } = {};
-
- public static showBackLinks(linkSource: Doc) {
- const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + "-pivotish";
- DocServer.GetRefField(docid).then(docx => {
- const rootAlias = () => {
- const rootAlias = Doc.MakeAlias(linkSource);
- rootAlias.x = rootAlias.y = 0;
- return rootAlias;
- };
- const linkCollection = ((docx instanceof Doc) && docx) || Docs.Create.StackingDocument([/*rootAlias()*/], { title: linkSource.title + "-pivot", _width: 500, _height: 500, }, docid);
- linkCollection.linkSource = linkSource;
- if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript("updateLinkCollection(self)");
- LightboxView.SetLightboxDoc(linkCollection);
- });
- }
-
- @observable public docView: DocumentViewInternal | undefined | null;
-
- get Document() { return this.props.Document; }
- get topMost() { return this.props.renderDepth === 0; }
- get rootDoc() { return this.docView?.rootDoc || this.Document; }
- get dataDoc() { return this.docView?.dataDoc || this.Document; }
- get finalLayoutKey() { return this.docView?.finalLayoutKey || "layout"; }
- get ContentDiv() { return this.docView?.ContentDiv; }
- get ComponentView() { return this.docView?._componentView; }
- get allLinks() { return this.docView?.allLinks || []; }
- get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
- get fitWidth() { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; }
-
- @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
- @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
- @computed get nativeWidth() {
- return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
- returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
- }
- @computed get nativeHeight() {
- return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
- returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
- }
- @computed get shouldNotScale() {
- return (this.fitWidth && !this.nativeWidth) ||
- this.props.dontScaleFilter?.(this.Document) ||
- this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
- }
- @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
- @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
- @computed get nativeScaling() {
- if (this.shouldNotScale) return 1;
- const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
- if (this.fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
- return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or fitWidth
- }
- return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
- }
-
- @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
- @computed get panelHeight() {
- if (this.effectiveNativeHeight) {
- return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
- }
- return this.props.PanelHeight();
- }
- @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; }
- @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; }
- @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; }
- @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; }
-
- toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
- focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options);
- getBounds = () => {
- if (!this.docView || !this.docView.ContentDiv || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
- return undefined;
- }
- const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse();
- const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
- if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
- }
- return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
- }
-
- public iconify(finished?: () => void) {
- this.ComponentView?.updateIcon?.();
- const layoutKey = Cast(this.Document.layoutKey, "string", null);
- if (layoutKey !== "layout_icon") {
- this.switchViews(true, "icon", finished);
- if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", "");
- } else {
- const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null);
- this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
- this.Document.deiconifyLayout = undefined;
- this.props.bringToFront(this.rootDoc);
- }
- }
- @undoBatch
- @action
- setCustomView = (custom: boolean, layout: string): void => {
- Doc.setNativeView(this.props.Document);
- custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
- }
- switchViews = action((custom: boolean, view: string, finished?: () => void) => {
- this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+ public static ROOT_DIV = "documentView-effectsWrapper";
+ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
+ public ContentRef = React.createRef<HTMLDivElement>();
+ private _disposers: { [name: string]: IReactionDisposer } = {};
+
+ public static showBackLinks(linkSource: Doc) {
+ const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + "-pivotish";
+ DocServer.GetRefField(docid).then(docx => {
+ const rootAlias = () => {
+ const rootAlias = Doc.MakeAlias(linkSource);
+ rootAlias.x = rootAlias.y = 0;
+ return rootAlias;
+ };
+ const linkCollection = ((docx instanceof Doc) && docx) || Docs.Create.StackingDocument([/*rootAlias()*/], { title: linkSource.title + "-pivot", _width: 500, _height: 500, }, docid);
+ linkCollection.linkSource = linkSource;
+ if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript("updateLinkCollection(self)");
+ LightboxView.SetLightboxDoc(linkCollection);
+ });
+ }
+
+ @observable public docView: DocumentViewInternal | undefined | null;
+
+ get Document() { return this.props.Document; }
+ get topMost() { return this.props.renderDepth === 0; }
+ get rootDoc() { return this.docView?.rootDoc || this.Document; }
+ get dataDoc() { return this.docView?.dataDoc || this.Document; }
+ get finalLayoutKey() { return this.docView?.finalLayoutKey || "layout"; }
+ get ContentDiv() { return this.docView?.ContentDiv; }
+ get ComponentView() { return this.docView?._componentView; }
+ get allLinks() { return this.docView?.allLinks || []; }
+ get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
+ get fitWidth() { return this.props.fitWidth?.(this.rootDoc) || this.layoutDoc.fitWidth; }
+
+ @computed get docViewPath(): DocumentView[] { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
+ @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
+ @computed get nativeWidth() {
+ return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
+ returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
+ }
+ @computed get nativeHeight() {
+ return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
+ returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
+ }
+ @computed get shouldNotScale() {
+ return (this.fitWidth && !this.nativeWidth) ||
+ this.props.dontScaleFilter?.(this.Document) ||
+ this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any);
+ }
+ @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
+ @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
+ @computed get nativeScaling() {
+ if (this.shouldNotScale) return 1;
+ const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
+ if (this.fitWidth || this.props.PanelHeight() / (this.effectiveNativeHeight || 1) > this.props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
+ return Math.max(minTextScale, this.props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or fitWidth
+ }
+ return Math.max(minTextScale, this.props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
+ }
+
+ @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); }
+ @computed get panelHeight() {
+ if (this.effectiveNativeHeight) {
+ return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling);
+ }
+ return this.props.PanelHeight();
+ }
+ @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; }
+ @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; }
+ @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; }
+ @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; }
+
+ toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
+ focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options);
+ getBounds = () => {
+ if (!this.docView || !this.docView.ContentDiv || this.props.Document.presBox || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) {
+ return undefined;
+ }
+ const xf = (this.docView?.props.ScreenToLocalTransform().scale(this.nativeScaling)).inverse();
+ const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
+ if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+ const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
+ }
+ return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) };
+ }
+
+ public iconify(finished?: () => void) {
+ this.ComponentView?.updateIcon?.();
+ const layoutKey = Cast(this.Document.layoutKey, "string", null);
+ if (layoutKey !== "layout_icon") {
+ this.switchViews(true, "icon", finished);
+ if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", "");
+ } else {
+ const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null);
+ this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished);
+ this.Document.deiconifyLayout = undefined;
+ this.props.bringToFront(this.rootDoc);
+ }
+ }
+ @undoBatch
+ @action
+ setCustomView = (custom: boolean, layout: string): void => {
+ Doc.setNativeView(this.props.Document);
+ custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined);
+ }
+ switchViews = action((custom: boolean, view: string, finished?: () => void) => {
+ this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc
+ setTimeout(action(() => {
+ this.setCustomView(custom, view);
+ this.docView && (this.docView._animateScalingTo = 1); // expand it
setTimeout(action(() => {
- this.setCustomView(custom, view);
- this.docView && (this.docView._animateScalingTo = 1); // expand it
- setTimeout(action(() => {
- this.docView && (this.docView._animateScalingTo = 0);
- finished?.();
- }), this.docView!._animateScaleTime - 10);
+ this.docView && (this.docView._animateScalingTo = 0);
+ finished?.();
}), this.docView!._animateScaleTime - 10);
- });
-
- startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
-
- docViewPathFunc = () => this.docViewPath;
- isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
- select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
- NativeWidth = () => this.effectiveNativeWidth;
- NativeHeight = () => this.effectiveNativeHeight;
- PanelWidth = () => this.panelWidth;
- PanelHeight = () => this.panelHeight;
- ContentScale = () => this.nativeScaling;
- selfView = () => this;
- screenToLocalTransform = () => {
- return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling);
- }
- componentDidMount() {
- this._disposers.reactionScript = reaction(
- () => ScriptCast(this.rootDoc.reactionScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result,
- output => output
- );
- this._disposers.height = reaction(
- () => NumCast(this.layoutDoc._height),
- action(height => {
- const docMax = NumCast(this.layoutDoc.docMaxAutoHeight);
- if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height;
- })
- );
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
- }
- componentWillUnmount() {
- Object.values(this._disposers).forEach(disposer => disposer?.());
- !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
- }
-
- render() {
- TraceMobx();
- const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
- const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
- const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
- const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
- return (<div className="contentFittingDocumentView">
- {!this.props.Document || !this.props.PanelWidth() ? (null) : (
- <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
- style={{
- transition: this.props.dataTransition,
- position: this.props.Document.isInkMask ? "absolute" : undefined,
- transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
- `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
- }}>
- <DocumentViewInternal {...this.props}
- DocumentView={this.selfView}
- viewPath={this.docViewPathFunc}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- NativeWidth={this.NativeWidth}
- NativeHeight={this.NativeHeight}
- isSelected={this.isSelected}
- select={this.select}
- ContentScaling={this.ContentScale}
- ScreenToLocalTransform={this.screenToLocalTransform}
- focus={this.props.focus || emptyFunction}
- ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
- </div>)}
- </div>);
- }
+ }), this.docView!._animateScaleTime - 10);
+ });
+
+ startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
+
+ docViewPathFunc = () => this.docViewPath;
+ isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
+ select = (extendSelection: boolean) => SelectionManager.SelectView(this, !SelectionManager.Views().some(v => v.props.Document === this.props.ContainingCollectionDoc) && extendSelection);
+ NativeWidth = () => this.effectiveNativeWidth;
+ NativeHeight = () => this.effectiveNativeHeight;
+ PanelWidth = () => this.panelWidth;
+ PanelHeight = () => this.panelHeight;
+ ContentScale = () => this.nativeScaling;
+ selfView = () => this;
+ screenToLocalTransform = () => {
+ const oshift = this.fitWidth && this.ComponentView instanceof FormattedTextBox;
+ const shift = oshift ? -(this.props.PanelHeight() - this.rootDoc[HeightSym]()) / 2 : 0;
+ return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).translate(0, shift).scale(1 / this.nativeScaling);
+ }
+ componentDidMount() {
+ this._disposers.reactionScript = reaction(
+ () => ScriptCast(this.rootDoc.reactionScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result,
+ output => output
+ );
+ this._disposers.height = reaction(
+ () => NumCast(this.layoutDoc._height),
+ action(height => {
+ const docMax = NumCast(this.layoutDoc.docMaxAutoHeight);
+ if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height;
+ })
+ );
+ !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+ }
+ componentWillUnmount() {
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+ !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
+ }
+
+ render() {
+ TraceMobx();
+ const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+ const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
+ const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES;
+ const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear;
+ return (<div className="contentFittingDocumentView">
+ {!this.props.Document || !this.props.PanelWidth() ? (null) : (
+ <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
+ style={{
+ transition: this.props.dataTransition,
+ position: this.props.Document.isInkMask ? "absolute" : undefined,
+ transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
+ margin: this.fitWidth ? "auto" : undefined,
+ width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
+ height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
+ `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
+ }}>
+ <DocumentViewInternal {...this.props}
+ DocumentView={this.selfView}
+ viewPath={this.docViewPathFunc}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
+ NativeWidth={this.NativeWidth}
+ NativeHeight={this.NativeHeight}
+ isSelected={this.isSelected}
+ select={this.select}
+ ContentScaling={this.ContentScale}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ focus={this.props.focus || emptyFunction}
+ ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
+ </div>)}
+ </div>);
+ }
}
export function deiconifyViewFunc(documentView: DocumentView) {
- documentView.iconify();
- //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
+ documentView.iconify();
+ //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc);
}
ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
- documentView.iconify();
- documentView.select(false);
+ documentView.iconify();
+ documentView.select(false);
});
ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
- if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
- else dv.switchViews(true, detailLayoutKeySuffix);
+ if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout");
+ else dv.switchViews(true, detailLayoutKeySuffix);
});
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) {
- const linkSource = Cast(linkCollection.linkSource, Doc, null);
- const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
- let wid = linkSource[WidthSym]();
- const links = DocListCast(linkSource.links);
- links.forEach(link => {
- const other = LinkManager.getOppositeAnchor(link, linkSource);
- const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
- if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
- const alias = Doc.MakeAlias(otherdoc);
- alias.x = wid;
- alias.y = 0;
- alias._lockedPosition = false;
- wid += otherdoc[WidthSym]();
- Doc.AddDocToList(Doc.GetProto(linkCollection), "data", alias);
- }
- });
- return links;
+ const linkSource = Cast(linkCollection.linkSource, Doc, null);
+ const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
+ let wid = linkSource[WidthSym]();
+ const links = DocListCast(linkSource.links);
+ links.forEach(link => {
+ const other = LinkManager.getOppositeAnchor(link, linkSource);
+ const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other;
+ if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) {
+ const alias = Doc.MakeAlias(otherdoc);
+ alias.x = wid;
+ alias.y = 0;
+ alias._lockedPosition = false;
+ wid += otherdoc[WidthSym]();
+ Doc.AddDocToList(Doc.GetProto(linkCollection), "data", alias);
+ }
+ });
+ return links;
}); \ No newline at end of file
diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss
index 229a1a485..c6a497831 100644
--- a/src/client/views/nodes/EquationBox.scss
+++ b/src/client/views/nodes/EquationBox.scss
@@ -2,38 +2,4 @@
.equationBox-cont {
transform-origin: top left;
- overflow: visible;
- width: 100%;
- height: 100%;
-}
-
-.button {
- position: absolute;
- display: flex;
- justify-content: center;
- align-items: center;
- top: 0;
- right: 0;
- width: 20px;
- height: 20px;
- border-radius: 5px;
- background: $dark-gray;
- color: white;
-
- svg {
- width: 12px;
- height: 12px;
- }
-}
-
-.ink-editor {
- top: 20px;
- min-width: 500px;
- min-height: 300px;
- background: $light-gray;
- pointer-events: all;
-
- button {
- float: right;
- }
} \ No newline at end of file
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index a47e17dfc..28834a202 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -206,7 +206,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
facetValues.strings.map(val => {
const num = val ? Number(val) : Number.NaN;
if (Number.isNaN(num)) {
- nonNumbers++;
+ num && nonNumbers++;
} else {
minVal = Math.min(num, minVal);
maxVal = Math.max(num, maxVal);
@@ -216,8 +216,9 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) {
newFacet = Docs.Create.TextDocument("", {
title: facetHeader, system: true, target: targetDoc, _width: 100, _height: 25, _stayInCollection: true,
- treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true
+ treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true,
});
+ Doc.GetProto(newFacet).forceActive = true; // required for FormattedTextBox to be able to gain focus since it will never be 'selected'
Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
newFacet._textBoxPaddingX = newFacet._textBoxPaddingY = 4;
const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
@@ -238,6 +239,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal;
const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`;
newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+ newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
} else {
newFacet = new Doc();
newFacet.system = true;
@@ -401,6 +403,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
docFilters={returnEmptyFilter}
docRangeFilters={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
+ childDocumentsActive={returnTrue}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
ContainingCollectionView={this.props.ContainingCollectionView}
PanelWidth={this.props.PanelWidth}
@@ -479,6 +482,32 @@ ScriptingGlobals.add(function determineCheckedState(layoutDoc: Doc, facetHeader:
}
return undefined;
});
+ScriptingGlobals.add(function readNumFacetData(layoutDoc: Doc, facetDoc: Doc, childKey: string, facetHeader: string) {
+ const allCollectionDocs = new Set<Doc>();
+ const activeTabs = DocListCast(layoutDoc[childKey]);
+ SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
+ const set = new Set<string>();
+ if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key)));
+ else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
+ const facetValues = Array.from(set).filter(v => v);
+
+ let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+ facetValues.map(val => {
+ const num = val ? Number(val) : Number.NaN;
+ if (!Number.isNaN(num)) {
+ minVal = Math.min(num, minVal);
+ maxVal = Math.max(num, maxVal);
+ }
+ });
+ const newFacetField = Doc.LayoutFieldKey(facetDoc);
+ const ranged = Doc.readDocRangeFilter(layoutDoc, facetHeader);
+ const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * .1));
+ const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * .05)));
+ facetDoc[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0];
+ facetDoc[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1];
+ Doc.GetProto(facetDoc)[newFacetField + "-minThumb"] = extendedMinVal;
+ Doc.GetProto(facetDoc)[newFacetField + "-maxThumb"] = extendedMaxVal;
+})
ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) {
const allCollectionDocs = new Set<Doc>();
const activeTabs = DocListCast(layoutDoc[childKey]);
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index d0d61fd79..b0b050cea 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -23,8 +23,8 @@ export interface LabelBoxProps {
@observer
export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxProps)>() {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LabelBox, fieldKey); }
- public static LayoutStringWithTitle(fieldType: { name: string }, fieldStr: string, label: string) {
- return `<${fieldType.name} fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
+ public static LayoutStringWithTitle(fieldStr: string, label?: string) {
+ return !label ? LabelBox.LayoutString(fieldStr) : `<LabelBox fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "<ImageBox {...props} fieldKey={"data} />"
}
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
@@ -79,17 +79,18 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
@computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; }
fitTextToBox = (r: any): any => {
+ const singleLine = BoolCast(this.rootDoc._singleLine, true);
const params = {
rotateText: null,
fontSizeFactor: 1,
- minimumFontSize: NumCast(this.rootDoc._minFontSize, 2),
+ minimumFontSize: NumCast(this.rootDoc._minFontSize, 8),
maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000),
limitingDimension: "both",
horizontalAlign: "center",
verticalAlign: "center",
textAlign: "center",
- singleLine: BoolCast(this.rootDoc._singleLine),
- whiteSpace: this.rootDoc._singleLine ? "nowrap" : "pre-wrap"
+ singleLine,
+ whiteSpace: singleLine ? "nowrap" : "pre-wrap"
};
this._timeout = undefined;
if (!r) return params;
@@ -99,15 +100,16 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
parentStyle.display = "";
parentStyle.alignItems = "";
r.setAttribute("style", "");
- r.style.width = this.rootDoc._singleLine ? "" : "100%";
+ r.style.width = singleLine ? "" : "100%";
r.style.textOverflow = "ellipsis";
r.style.overflow = "hidden";
BigText(r, params);
+ return params;
}
// (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
render() {
- this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes
+ const boxParams = this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes
const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []);
const missingParams = params?.filter(p => !this.paramsDoc[p]);
params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ...
@@ -130,9 +132,9 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro
paddingBottom: NumCast(this.rootDoc._yPadding),
width: this.props.PanelWidth(),
height: this.props.PanelHeight(),
- whiteSpace: this.rootDoc._singleLine ? "pre" : "pre-wrap"
+ whiteSpace: boxParams.singleLine ? "pre" : "pre-wrap"
}} >
- <span style={{ width: this.layoutDoc._singleLine ? "" : "100%" }} ref={action((r: any) => this.fitTextToBox(r))}>
+ <span style={{ width: boxParams.singleLine ? "" : "100%" }} ref={action((r: any) => this.fitTextToBox(r))}>
{label.startsWith("#") ? (null) : label.replace(/([^a-zA-Z])/g, "$1\u200b")}
</span>
</div>
diff --git a/src/client/views/nodes/LinkDocPreview.scss b/src/client/views/nodes/LinkDocPreview.scss
index 06ae466f0..3febbcecb 100644
--- a/src/client/views/nodes/LinkDocPreview.scss
+++ b/src/client/views/nodes/LinkDocPreview.scss
@@ -1,4 +1,4 @@
- .linkDocPreview {
+.linkDocPreview {
position: absolute;
pointer-events: all;
background-color: lightblue;
@@ -8,11 +8,14 @@
border-bottom: 8px solid white;
border-right: 8px solid white;
z-index: 2004;
+
.linkDocPreview-inner {
background-color: white;
width: 100%;
height: 100%;
pointer-events: none;
+ display: flex;
+ flex-direction: column;
.linkDocPreview-info {
height: 37px;
@@ -21,6 +24,7 @@
.linkDocPreview-buttonBar {
float: right;
}
+
.linkDocPreview-title {
padding-right: 4px;
float: left;
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ba515fb89..6c7e174f7 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -93,7 +93,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
} else {
this._linkSrc = anchor;
const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
- this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
}
this._toolTipText = "";
}
@@ -108,8 +108,11 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
}
nextHref = (e: React.PointerEvent) => {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => {
- this._linkDoc = undefined;
- this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ if (nextHrefInd !== this._hrefInd) {
+ this._linkDoc = undefined;
+ this._hrefInd = nextHrefInd;
+ }
}), true);
}
@@ -118,7 +121,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
LinkDocPreview.Clear();
LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false);
} else if (this.props.hrefs?.length) {
- this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right");
+ this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), "add:right");
}
}
width = () => {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index cbe7a5cc6..f2ca6c96e 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -320,7 +320,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title={searchTitle} />
<input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged}
- onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey)} />
+ onKeyDown={e => {
+ e.stopPropagation();
+ e.keyCode === KeyCodes.ENTER && this.search(this._searchString, e.shiftKey);
+ }} />
<button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" />
</button>
@@ -342,6 +345,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
<div className="pdfBox-pageNums">
<input value={curPage} style={{ width: `${curPage > 99 ? 4 : 3}ch`, pointerEvents: "all" }}
onChange={e => this.Document._curPage = Number(e.currentTarget.value)}
+ onKeyDown={e => e.stopPropagation()}
onClick={action(() => this._pageControls = !this._pageControls)} />
{this._pageControls ? pageBtns : (null)}
</div>
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index f267407eb..d4cddd65e 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -87,7 +87,6 @@
justify-content: center;
display: flex;
width: 100%;
- height: 38px;
visibility: none;
opacity: 0;
background-color: $dark-gray;
@@ -98,7 +97,7 @@
bottom: 0;
transition: top 0.5s, width 0.5s, opacity 0.2s, visibility 0s;
- height: 38px;
+ height: 24px;
padding: 0 20px;
.timecode-controls {
@@ -108,7 +107,7 @@
justify-content: center;
margin: 0 5px;
flex-grow: 2;
- font-size: 14px;
+ font-size: 12px;
.timecode {
margin: 0 5px;
@@ -128,8 +127,8 @@
.videobox-button {
margin: 5px;
cursor: pointer;
- width: 38px;
- height: 38px;
+ width: 24px;
+ height: 24px;
border-radius: 50%;
background: $dark-gray;
display: flex;
@@ -141,8 +140,8 @@
}
svg {
- width: 25px;
- height: 25px;
+ width: 18px;
+ height: 18px;
}
}
}
@@ -205,21 +204,21 @@ input[type="range"]:focus {
input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
- height: 20px;
+ height: 18px;
cursor: pointer;
box-shadow: 0;
background: $light-gray;
- border-radius: 20px;
+ border-radius: 18px;
}
input[type="range"]::-webkit-slider-thumb {
box-shadow: 0;
border: 0;
- height: 26px;
- width: 26px;
+ height: 20px;
+ width: 20px;
border-radius: 20px;
background: $medium-blue;
cursor: pointer;
-webkit-appearance: none;
- margin-top: -3px;
+ margin-top: -1px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7c4c007ab..b14a1f0f6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -860,12 +860,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return <div className="videoBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
}
+ savedAnnotations = () => this._savedAnnotations;
render() {
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
return (<div className="videoBox" onContextMenu={this.specificContextMenu} ref={this._mainCont}
style={{
- pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
+ pointerEvents: this.layoutDoc._lockedPosition ? "none" : undefined,
borderRadius,
overflow: this.props.docViewPath?.().slice(-1)[0].fitWidth ? "auto" : undefined
}} onWheel={e => { e.stopPropagation(); e.preventDefault(); }}>
@@ -907,7 +908,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
finishMarquee={this.finishMarquee}
- savedAnnotations={this._savedAnnotations}
+ savedAnnotations={this.savedAnnotations}
annotationLayer={this._annotationLayer.current}
mainCont={this._mainCont.current}
/>}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 28af6bfa1..10974ca03 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -756,7 +756,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
moveDocument={this.moveDocument}
- addDocument={this.sidebarAddDocument}
+ addDocument={this.addDocument}
styleProvider={this.childStyleProvider}
childPointerEvents={this.props.isContentActive() ? "all" : undefined}
pointerEvents={this.annotationPointerEvents} />;
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index d9524dd6e..f3f1bcf5c 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -214,8 +214,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
.replace(/noscript/g, "div").replace(/<div class="mediaset"><\/div>/g, "") // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
.replace(/<link[^>]*>/g, "") // don't need to keep any linked style sheets because we've already processed all style sheets above
.replace(/srcset="([^ "]*)[^"]*"/g, "src=\"$1\""); // instead of converting each item in the srcset to a data url, just convert the first one and use that
- let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml);
- const fetchedResources = await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml);
+ let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith("data:"));
+ const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : [];
for (let i = 0; i < fetchedResources.length; i++) {
const r = fetchedResources[i];
if (r.resourceUrl) {
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index e16c055e4..a1b9023f3 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
-import { DataSym, Doc, DocListCast, HeightSym, LayoutSym, StrListCast, WidthSym } from '../../../../fields/Doc';
+import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { createSchema } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
@@ -14,7 +14,6 @@ import { WebField } from '../../../../fields/URLField';
import { aggregateBounds, Utils } from '../../../../Utils';
import { DocumentType } from '../../../documents/DocumentTypes';
import { CurrentUserUtils } from '../../../util/CurrentUserUtils';
-import { DocumentManager } from '../../../util/DocumentManager';
import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
import { SelectionManager } from '../../../util/SelectionManager';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
@@ -235,6 +234,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
const script = ScriptCast(this.rootDoc.script);
+ if (!script) { return null; }
let noviceList: string[] = [];
let text: string | undefined;
@@ -264,7 +264,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
"Comic Sans MS", "Tahoma", "Impact", "Crimson Text"];
}
} catch (e) {
- // console.log(e);
+ console.log(e);
}
// Get items to place into the list
@@ -462,7 +462,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
const buttonText = StrCast(this.rootDoc.buttonText);
// TODO:glr Add label of button type
- let button = this.defaultButton;
+ let button: JSX.Element | null = this.defaultButton;
switch (this.type) {
case ButtonType.TextButton:
@@ -530,9 +530,10 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
}
return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button :
- <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
- {button}
- </Tooltip>;
+ button !== null ?
+ <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>
+ {button}
+ </Tooltip > : null
}
}
@@ -706,6 +707,83 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) {
else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === "italics" ? undefined : "italics";
});
+
+export function checkInksToGroup() {
+ // console.log("getting here to inks group");
+ if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+ CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+ // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+ // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
+ const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => {
+ // console.log(inkDoc.x, inkDoc.y);
+ });
+ });
+ }
+}
+
+export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
+ // TODO nda - if document being added to is a inkGrouping then we can just add to that group
+ if (CurrentUserUtils.SelectedTool === InkTool.Write) {
+ CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
+ // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
+ const selected = ffView.unprocessedDocs;
+ // loop through selected an get the bound
+ const bounds: { x: number, y: number, width?: number, height?: number }[] = []
+
+ selected.map(action(d => {
+ const x = NumCast(d.x);
+ const y = NumCast(d.y);
+ const width = d[WidthSym]();
+ const height = d[HeightSym]();
+ bounds.push({ x, y, width, height });
+ }))
+
+ const aggregBounds = aggregateBounds(bounds, 0, 0);
+ const marqViewRef = ffView._marqueeViewRef.current;
+
+ // set the vals for bounds in marqueeView
+ if (marqViewRef) {
+ marqViewRef._downX = aggregBounds.x;
+ marqViewRef._downY = aggregBounds.y;
+ marqViewRef._lastX = aggregBounds.r;
+ marqViewRef._lastY = aggregBounds.b;
+ }
+
+ selected.map(action(d => {
+ const dx = NumCast(d.x);
+ const dy = NumCast(d.y);
+ delete d.x;
+ delete d.y;
+ delete d.activeFrame;
+ delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
+ // calculate pos based on bounds
+ if (marqViewRef?.Bounds) {
+ d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
+ d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
+ }
+ return d;
+ }));
+ ffView.props.removeDocument?.(selected);
+ // TODO: nda - this is the code to actually get a new grouped collection
+ const newCollection = marqViewRef?.getCollection(selected, undefined, true);
+ if (newCollection) {
+ newCollection.height = newCollection[HeightSym]();
+ newCollection.width = newCollection[WidthSym]();
+ }
+
+ // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
+ newCollection && ffView.props.addDocument?.(newCollection);
+ // TODO: nda - will probably need to go through and only remove the unprocessed selected docs
+ ffView.unprocessedDocs = [];
+
+ InkTranscription.Instance.transcribeInk(newCollection, selected, false, ffView);
+ });
+ }
+ CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
+}
+
+
/** INK
* setActiveInkTool
* setStrokeWidth
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 6a3f9ed00..bb3791f1e 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -86,7 +86,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
public static fieldContent(textBoxDoc: Doc, dashDoc: Doc, fieldKey: string) {
const dashVal = dashDoc[fieldKey] ?? dashDoc[DataSym][fieldKey] ?? (fieldKey === "PARAMS" ? textBoxDoc[fieldKey] : "");
const fval = dashVal instanceof List ? dashVal.join(DashFieldViewInternal.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(textBoxDoc)[fieldKey] : dashVal;
- return { boolVal: Cast(fval, "boolean", null), strVal: Field.toString(fval as Field) || "" }
+ return { boolVal: Cast(fval, "boolean", null), strVal: Field.toString(fval as Field) || "" };
}
// set the display of the field's value (checkbox for booleans, span of text for strings)
@@ -241,8 +241,8 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> {
const hideMenu = () => {
this.fadeOut(true);
document.removeEventListener("pointerdown", hideMenu);
- }
- document.addEventListener("pointerdown", hideMenu)
+ };
+ document.addEventListener("pointerdown", hideMenu);
}
render() {
const buttons = [
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e866e96d6..7d0302b26 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -83,7 +83,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
public static LiveTextUndo: UndoManager.Batch | undefined;
- static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
+ static _globalHighlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
static _highlightStyleSheet: any = addStyleSheet();
static _bulletStyleSheet: any = addStyleSheet();
static _userStyleSheet: any = addStyleSheet();
@@ -283,6 +283,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this.dataDoc[this.props.fieldKey] = undefined;
this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data)));
this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have
+ ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText });
unchanged = false;
}
this._applyingChange = "";
@@ -412,6 +413,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
return tr;
}
+ @action
+ search = (searchString: string, bwd?: boolean, clear: boolean = false) => {
+ if (clear) this.unhighlightSearchTerms();
+ else this.highlightSearchTerms([searchString], bwd!);
+ return true;
+ }
highlightSearchTerms = (terms: string[], backward: boolean) => {
if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
@@ -548,35 +555,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
updateHighlights = () => {
+ const highlights = FormattedTextBox._globalHighlights;
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
- if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) {
+ if (highlights.indexOf("Audio Tags") === -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, "");
}
- if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) {
+ if (highlights.indexOf("Text from Others") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" });
}
- if (FormattedTextBox._highlights.indexOf("My Text") !== -1) {
+ if (highlights.indexOf("My Text") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" });
}
- if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) {
+ if (highlights.indexOf("Todo Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" });
}
- if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) {
+ if (highlights.indexOf("Important Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" });
}
- if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) {
+ if (highlights.indexOf("Bold Text") !== -1) {
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror strong > span", { "font-size": "large" }, "");
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror :not(strong > span)", { "font-size": "0px" }, "");
+ }
+ if (highlights.indexOf("Disagree Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" });
}
- if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
+ if (highlights.indexOf("Ignore Items") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" });
}
- if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
+ if (highlights.indexOf("By Recent Minute") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
setTimeout(this.updateHighlights);
}
- if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
+ if (highlights.indexOf("By Recent Hour") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
@@ -634,17 +646,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
});
});
const highlighting: ContextMenuProps[] = [];
- const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others"];
+ const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"];
const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"];
(Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option =>
highlighting.push({
- description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
+ description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => {
e.stopPropagation();
- if (FormattedTextBox._highlights.indexOf(option) === -1) {
- FormattedTextBox._highlights.push(option);
+ if (FormattedTextBox._globalHighlights.indexOf(option) === -1) {
+ FormattedTextBox._globalHighlights.push(option);
} else {
- FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1);
+ FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1);
}
+ runInAction(() => this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join(""));
this.updateHighlights();
}, icon: "expand-arrows-alt"
}));
@@ -862,7 +875,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on
() => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }),
({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => {
- autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight));
+ autoHeight && this.props.setHeight?.((this.props.scaling?.() || 1) * (marginsHeight + Math.max(sidebarHeight, textHeight)));
}, { fireImmediately: true });
this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks
newLinks => {
@@ -924,6 +937,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._disposers.selected = reaction(() => this.props.isSelected(),
action(selected => {
+ this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join("") : "";
if (RichTextMenu.Instance?.view === this._editorView && !selected) {
RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined);
}
@@ -1203,7 +1217,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks);
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size))));
} else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) {
- selectAll(this._editorView.state, this._editorView?.dispatch)
+ selectAll(this._editorView.state, this._editorView?.dispatch);
this.startUndoTypingBatch();
} else if (this._editorView) {
this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })));
@@ -1235,6 +1249,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
onPointerDown = (e: React.PointerEvent): void => {
+ if (this.Document.forceActive) e.stopPropagation();
this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view.
if ((e.target as any).tagName === "AUDIOTAG") {
e.preventDefault();
@@ -1396,9 +1411,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos);
if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list && listNode) {
if (!highlightOnly) {
- if (selectOrderedList || (!collapse && listNode.attrs.visibility)) {
+ if (selectOrderedList) {
this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection(selectOrderedList ? $olistPos! : listPos!)));
- } else if (!listNode.attrs.visibility || downNode === listNode) {
+ } else {
const tr = this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility });
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos)));
}
@@ -1489,9 +1504,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
this._rules!.EnteringStyle = false;
}
e.stopPropagation();
- for (var i = state.selection.from; i < state.selection.to; i++) {
+ for (var i = state.selection.from; i <= state.selection.to; i++) {
const node = state.doc.resolve(i);
- if (node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
+ if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark &&
mark.attrs.userid !== Doc.CurrentUserEmail) &&
[AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) {
e.preventDefault();
@@ -1628,7 +1643,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
}
render() {
TraceMobx();
- const selected = this.props.isSelected();
+ const selected = this.props.isSelected() || this.Document.forceActive;
const active = this.props.isContentActive();
const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1);
const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 3df1e45a5..9bc2e5628 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -1,15 +1,15 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
-import { action, IReactionDisposer, observable, reaction, runInAction, computed } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { lift, wrapIn } from "prosemirror-commands";
-import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model";
+import { Mark, MarkType, Node as ProsNode, ResolvedPos } from "prosemirror-model";
import { wrapInList } from "prosemirror-schema-list";
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc } from "../../../../fields/Doc";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { Cast, StrCast } from "../../../../fields/Types";
import { DocServer } from "../../../DocServer";
import { LinkManager } from "../../../util/LinkManager";
import { SelectionManager } from "../../../util/SelectionManager";
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 06932d145..a0a2dd4f8 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -1078,11 +1078,13 @@
.miniPres {
cursor: grab;
position: absolute;
- right: 10;
- top: 10;
+ top: 0;
+ left: 0;
opacity: 0.1;
transition: all 0.4s;
color: $white;
+ width: 100%;
+ height: 100%;
}
.miniPres:hover {
@@ -1101,7 +1103,6 @@
align-items: center;
display: flex;
position: absolute;
- right: 10px;
transition: all 0.2s;
.presPanel-button-text {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 64f5a296f..591480023 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -10,17 +10,16 @@ import { InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { PrefetchProxy } from "../../../../fields/Proxy";
import { listSpec } from "../../../../fields/Schema";
-import { ScriptField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
-import { emptyFunction, returnFalse, returnOne, returnTrue } from '../../../../Utils';
+import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils';
import { Docs } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
import { DocumentManager } from "../../../util/DocumentManager";
-import { ScriptingGlobals } from "../../../util/ScriptingGlobals";
import { SelectionManager } from "../../../util/SelectionManager";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { CollectionDockingView } from "../../collections/CollectionDockingView";
+import { MarqueeViewBounds } from "../../collections/collectionFreeForm";
import { CollectionView, CollectionViewType } from "../../collections/CollectionView";
import { TabDocView } from "../../collections/TabDocView";
import { ViewBoxBaseComponent } from "../../DocComponent";
@@ -31,11 +30,16 @@ import { FieldView, FieldViewProps } from '../FieldView';
import "./PresBox.scss";
import { PresEffect, PresMovement, PresStatus } from "./PresEnums";
-export class PinProps {
+export interface PinProps {
audioRange?: boolean;
- unpin?: boolean;
setPosition?: boolean;
hidePresBox?: boolean;
+ pinWithView?: PinViewProps;
+}
+
+export interface PinViewProps {
+ bounds: MarqueeViewBounds;
+ scale: number;
}
@observer
@@ -47,17 +51,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* @param renderDoc
* @param layoutDoc
*/
- static renderEffectsDoc(renderDoc: any, layoutDoc: Doc) {
+ static renderEffectsDoc(renderDoc: any, layoutDoc: Doc, presDoc: Doc) {
const effectProps = {
- left: layoutDoc.presEffectDirection === PresEffect.Left,
- right: layoutDoc.presEffectDirection === PresEffect.Right,
- top: layoutDoc.presEffectDirection === PresEffect.Top,
- bottom: layoutDoc.presEffectDirection === PresEffect.Bottom,
+ left: presDoc.presEffectDirection === PresEffect.Left,
+ right: presDoc.presEffectDirection === PresEffect.Right,
+ top: presDoc.presEffectDirection === PresEffect.Top,
+ bottom: presDoc.presEffectDirection === PresEffect.Bottom,
opposite: true,
- delay: layoutDoc.presTransition,
+ delay: presDoc.presTransition,
// when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc,
};
- switch (layoutDoc.presEffect) {
+ switch (presDoc.presEffect) {
case PresEffect.Zoom: return (<Zoom {...effectProps}>{renderDoc}</Zoom>);
case PresEffect.Fade: return (<Fade {...effectProps}>{renderDoc}</Fade>);
case PresEffect.Flip: return (<Flip {...effectProps}>{renderDoc}</Flip>);
@@ -71,7 +75,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
public static EffectsProvider(layoutDoc: Doc, renderDoc: any) {
return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ?
- PresBox.renderEffectsDoc(renderDoc, layoutDoc)
+ PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex])
:
renderDoc;
}
@@ -90,13 +94,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@observable _expandBoolean: boolean = false;
private _disposers: { [name: string]: IReactionDisposer } = {};
+
+ @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView
@observable private transitionTools: boolean = false;
@observable private newDocumentTools: boolean = false;
@observable private progressivizeTools: boolean = false;
@observable private openMovementDropdown: boolean = false;
@observable private openEffectDropdown: boolean = false;
@observable private presentTools: boolean = false;
- @computed get childDocs() { return DocListCast(this.rootDoc[this.fieldKey]); }
+ @computed get isTreeOrStack() {return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any) }
+ @computed get isTree() { return this.layoutDoc._viewType === CollectionViewType.Tree;}
+ @computed get presFieldKey() { return StrCast(this.layoutDoc.presFieldKey, "data"); }
+ @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); }
+ @observable _treeViewMap: Map<Doc, number> = new Map();
+
@computed get tagDocs() {
const tagDocs: Doc[] = [];
for (const doc of this.childDocs) {
@@ -124,14 +135,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({
title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"
}));
- // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement
- // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent
- // the preselement docs from being part of multiple presentations since they would all have the same field, or we'd have to keep per-presentation data
- // stored on each pres element.
- (this.presElement as Doc).lookupField = ScriptField.MakeFunction("lookupPresBoxField(container, field, data)",
- { field: "string", data: Doc.name, container: Doc.name });
}
this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox
+
}
@computed get selectedDocumentView() {
if (SelectionManager.Views().length) return SelectionManager.Views()[0];
@@ -148,8 +154,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
@computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ _unmounting = false;
@action
componentWillUnmount() {
+ this._unmounting = true;
document.removeEventListener("keydown", PresBox.keyEventsWrapper, true);
this._presKeyEventsActive = false;
this.resetPresentation();
@@ -160,7 +168,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
componentDidMount() {
- this.rootDoc.presBox = this.rootDoc;
+ this._unmounting = false;
this.rootDoc._forceRenderEngine = "timeline";
this.layoutDoc.presStatus = PresStatus.Edit;
this.layoutDoc._gridGap = 0;
@@ -212,6 +220,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
//TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
+ // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
// No more frames in current doc and next slide is defined, therefore move to next slide
nextSlide = (activeNext: Doc) => {
const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null);
@@ -297,6 +306,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
this.startTempMedia(targetDoc, activeItem);
}
if (targetDoc) {
+ Doc.linkFollowHighlight((targetDoc.annotationOn instanceof Doc) ? [targetDoc, targetDoc.annotationOn] : targetDoc);
targetDoc && runInAction(() => {
if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0;
else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500;
@@ -308,7 +318,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (!group) this._selectedArray.clear();
this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array
- if ([CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.layoutDoc._viewType as any) && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
+ this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list
this.onHideDocument(); //Handles hide after/before
}
});
@@ -317,14 +327,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
navigateToView = (targetDoc: Doc, activeItem: Doc) => {
clearTimeout(this._navTimer);
const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document;
+ if (bestTarget) console.log(bestTarget.title, bestTarget.type);
+ else console.log("no best target");
bestTarget && runInAction(() => {
+ console.log(bestTarget.title, bestTarget.type);
if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) {
bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
bestTarget._scrollTop = activeItem.presPinViewScroll;
} else if (bestTarget.type === DocumentType.COMPARISON) {
bestTarget._clipWidth = activeItem.presPinClipWidth;
- } else if (bestTarget.type === DocumentType.VID) {
- bestTarget._currentTimecode = activeItem.presPinTimecode;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) {
+ bestTarget._currentTimecode = activeItem.presStartTime;
} else {
bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s';
bestTarget._panX = activeItem.presPinViewX;
@@ -346,7 +359,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
navigateToElement = async (curDoc: Doc) => {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- const srcContext = Cast(targetDoc.context, Doc, null);
+ const srcContext = Cast(targetDoc.context, Doc, null) ?? Cast(Cast(targetDoc.annotationOn, Doc, null)?.context, Doc, null);
const presCollection = Cast(this.layoutDoc.presCollection, Doc, null);
const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined;
const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc);
@@ -374,7 +387,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
self._eleArray.splice(0, self._eleArray.length, ...eleViewCache);
});
const openInTab = (doc: Doc, finished?: () => void) => {
- collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ":left");
+ collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, "");
this.layoutDoc.presCollection = targetDoc;
// this still needs some fixing
setTimeout(resetSelection, 500);
@@ -387,17 +400,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
// If openDocument is selected then it should open the document for the user
if (activeItem.openDocument) {
LightboxView.SetLightboxDoc(targetDoc);
+ // openInTab(targetDoc);
} else if (curDoc.presMovement === PresMovement.Pan && targetDoc) {
LightboxView.SetLightboxDoc(undefined);
- await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
+ await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
} else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
LightboxView.SetLightboxDoc(undefined);
//awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
+ await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
}
// After navigating to the document, if it is added as a presPinView then it will
// adjust the pan and scale to that of the pinView when it was added.
if (activeItem.presPinView) {
+ console.log(targetDoc.title);
+ console.log("presPinView in PresBox.tsx:420");
// if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target
this.navigateToView(targetDoc, activeItem);
}
@@ -587,27 +603,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* The method called to open the presentation as a minimized view
*/
@action
- updateMinimize = () => {
- const docView = DocumentManager.Instance.getDocumentView(this.layoutDoc);
+ updateMinimize = async () => {
if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
- console.log("case 1");
this.layoutDoc.presStatus = PresStatus.Edit;
Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
CollectionDockingView.AddSplit(this.rootDoc, "right");
- } else if ((true || this.layoutDoc.context) && docView) {
- console.log("case 2");
+ } else {
this.layoutDoc.presStatus = PresStatus.Edit;
clearTimeout(this._presTimer);
const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250);
this.rootDoc.y = pt[1] + 10;
- this.rootDoc._height = 35;
- this.rootDoc._width = 250;
- docView.props.removeDocument?.(this.layoutDoc);
+ this.rootDoc._height = 30;
+ this.rootDoc._width = 248;
Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc);
- } else {
- console.log("case 3");
- // TODO glr: fix this case
+ this.props.removeDocument?.(this.layoutDoc);
}
}
@@ -615,14 +625,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
* Called when the user changes the view type
* Either 'List' (stacking) or 'Slides' (carousel)
*/
- // @undoBatch
+ @undoBatch
viewChanged = action((e: React.ChangeEvent) => {
//@ts-ignore
const viewType = e.target.selectedOptions[0].value as CollectionViewType;
+ this.layoutDoc.presFieldKey = this.fieldKey+(viewType === CollectionViewType.Tree ?"-linearized":"");
// pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
[CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined);
this.rootDoc._viewType = viewType;
- if ([CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType)) this.layoutDoc._gridGap = 0;
+ if (this.isTreeOrStack) {
+ this.layoutDoc._gridGap = 0;
+ }
});
/**
@@ -696,7 +709,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
return true;
}
- childLayoutTemplate = () => ![CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any) ? undefined : this.presElement;
+ childLayoutTemplate = () => !this.isTreeOrStack ? undefined : this.presElement;
removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc);
getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight
panelHeight = () => this.props.PanelHeight() - 40;
@@ -795,13 +808,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
//regular click
@action
- regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean) => {
+ regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true) => {
this._selectedArray.clear();
this._selectedArray.set(doc, undefined);
this._eleArray.splice(0, this._eleArray.length, ref);
this._dragArray.splice(0, this._dragArray.length, drag);
focus && this.selectElement(doc);
- this.selectPres();
+ selectPres && this.selectPres();
}
modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean) => {
@@ -1089,7 +1102,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateEffectDirection = (effect: any, all?: boolean) => {
const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
array.forEach((doc) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const tagDoc = doc;// Cast(doc.presentationTargetDoc, Doc, null);
switch (effect) {
case PresEffect.Left:
tagDoc.presEffectDirection = PresEffect.Left;
@@ -1115,7 +1128,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
updateEffect = (effect: any, all?: boolean) => {
const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys());
array.forEach((doc) => {
- const tagDoc = Cast(doc.presentationTargetDoc, Doc, null);
+ const tagDoc = doc;//Cast(doc.presentationTargetDoc, Doc, null);
switch (effect) {
case PresEffect.Bounce:
tagDoc.presEffect = PresEffect.Bounce;
@@ -1152,7 +1165,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75;
let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2;
if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
- const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None';
+ const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None';
activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom';
return (
<div className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}>
@@ -1204,6 +1217,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="ribbon-property">
<input className="presBox-input"
type="number" value={transitionSpeed}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e) => this.setTransitionTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
@@ -1243,6 +1257,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="ribbon-property">
<input className="presBox-input"
type="number" value={duration}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e) => this.setDurationTime(e.target.value))} /> s
</div>
<div className="ribbon-propertyUpDown">
@@ -1274,12 +1289,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
{effect}
<FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={"angle-down"} />
<div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.None || !targetDoc.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div>
- <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.None || !this.activeItem.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div>
+ <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div>
</div>
</div>
<div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}>
@@ -1289,11 +1304,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
</div>
<div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: this.activeItem.presEffectDirection === PresEffect.Center || !this.activeItem.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip>
</div>
</div>}
<div className="ribbon-final-box">
@@ -1308,7 +1323,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@computed get effectDirection(): string {
let effect = '';
- switch (this.targetDoc.presEffectDirection) {
+ switch (this.activeItem.presEffectDirection) {
case 'left': effect = "Enter from left"; break;
case 'right': effect = "Enter from right"; break;
case 'top': effect = "Enter from top"; break;
@@ -1324,8 +1339,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
this.updateMovement(activeItem.presMovement, true);
- this.updateEffect(targetDoc.presEffect, true);
- this.updateEffectDirection(targetDoc.presEffectDirection, true);
+ this.updateEffect(activeItem.presEffect, true);
+ this.updateEffectDirection(activeItem.presEffectDirection, true);
array.forEach((doc) => {
const curDoc = Cast(doc, Doc, null);
const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
@@ -1355,8 +1370,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const scroll = targetDoc._scrollTop;
activeItem.presPinView = true;
activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1;
} else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) {
const x = targetDoc._panX;
const y = targetDoc._panY;
@@ -1377,8 +1393,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
const scroll = targetDoc._scrollTop;
activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
+ } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) {
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1;
} else if (targetDoc.type === DocumentType.COMPARISON) {
const clipWidth = targetDoc._clipWidth;
activeItem.presPinClipWidth = clipWidth;
@@ -1408,6 +1425,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewX)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} />
</div>
</div>
@@ -1417,6 +1435,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewY)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} />
</div>
</div>
@@ -1426,6 +1445,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewScale)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} />
</div>
</div>
@@ -1446,6 +1466,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'left', width: 50 }}
type="number" value={NumCast(activeItem.presPinViewScroll)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} />
</div>
</div>
@@ -1492,6 +1513,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
<input className="presBox-input"
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presStartTime)}
+ onKeyDown={e => e.stopPropagation()}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })}
/>
</div>
@@ -1510,6 +1532,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>
<div id={"endTime"} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}>
<input className="presBox-input"
+ onKeyDown={e => e.stopPropagation()}
style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }}
type="number" value={NumCast(activeItem.presEndTime)}
onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presEndTime = Number(e.target.value); })}
@@ -2403,6 +2426,63 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
else this.pauseAutoPres();
}
+ @action
+ prevClicked = (e: PointerEvent) => {
+ this.back();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ }
+
+ @action
+ nextClicked = (e: PointerEvent) => {
+ this.next();
+ if (this._presTimer) {
+ clearTimeout(this._presTimer);
+ this.layoutDoc.presStatus = PresStatus.Manual;
+ }
+ }
+ @undoBatch
+ @action
+ exitClicked = (e: PointerEvent) => {
+ this.updateMinimize();
+ this.layoutDoc.presStatus = PresStatus.Edit;
+ clearTimeout(this._presTimer);
+ }
+
+ @action
+ startMarqueeCreateSlide = () => {
+ PresBox.startMarquee = true;
+ }
+
+ AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ var indexNum = 0;
+ for (let i = 0; i < index.length; i++) {
+ indexNum += (index[i] * (10 ** (-i)));
+ }
+ if (this._treeViewMap.get(treeViewDoc) !== indexNum) {
+ this._treeViewMap.set(treeViewDoc, indexNum);
+ const sorted = this.sort(this._treeViewMap);
+ const curList = DocListCast(this.dataDoc[this.presFieldKey]);
+ if (sorted.length !== curList.length || sorted.some((doc,ind) => doc !== curList[ind])) {
+ this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
+ }
+ }
+ return this.childDocs;
+ }
+
+ RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => {
+ if (!this._unmounting && this.isTree) {
+ this._treeViewMap.delete(treeViewDoc);
+ this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
+ }
+ return this.childDocs;
+ }
+
+ // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1])
+ sort = (treeViewMap: Map<Doc, number>) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0).map(kv => kv[0]);
+
render() {
// calling this method for keyEvents
this.isPres;
@@ -2413,56 +2493,67 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1);
const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0);
return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ?
- <div className="miniPres">
+ <div className="miniPres" onClick={e => e.stopPropagation()}>
<div className="presPanelOverlay" style={{ display: "inline-flex", height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
- <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.layoutDoc.presLoop = !this.layoutDoc.presLoop, false, false)}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip>
<div className="presPanel-divider"></div>
- <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onClick={() => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-left"} /></div>
- <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
- <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onClick={() => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}><FontAwesomeIcon icon={"arrow-right"} /></div>
+ <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}><FontAwesomeIcon icon={"arrow-left"} /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button"
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip>
+ <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}><FontAwesomeIcon icon={"arrow-right"} /></div>
<div className="presPanel-divider"></div>
- <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}><b>1</b></div></Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Click to return to 1st slide"}</div></>}><div className="presPanel-button" style={{ border: 'solid 1px white' }}
+ onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}><b>1</b></div></Tooltip>
<div className="presPanel-button-text">
Slide {this.itemIndex + 1} / {this.childDocs.length}
{this.playButtonFrames}
</div>
- <div className="presPanel-divider"></div>
- <div className="presPanel-button-text" onClick={undoBatch(action(() => { this.updateMinimize(); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); }))}>EXIT</div>
+ <div className="presPanel-divider" />
+ <div className="presPanel-button-text" onPointerDown={e =>
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>EXIT</div>
</div>
- </div>
+ </div >
:
<div className="presBox-cont" style={{ minWidth: CurrentUserUtils.OverlayDocs.includes(this.layoutDoc) ? 240 : undefined }} >
{this.topPanel}
{this.toolbar}
{this.newDocumentToolbarDropdown}
<div className="presBox-listCont">
- {mode !== CollectionViewType.Invalid ?
- <CollectionView {...this.props}
- ContainingCollectionDoc={this.props.Document}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.panelHeight}
- childIgnoreNativeSize={true}
- moveDocument={returnFalse}
- childFitWidth={returnTrue}
- childOpacity={returnOne}
- childLayoutTemplate={this.childLayoutTemplate}
- filterAddDocument={this.addDocumentFilter}
- removeDocument={returnFalse}
- dontRegisterView={true}
- focus={this.selectElement}
- ScreenToLocalTransform={this.getTransform}
- />
- : (null)
+ <div className="Slide" style={{ height: `calc(100% - 30px)` }}>
+ {mode !== CollectionViewType.Invalid ?
+ <CollectionView {...this.props}
+ ContainingCollectionDoc={this.props.Document}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.panelHeight}
+ childIgnoreNativeSize={true}
+ moveDocument={returnFalse}
+ childFitWidth={returnTrue}
+ childOpacity={returnOne}
+ childLayoutTemplate={this.childLayoutTemplate}
+ filterAddDocument={this.addDocumentFilter}
+ removeDocument={returnFalse}
+ dontRegisterView={true}
+ focus={this.selectElement}
+ scriptContext={this}
+ ScreenToLocalTransform={this.getTransform}
+ AddToMap={this.AddToMap}
+ RemFromMap={this.RemFromMap}
+ hierarchyIndex={[]}
+ /> : (null)
+ }
+ </div>
+
+ { // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack
+ <Tooltip title={<div className="dash-tooltip">{"Click on document to pin to presentaiton or make a marquee selection to pin your desired view"}</div>}>
+ <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}>
+ + NEW SLIDE
+ </button>
+ </Tooltip>
}
</div>
</div>;
}
-}
-ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) {
- if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data);
- if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(container._viewType as any) ? 35 : 31;
- if (field === 'presStatus') return container.presStatus;
- if (field === '_itemIndex') return container._itemIndex;
- if (field === 'presBox') return container;
- return undefined;
-}); \ No newline at end of file
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 1919071df..969f034a8 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -194,7 +194,48 @@ $slide-active: #5B9FDD;
// }
.presItem-slide.active {
- box-shadow: 0 0 0px 1.5px $dark-blue;
+ box-shadow: 0 0 0px 2.5px $dark-blue;
+}
+
+.presItem-slide.group {
+ border-radius: 5px;
+}
+
+.presItem-slide.activeGroup {
+ border-radius: 5px;
+ box-shadow: 0 0 0px 2.5px $dark-blue;
+}
+
+.presItem-groupSlideContainer {
+ position: absolute;
+ /* grid-row: 3; */
+ /* grid-column: 1/8; */
+ top: 28;
+ display: block;
+ background: #92adb9;
+ width: 100%;
+ height: 10px;
+ border-radius: 0px 0px 5px 5px;
+ height: calc(100% - 28px);
+ display: grid;
+ grid-template-rows: auto auto auto;
+ grid-template-columns: 100%;
+ justify-items: left;
+ align-items: center;
+}
+
+.presItem-groupSlide {
+ position: relative;
+ background-color: #d5dce2;
+ border-radius: 5px;
+ height: calc(100% - 7px);
+ width: calc(100% - 20px);
+ left: 15px;
+ /* height: 20px; */
+ /* width: calc(100% - 19px); */
+ display: flex;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
}
.presItem-multiDrag {
@@ -232,6 +273,37 @@ $slide-active: #5B9FDD;
box-shadow: 0 0 0px 1.5px $dark-blue;
}
-// .presItem-slide:hover {
-// background: $slide-hover;
-// } \ No newline at end of file
+.expandButton {
+ cursor: pointer;
+ position: absolute;
+ border-radius: 100px;
+ bottom: 0;
+ left: -18;
+ z-index: 300;
+ width: 15px;
+ height: 15px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: #92adb9;
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+}
+
+.expandButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+}
+
+.presItem-groupNum {
+ color: #d5dce2;
+ position: absolute;
+ left: -15px;
+ top: 1;
+ font-weight: 600;
+ font-size: 12;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 9bace1c8d..f56ca5471 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction } from "mobx"
import { observer } from "mobx-react";
import { DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils";
import { Docs, DocUtils } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
@@ -34,242 +34,278 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); }
_heightDisposer: IReactionDisposer | undefined;
- @observable _dragging = false;
- // these fields are conditionally computed fields on the layout document that take this document as a parameter
- @computed get indexInPres() { return Number(this.lookupField("indexInPres")); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
- @computed get collapsedHeight() { return Number(this.lookupField("presCollapsedHeight")); } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
- @computed get presStatus() { return StrCast(this.lookupField("presStatus")); }
- @computed get itemIndex() { return NumCast(this.lookupField("_itemIndex")); }
- @computed get presBox() { return Cast(this.lookupField("presBox"), Doc, null); }
- @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
-
- componentDidMount() {
- this.layoutDoc.hideLinkButton = true;
- this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
- params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
- }
- componentWillUnmount() {
- this._heightDisposer?.();
- }
-
- @action
- presExpandDocumentClick = () => {
- this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton;
- }
-
- /**
- * Returns a local transformed coordinate array for given coordinates.
- */
- ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
-
- embedHeight = (): number => 97;
- // embedWidth = () => this.props.PanelWidth();
- // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
- embedWidth = (): number => this.props.PanelWidth() - 35;
- styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
- if (property === StyleProp.Opacity) return 1;
- return this.props.styleProvider?.(doc, props, property);
- }
- /**
- * The function that is responsible for rendering a preview or not for this
- * presentation element.
- */
- @computed get renderEmbeddedInline() {
- return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
- <div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
- <DocumentView
- Document={this.targetDoc}
- DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
- styleProvider={this.styleProvider}
- docViewPath={returnEmptyDoclist}
- rootSelected={returnTrue}
- addDocument={returnFalse}
- removeDocument={returnFalse}
- isContentActive={this.props.isContentActive}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- fitContentsToDoc={returnTrue}
- PanelWidth={this.embedWidth}
- PanelHeight={this.embedHeight}
- ScreenToLocalTransform={Transform.Identity}
- moveDocument={this.props.moveDocument!}
- renderDepth={this.props.renderDepth + 1}
- focus={DocUtils.DefaultFocus}
- whenChildContentsActiveChanged={returnFalse}
- bringToFront={returnFalse}
- docFilters={this.props.docFilters}
- docRangeFilters={this.props.docRangeFilters}
- searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- hideLinkButton={true}
- />
- <div className="presItem-embeddedMask" />
- </div>;
- }
- @computed get duration() {
- let durationInS: number;
- if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; }
- else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
- else durationInS = 2;
- return "D: " + durationInS + "s";
- }
-
- @computed get transition() {
- let transitionInS: number;
- if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000;
- else transitionInS = 0.5;
- return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? (null) : "M: " + transitionInS + "s";
- }
-
- private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
- private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
- private _titleRef: React.RefObject<EditableView> = React.createRef();
-
-
- @action
- headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
- const element = e.target as any;
- e.stopPropagation();
- e.preventDefault();
- if (element && !(e.ctrlKey || e.metaKey)) {
- if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
- PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
- setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
- } else {
- setupMoveUpEvents(this, e, ((e: PointerEvent) => {
- PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false);
- return this.startDrag(e);
- }), emptyFunction, emptyFunction);
- }
- }
- }
-
- headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
- e.stopPropagation();
- e.preventDefault();
- }
-
- /**
- * Function to drag and drop the pres element to a diferent location
- */
- startDrag = (e: PointerEvent) => {
- const miniView: boolean = this.toolbarWidth <= 100;
- const activeItem = this.rootDoc;
- const dragArray = PresBox.Instance._dragArray;
- const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
- if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
- dragData.dropAction = "move";
- dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
- dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
- const dragItem: HTMLElement[] = [];
- if (dragArray.length === 1) {
- const doc = dragArray[0];
- doc.className = miniView ? "presItem-miniSlide" : "presItem-slide";
- dragItem.push(doc);
- } else if (dragArray.length >= 1) {
- const doc = document.createElement('div');
- doc.className = "presItem-multiDrag";
- doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides";
- doc.style.position = 'absolute';
- doc.style.top = (e.clientY) + 'px';
- doc.style.left = (e.clientX - 50) + 'px';
- dragItem.push(doc);
- }
-
- // const dropEvent = () => runInAction(() => this._dragging = false);
- if (activeItem) {
- DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined);
- // runInAction(() => this._dragging = true);
- return true;
- }
- return false;
- }
-
- onPointerOver = (e: any) => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- }
-
- onPointerMove = (e: PointerEvent) => {
- const slide = this._itemRef.current!;
- let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false;
- for (const doc of DragManager.docsBeingDragged) {
- if (!doc.presentationTargetDoc) dragIsPresItem = false;
- }
- if (slide && dragIsPresItem) {
- const rect = slide.getBoundingClientRect();
- const y = e.clientY - rect.top; //y position within the element.
- const height = slide.clientHeight;
- const halfLine = height / 2;
- if (y <= halfLine) {
- slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
- slide.style.borderBottom = "0px";
- } else if (y > halfLine) {
- slide.style.borderTop = "0px";
- slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
- }
- }
- document.removeEventListener("pointermove", this.onPointerMove);
- }
-
- onPointerLeave = (e: any) => {
- this._itemRef.current!.style.borderTop = "0px";
- this._itemRef.current!.style.borderBottom = "0px";
- document.removeEventListener("pointermove", this.onPointerMove);
- }
-
- @action
- toggleProperties = () => {
- if (CurrentUserUtils.propertiesWidth < 5) {
- action(() => (CurrentUserUtils.propertiesWidth = 250));
- }
- }
-
- @undoBatch
- removeItem = action((e: React.MouseEvent) => {
- this.props.removeDocument?.(this.rootDoc);
+ @observable _dragging = false;
+ @computed get indexInPres() { return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, "data")]).indexOf(this.rootDoc); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements)
+ @computed get collapsedHeight() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31; } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up
+ @computed get presStatus() { return this.presBox.presStatus; }
+ @computed get presBox() { return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!; }
+ @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; }
+
+ componentDidMount() {
+ this.layoutDoc.hideLinkButton = true;
+ this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight],
+ params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ this._heightDisposer?.();
+ }
+
+ /**
+ * Returns a local transformed coordinate array for given coordinates.
+ */
+ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord];
+
+ @action
+ presExpandDocumentClick = () => {
+ this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton;
+ }
+
+ embedHeight = (): number => 97;
+ // embedWidth = () => this.props.PanelWidth();
+ // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight);
+ embedWidth = (): number => this.props.PanelWidth() - 35;
+ styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => {
+ if (property === StyleProp.Opacity) return 1;
+ return this.props.styleProvider?.(doc, props, property);
+ }
+ /**
+ * The function that is responsible for rendering a preview or not for this
+ * presentation element.
+ */
+ @computed get renderEmbeddedInline() {
+ return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
+ <div className="presItem-embedded" style={{ height: this.embedHeight(), width: this.embedWidth() }}>
+ <DocumentView
+ Document={this.targetDoc}
+ DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]}
+ styleProvider={this.styleProvider}
+ docViewPath={returnEmptyDoclist}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ isContentActive={this.props.isContentActive}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ fitContentsToDoc={returnTrue}
+ PanelWidth={this.embedWidth}
+ PanelHeight={this.embedHeight}
+ ScreenToLocalTransform={Transform.Identity}
+ moveDocument={this.props.moveDocument!}
+ renderDepth={this.props.renderDepth + 1}
+ focus={DocUtils.DefaultFocus}
+ whenChildContentsActiveChanged={returnFalse}
+ bringToFront={returnFalse}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ hideLinkButton={true}
+ />
+ <div className="presItem-embeddedMask" />
+ </div>;
+ }
+
+ @computed get renderGroupSlides() {
+ const childDocs = DocListCast(this.targetDoc.data);
+ const groupSlides = childDocs.map((doc: Doc, ind: number) =>
+ <div className="presItem-groupSlide" onClick={(e) => {
+ console.log("Clicked on slide with index: ", ind);
+ e.stopPropagation();
+ e.preventDefault();
+ PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ this.presExpandDocumentClick();
+ }}>
+ <div className="presItem-groupNum">
+ {`${ind + 1}.`}
+ </div>
+ {/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */}
+ <div className="presItem-name">
+ <EditableView
+ ref={this._titleRef}
+ editing={undefined}
+ contents={doc.title}
+ overflow={'ellipsis'}
+ GetValue={() => StrCast(doc.title)}
+ SetValue={(value: string) => {
+ doc.title = !value.trim().length ? "-untitled-" : value;
+ return true;
+ }}
+ />
+ </div>
+ </div>
+ );
+ return (
+ groupSlides
+ );
+ }
+ @computed get duration() {
+ let durationInS: number;
+ if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; }
+ else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000;
+ else durationInS = 2;
+ return "D: " + durationInS + "s";
+ }
+
+ @computed get transition() {
+ let transitionInS: number;
+ if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000;
+ else transitionInS = 0.5;
+ return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? (null) : "M: " + transitionInS + "s";
+ }
+
+ private _itemRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _titleRef: React.RefObject<EditableView> = React.createRef();
+
+
+ @action
+ headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
+ const element = e.target as any;
+ e.stopPropagation();
+ e.preventDefault();
+ if (element && !(e.ctrlKey || e.metaKey)) {
if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
- PresBox.Instance._selectedArray.delete(this.rootDoc);
+ PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
+ setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction);
+ } else {
+ setupMoveUpEvents(this, e, ((e: PointerEvent) => {
+ PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false);
+ return this.startDrag(e);
+ }), emptyFunction, emptyFunction);
}
- this.hideRecording();
- e.stopPropagation();
- });
+ }
+ }
+
+ headerUp = (e: React.PointerEvent<HTMLDivElement>) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
- // set the value/title of the individual pres element
- @undoBatch
- @action
- onSetValue = (value: string) => {
- this.rootDoc.title = !value.trim().length ? "-untitled-" : value;
- return true;
- }
/**
- * Method called for updating the view of the currently selected document
- *
- * @param targetDoc
- * @param activeItem
+ * Function to drag and drop the pres element to a diferent location
*/
- @undoBatch
- @action
- updateView = (targetDoc: Doc, activeItem: Doc) => {
- if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) {
- const scroll = targetDoc._scrollTop;
- activeItem.presPinViewScroll = scroll;
- } else if (targetDoc.type === DocumentType.VID) {
- activeItem.presPinTimecode = targetDoc._currentTimecode;
- } else if (targetDoc.type === DocumentType.COMPARISON) {
- const clipWidth = targetDoc._clipWidth;
- activeItem.presPinClipWidth = clipWidth;
- } else {
- const x = targetDoc._panX;
- const y = targetDoc._panY;
- const scale = targetDoc._viewScale;
- activeItem.presPinViewX = x;
- activeItem.presPinViewY = y;
- activeItem.presPinViewScale = scale;
+ startDrag = (e: PointerEvent) => {
+ const miniView: boolean = this.toolbarWidth <= 100;
+ const activeItem = this.rootDoc;
+ const dragArray = PresBox.Instance._dragArray;
+ const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray());
+ if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc);
+ dragData.dropAction = "move";
+ dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc;
+ dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument;
+ const dragItem: HTMLElement[] = [];
+ if (dragArray.length === 1) {
+ const doc = this._itemRef.current || dragArray[0];
+ doc.className = miniView ? "presItem-miniSlide" : "presItem-slide";
+ dragItem.push(doc);
+ } else if (dragArray.length >= 1) {
+ const doc = document.createElement('div');
+ doc.className = "presItem-multiDrag";
+ doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides";
+ doc.style.position = 'absolute';
+ doc.style.top = (e.clientY) + 'px';
+ doc.style.left = (e.clientX - 50) + 'px';
+ dragItem.push(doc);
+ }
+
+ // const dropEvent = () => runInAction(() => this._dragging = false);
+ if (activeItem) {
+ DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined);
+ // runInAction(() => this._dragging = true);
+ return true;
+ }
+ return false;
+ }
+
+ onPointerOver = (e: any) => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
+
+ onPointerMove = (e: PointerEvent) => {
+ const slide = this._itemRef.current!;
+ let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false;
+ for (const doc of DragManager.docsBeingDragged) {
+ if (!doc.presentationTargetDoc) dragIsPresItem = false;
+ }
+ if (slide && dragIsPresItem) {
+ const rect = slide.getBoundingClientRect();
+ const y = e.clientY - rect.top; //y position within the element.
+ const height = slide.clientHeight;
+ const halfLine = height / 2;
+ if (y <= halfLine) {
+ slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`;
+ slide.style.borderBottom = "0px";
+ } else if (y > halfLine) {
+ slide.style.borderTop = "0px";
+ slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`;
}
- }
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ onPointerLeave = (e: any) => {
+ this._itemRef.current!.style.borderTop = "0px";
+ this._itemRef.current!.style.borderBottom = "0px";
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ @action
+ toggleProperties = () => {
+ if (CurrentUserUtils.propertiesWidth < 5) {
+ action(() => (CurrentUserUtils.propertiesWidth = 250));
+ }
+ }
+
+ @undoBatch
+ removeItem = action((e: React.MouseEvent) => {
+ this.props.removeDocument?.(this.rootDoc);
+ if (PresBox.Instance._selectedArray.has(this.rootDoc)) {
+ PresBox.Instance._selectedArray.delete(this.rootDoc);
+ }
+ e.stopPropagation();
+ });
+
+ // set the value/title of the individual pres element
+ @undoBatch
+ @action
+ onSetValue = (value: string) => {
+ this.rootDoc.title = !value.trim().length ? "-untitled-" : value;
+ return true;
+ }
+
+ /**
+ * Method called for updating the view of the currently selected document
+ *
+ * @param targetDoc
+ * @param activeItem
+ */
+ @undoBatch
+ @action
+ updateView = (targetDoc: Doc, activeItem: Doc) => {
+ switch (targetDoc.type) {
+ case DocumentType.PDF: case DocumentType.WEB: case DocumentType.RTF :
+ const scroll = targetDoc._scrollTop;
+ activeItem.presPinViewScroll = scroll;
+ break;
+ case DocumentType.VID: case DocumentType.AUDIO:
+ activeItem.presStartTime = targetDoc._currentTimecode;
+ break;
+ case DocumentType.COMPARISON :
+ const clipWidth = targetDoc._clipWidth;
+ activeItem.presPinClipWidth = clipWidth;
+ break;
+ default:
+ const x = targetDoc._panX;
+ const y = targetDoc._panY;
+ const scale = targetDoc._viewScale;
+ activeItem.presPinViewX = x;
+ activeItem.presPinViewY = y;
+ activeItem.presPinViewScale = scale;
+ }
+ }
@computed get recordingIsInOverlay() {
let isInOverlay = false