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.tsx23
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx190
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx13
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.scss4
-rw-r--r--src/client/views/nodes/DataVizBox/DataVizBox.tsx184
-rw-r--r--src/client/views/nodes/DataVizBox/components/Chart.scss3
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx28
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx90
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx21
-rw-r--r--src/client/views/nodes/DataVizBox/components/TableBox.tsx38
-rw-r--r--src/client/views/nodes/DataVizBox/utils/D3Utils.ts4
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx35
-rw-r--r--src/client/views/nodes/DocumentIcon.tsx4
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx46
-rw-r--r--src/client/views/nodes/DocumentView.scss1
-rw-r--r--src/client/views/nodes/DocumentView.tsx1561
-rw-r--r--src/client/views/nodes/EquationBox.tsx6
-rw-r--r--src/client/views/nodes/FieldView.tsx113
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.scss10
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx26
-rw-r--r--src/client/views/nodes/FunctionPlotBox.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.tsx35
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx68
-rw-r--r--src/client/views/nodes/LabelBox.scss5
-rw-r--r--src/client/views/nodes/LabelBox.tsx29
-rw-r--r--src/client/views/nodes/LinkAnchorBox.tsx21
-rw-r--r--src/client/views/nodes/LinkBox.scss25
-rw-r--r--src/client/views/nodes/LinkBox.tsx283
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx45
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx19
-rw-r--r--src/client/views/nodes/MapBox/MapBox.tsx20
-rw-r--r--src/client/views/nodes/MapBox/MapBox2.tsx12
-rw-r--r--src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx5
-rw-r--r--src/client/views/nodes/MapBox/MapPushpinBox.tsx4
-rw-r--r--src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx30
-rw-r--r--src/client/views/nodes/PDFBox.tsx39
-rw-r--r--src/client/views/nodes/RecordingBox/RecordingBox.tsx12
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx13
-rw-r--r--src/client/views/nodes/ScriptingBox.tsx8
-rw-r--r--src/client/views/nodes/VideoBox.tsx60
-rw-r--r--src/client/views/nodes/WebBox.tsx70
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx10
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx39
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx8
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss10
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx42
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx147
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx3
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts2
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx16
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts138
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts5
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts15
-rw-r--r--src/client/views/nodes/importBox/ImportElementBox.tsx6
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx62
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx13
57 files changed, 1990 insertions, 1733 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 908cd5dc0..8a38ef663 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { DateField } from '../../../fields/DateField';
import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, DateCast, NumCast } from '../../../fields/Types';
import { AudioField, nullAudio } from '../../../fields/URLField';
@@ -19,8 +20,7 @@ import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import './AudioBox.scss';
-import { DocFocusOptions } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
/**
@@ -121,7 +121,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
if (this.path) {
this.mediaState = media_state.Paused;
this.setPlayheadTime(NumCast(this.layoutDoc.clipStart));
@@ -211,7 +211,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// removes from currently playing display
@action
removeCurrentlyPlaying = () => {
- const docView = this._props.DocumentView?.();
+ const docView = this.DocumentView?.();
if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -221,7 +221,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
- const docView = this._props.DocumentView?.();
+ const docView = this.DocumentView?.();
if (!CollectionStackedTimeline.CurrentlyPlaying) {
CollectionStackedTimeline.CurrentlyPlaying = [];
}
@@ -374,9 +374,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
action(() => {
const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
const textField = Doc.LayoutFieldKey(newDoc);
- Doc.GetProto(newDoc)[`${textField}_recordingSource`] = this.dataDoc;
- Doc.GetProto(newDoc)[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
- Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
+ const newDocData = newDoc[DocData];
+ newDocData[`${textField}_recordingSource`] = this.dataDoc;
+ newDocData[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
+ newDocData.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
if (Doc.IsInMyOverlay(this.Document)) {
newDoc.overlayX = this.Document.x;
newDoc.overlayY = NumCast(this.Document.y) + NumCast(this.layoutDoc._height);
@@ -432,7 +433,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
// plays link
- playLink = (link: Doc, options: DocFocusOptions) => {
+ playLink = (link: Doc, options: FocusViewOptions) => {
if (link.annotationOn === this.Document) {
if (!this.layoutDoc.dontAutoPlayFollowedLinks) {
this.playFrom(this.timeline?.anchorStart(link) || 0, this.timeline?.anchorEnd(link));
@@ -567,8 +568,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
const [xp, yp] = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y);
de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
},
- this.layoutDoc,
- undefined
+ this.layoutDoc
);
}
};
@@ -717,7 +717,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
renderDepth={this._props.renderDepth + 1}
startTag={'_timecodeToShow' /* audioStart */}
endTag={'_timecodeToHide' /* audioEnd */}
- bringToFront={emptyFunction}
playFrom={this.playFrom}
setTime={this.setPlayheadTime}
playing={this.playing}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ad5aabc21..2800ea200 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { OmitKeys, numberRange } from '../../../Utils';
@@ -15,10 +15,13 @@ import { DocComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import './CollectionFreeFormDocumentView.scss';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
+import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import { FieldViewProps } from './FieldView';
-export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps {
+/// Ugh, typescript has no run-time way of iterating through the keys of an interface. so we need
+/// manaully keep this list of keys in synch wih the fields of the freeFormProps interface
+const freeFormPropsKeys = ['x', 'y', 'z', 'zIndex', 'rotation', 'opacity', 'backgroundColor', 'color', 'highlight', 'width', 'height', 'autoDim', 'transition'];
+interface freeFormProps {
x: number;
y: number;
z: number;
@@ -32,16 +35,48 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
opacity?: number;
highlight?: boolean;
transition?: string;
- dataTransition?: string;
+}
+export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
RenderCutoffProvider: (doc: Doc) => boolean;
CollectionFreeFormView: CollectionFreeFormView;
}
@observer
-export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>() implements CollectionFreeFormDocumentViewProps {
- constructor(props: any) {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & freeFormProps>() {
+ get displayName() { // this makes mobx trace() statements more descriptive
+ return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
+ } // prettier-ignore
+ public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
+ public static animFields: { key: string; val?: number }[] = [
+ { key: 'x' },
+ { key: 'y' },
+ { key: 'opacity', val: 1 },
+ { key: '_height' },
+ { key: '_width' },
+ { key: '_rotation', val: 0 },
+ { key: '_layout_scrollTop' },
+ { key: '_currentFrame' },
+ { key: 'freeform_scale', val: 1 },
+ { key: 'freeform_panX' },
+ { key: 'freeform_panY' },
+ ]; // fields that are configured to be animatable using animation frames
+ public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
+ public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
+
+ constructor(props: CollectionFreeFormDocumentViewProps & freeFormProps) {
super(props);
makeObservable(this);
}
+
+ get WrapperKeys() {
+ // each of these keys is set by the CollectionView and passed via props. however, if any one of these props changes
+ // (or any other prop), then it's as if they all change.
+ // Anything that accesses these props will then invalidate unncessarily.
+ // To avoid this, we copy these prop values into local observables. Now when 'props' changes, nothing invalidates.
+ // Instead, we copy each values into its observable which ohnly triggers invalidations if the new value is different
+ // than the old value, and then only things that access that observable will invalidate.
+ return freeFormPropsKeys
+ .map(key => ({upper:key[0].toUpperCase() + key.substring(1), lower:key})); // prettier-ignore
+ }
@observable X = this.props.x;
@observable Y = this.props.y;
@observable Z = this.props.z;
@@ -55,106 +90,25 @@ export class CollectionFreeFormDocumentViewWrapper extends DocComponent<Collecti
@observable Height = this.props.height;
@observable AutoDim = this.props.autoDim;
@observable Transition = this.props.transition;
- @observable DataTransition = this.props.dataTransition;
- CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
- RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
- @computed get WrapperKeys() {
- return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', ''))
- .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore
+ componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewProps & freeFormProps>>) {
+ super.componentDidUpdate(prevProps);
+ this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
}
- // wrapper functions around prop fields that have been converted to observables to keep 'props' from ever changing.
+ CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
// this way, downstream code only invalidates when it uses a specific prop, not when any prop changes
- w_X = () => this.X; // prettier-ignore
- w_Y = () => this.Y; // prettier-ignore
- w_Z = () => this.Z; // prettier-ignore
- w_ZIndex = () => this.ZIndex ?? NumCast(this.Document.zIndex); // prettier-ignore
- w_Rotation = () => this.Rotation ?? NumCast(this.Document._rotation); // prettier-ignore
- w_Opacity = () => this.Opacity; // prettier-ignore
- w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); // prettier-ignore
- w_Color = () => this.Color ?? Cast(this.Document._color, 'string', null); // prettier-ignore
- w_Highlight = () => this.Highlight; // prettier-ignore
- w_Width = () => this.Width; // prettier-ignore
- w_Height = () => this.Height; // prettier-ignore
- w_AutoDim = () => this.AutoDim; // prettier-ignore
- w_Transition = () => this.Transition; // prettier-ignore
- w_DataTransition = () => this.DataTransition; // prettier-ignore
-
+ DataTransition = () => this._props.transition; // prettier-ignore
+ RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
- componentDidUpdate(prevProps: Readonly<React.PropsWithChildren<CollectionFreeFormDocumentViewWrapperProps & { fieldKey: string }>>) {
- super.componentDidUpdate(prevProps);
- this.WrapperKeys.forEach(action(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])));
- }
- render() {
- const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function });
- return (
- <CollectionFreeFormDocumentView
- {...OmitKeys(this._props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore
- {...layoutProps}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- />
- );
- }
-}
-export interface CollectionFreeFormDocumentViewProps {
- w_X: () => number;
- w_Y: () => number;
- w_Z: () => number;
- w_ZIndex?: () => number;
- w_Rotation?: () => number;
- w_Color: () => string;
- w_BackgroundColor: () => string;
- w_Opacity: () => number | undefined;
- w_Highlight: () => boolean | undefined;
- w_Transition: () => string | undefined;
- w_Width: () => number;
- w_Height: () => number;
- w_DataTransition: () => string | undefined;
- PanelWidth: () => number;
- PanelHeight: () => number;
- RenderCutoffProvider: (doc: Doc) => boolean;
- CollectionFreeFormView: CollectionFreeFormView;
-}
-
-@observer
-export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() {
- constructor(props: any) {
- super(props);
- makeObservable(this);
- }
- get displayName() { // this makes mobx trace() statements more descriptive
- return 'CollectionFreeFormDocumentView(' + this.Document.title + ')';
- } // prettier-ignore
- public static animFields: { key: string; val?: number }[] = [
- { key: 'x' },
- { key: 'y' },
- { key: 'opacity', val: 1 },
- { key: '_height' },
- { key: '_width' },
- { key: '_rotation', val: 0 },
- { key: '_layout_scrollTop' },
- { key: '_currentFrame' },
- { key: 'freeform_scale', val: 1 },
- { key: 'freeform_panX' },
- { key: 'freeform_panY' },
- ]; // fields that are configured to be animatable using animation frames
- public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
- public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
-
- get CollectionFreeFormView() {
- return this._props.CollectionFreeFormView;
- }
-
- styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => {
+ styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => {
if (doc === this.layoutDoc) {
switch (property) {
- case StyleProp.Opacity: return this._props.w_Opacity(); // only change the opacity for this specific document, not its children
- case StyleProp.BackgroundColor: return this._props.w_BackgroundColor();
- case StyleProp.Color: return this._props.w_Color();
+ case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children
+ case StyleProp.BackgroundColor: return this.BackgroundColor;
+ case StyleProp.Color: return this.Color;
} // prettier-ignore
}
return this._props.styleProvider?.(doc, props, property);
@@ -227,9 +181,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
});
}
- @action public float = () => {
+ float = () => {
const topDoc = this.Document;
- const containerDocView = this._props.docViewPath().lastElement();
+ const containerDocView = this._props.containerViewPath?.().lastElement();
const screenXf = containerDocView?.screenToContentsTransform();
if (screenXf) {
SelectionManager.DeselectAll();
@@ -252,15 +206,15 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
};
nudge = (x: number, y: number) => {
- const [locX, locY] = this.ScreenToLocalBoxXf().transformDirection(x, y);
- this._props.Document.x = this._props.w_X() + locX;
- this._props.Document.y = this._props.w_Y() + locY;
+ const [locX, locY] = this._props.ScreenToLocalTransform().transformDirection(x, y);
+ this.Document.x = this.X + locX;
+ this.Document.y = this.Y + locY;
};
screenToLocalTransform = () =>
this._props
.ScreenToLocalTransform()
- .translate(-this._props.w_X(), -this._props.w_Y())
- .rotateDeg(-(this._props.w_Rotation?.() || 0));
+ .translate(-this.X, -this.Y)
+ .rotateDeg(-(this.Rotation || 0));
returnThis = () => this;
/// this indicates whether the doc view is activated because of its relationshop to a group
@@ -273,25 +227,31 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const isGroup = this.dataDoc.isGroup && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined;
};
- public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container';
render() {
TraceMobx();
- const passOnProps = OmitKeys(this._props, Object.keys(this._props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore
+
return (
<div
className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName}
style={{
- width: this._props.PanelWidth(),
- height: this._props.PanelHeight(),
- transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
- transition: this._props.w_Transition?.() ?? (this._props.w_DataTransition?.() || this._props.w_Transition?.()),
- zIndex: this._props.w_ZIndex?.(),
- display: this._props.w_Width?.() ? undefined : 'none',
+ width: this.PanelWidth(),
+ height: this.PanelHeight(),
+ transform: `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rotation)}deg)`,
+ transition: this.Transition || StrCast(this.Document.dataTransition),
+ zIndex: this.ZIndex,
+ display: this.Width ? undefined : 'none',
}}>
- {this._props.RenderCutoffProvider(this._props.Document) ? (
- <div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
+ {this.RenderCutoffProvider(this.Document) ? (
+ <div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} />
) : (
- <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
+ <DocumentView
+ {...OmitKeys(this._props,this.WrapperKeys.map(val => val.lower)).omit} // prettier-ignore
+ DataTransition={this.DataTransition}
+ CollectionFreeFormDocumentView={this.returnThis}
+ styleProvider={this.styleProvider}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ isGroupActive={this.isGroupActive}
+ />
)}
</div>
);
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 5e7e568b0..116dc48a6 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -8,20 +8,20 @@ import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import './ComparisonBox.scss';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from './DocumentView';
+import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { PinProps, PresBox } from './trails';
@observer
-export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ComparisonBox, fieldKey);
}
private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined];
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
@@ -35,7 +35,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
return '_' + this._props.fieldKey + '_clipWidth';
}
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => {
this._disposers[disposerId]?.();
@@ -148,7 +148,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
e => this.clearDoc(which)
);
};
- docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+ docStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
if (property === StyleProp.PointerEvents) return 'none';
return this._props.styleProvider?.(doc, props, property);
};
@@ -178,6 +178,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
{...this._props}
Document={targetDoc}
TemplateDataDocument={undefined}
+ containerViewPath={this.DocumentView?.().docViewPath}
moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2}
removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2}
NativeWidth={returnZero}
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss
index a3132dc6e..6b5738790 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.scss
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss
@@ -29,6 +29,10 @@
}
}
+ .liveSchema-checkBox {
+ margin-bottom: -35px;
+ }
+
.dataviz-sidebar {
position: absolute;
right: 0;
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 5a55ca764..66a08f13e 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
-import { ObservableMap, action, computed, observable, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils';
@@ -11,23 +11,23 @@ import { listSpec } from '../../../../fields/Schema';
import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { CsvField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
-import { Docs } from '../../../documents/Documents';
+import { DocUtils, Docs } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { MarqueeAnnotator } from '../../MarqueeAnnotator';
import { SidebarAnnos } from '../../SidebarAnnos';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { AnchorMenu } from '../../pdf/AnchorMenu';
import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { PinProps } from '../trails';
import './DataVizBox.scss';
import { Histogram } from './components/Histogram';
import { LineChart } from './components/LineChart';
import { PieChart } from './components/PieChart';
import { TableBox } from './components/TableBox';
+import { Checkbox } from '@mui/material';
export enum DataVizView {
TABLE = 'table',
@@ -37,22 +37,28 @@ export enum DataVizView {
}
@observer
-export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
- private _ffref = React.createRef<CollectionFreeFormView>();
private _marqueeref = React.createRef<MarqueeAnnotator>();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
+ private _disposers: { [name: string]: IReactionDisposer } = {};
anchorMenuClick?: () => undefined | ((anchor: Doc) => void);
crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
- @observable schemaDataVizChildren: any = undefined;
@observable _marqueeing: number[] | undefined = undefined;
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ this._props.setContentViewBox?.(this);
+ }
+
@computed get annotationLayer() {
TraceMobx();
return <div className="dataVizBox-annotationLayer" style={{ height: this._props.PanelHeight(), width: this._props.PanelWidth() }} ref={this._annotationLayer} />;
}
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.Document._freeform_scale, 1) <= NumCast(this.Document.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -80,6 +86,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// all datasets that have been retrieved from the server stored as a map from the dataset url to an array of records
static dataset = new ObservableMap<string, { [key: string]: string }[]>();
+ // when a dataset comes from schema view, this stores the original dataset to refer back to
+ // href : dataset
+ static datasetSchemaOG = new ObservableMap<string, { [key: string]: string }[]>();
+
private _vizRenderer: LineChart | Histogram | PieChart | undefined;
private _sidebarRef = React.createRef<SidebarAnnos>();
@@ -98,10 +108,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return Cast(this.dataDoc[this.fieldKey], CsvField);
}
@computed.struct get axes() {
- return StrListCast(this.layoutDoc.dataViz_axes);
+ return StrListCast(this.layoutDoc._dataViz_axes);
}
-
- selectAxes = (axes: string[]) => (this.layoutDoc.dataViz_axes = new List<string>(axes));
+ selectAxes = (axes: string[]) => (this.layoutDoc._dataViz_axes = new List<string>(axes));
+ @computed.struct get titleCol() {
+ return StrCast(this.layoutDoc._dataViz_titleCol);
+ }
+ selectTitleCol = (titleCol: string) => (this.layoutDoc._dataViz_titleCol = titleCol);
@action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
restoreView = (data: Doc) => {
@@ -211,12 +224,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
.ScreenToLocalTransform()
.scale(this._props.NativeDimScaling?.() || 1)
.transformDirection(delta[0], delta[1]);
- const fullWidth = this._props.width;
- const mapWidth = fullWidth! - this.sidebarWidth();
+ const fullWidth = NumCast(this.layoutDoc._width);
+ const mapWidth = fullWidth - this.sidebarWidth();
if (this.sidebarWidth() + localDelta[0] > 0) {
this.layoutDoc._layout_showSidebar = true;
- this.layoutDoc._width = fullWidth! + localDelta[0];
- this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth! + localDelta[0])).toString() + '%';
+ this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
+ this.layoutDoc._width = fullWidth + localDelta[0];
} else {
this.layoutDoc._layout_showSidebar = false;
this.layoutDoc._width = mapWidth;
@@ -228,7 +241,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
() => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar')
);
};
- getView = async (doc: Doc, options: DocFocusOptions) => {
+ getView = async (doc: Doc, options: FocusViewOptions) => {
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
options.didMove = true;
this.toggleSidebar();
@@ -249,14 +262,75 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeDocument(doc, sidebarKey);
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
+ this._disposers.datavis = reaction(
+ () => {
+ if (this.layoutDoc.dataViz_schemaLive==undefined) this.layoutDoc.dataViz_schemaLive = true;
+ const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
+ const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
+ if (!keys) return;
+ const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
+ var current: { [key: string]: string }[] = [];
+ children
+ .filter(child => child)
+ .forEach(child => {
+ const row: { [key: string]: string } = {};
+ keys.forEach(key => {
+ var cell = child[key];
+ if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ row[key] = StrCast(cell);
+ });
+ current.push(row);
+ });
+ if (!this.layoutDoc._dataViz_schemaOG){ // makes a copy of the original table for the "live" toggle
+ let csvRows = [];
+ csvRows.push(keys.join(','));
+ for (let i = 0; i < children.length-1; i++) {
+ let eachRow = [];
+ for (let j = 0; j < keys.length; j++) {
+ var cell = children[i][keys[j]];
+ if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
+ eachRow.push(cell);
+ }
+ csvRows.push(eachRow);
+ }
+ const blob = new Blob([csvRows.join('\n')], { type: 'text/csv' });
+ const options = { x: 0, y: 0, title: 'schemaTable for static dataviz', _width: 300, _height: 100, type: 'text/csv' };
+ const file = new File([blob], 'schemaTable for static dataviz', options);
+ const loading = Docs.Create.LoadingDocument(file, options);
+ DocUtils.uploadFileToDoc(file, {}, loading);
+ this.layoutDoc._dataViz_schemaOG = loading;
+ }
+ const ogDoc = this.layoutDoc._dataViz_schemaOG as Doc
+ const ogHref = CsvCast(ogDoc[this.fieldKey])? CsvCast(ogDoc[this.fieldKey]).url.href : undefined;
+ const href = CsvCast(this.Document[this.fieldKey]).url.href
+ if (ogHref && !DataVizBox.datasetSchemaOG.has(href)){ // sets original dataset to the var
+ const lastRow = current.pop();
+ DataVizBox.datasetSchemaOG.set(href, current);
+ current.push(lastRow!);
+ fetch('/csvData?uri=' + ogHref)
+ .then(res => res.json().then(action(res => !res.errno && DataVizBox.datasetSchemaOG.set(href, res))));
+ }
+ return current;
+ },
+ current => {
+ if (current) {
+ const href = CsvCast(this.Document[this.fieldKey]).url.href;
+ if (this.layoutDoc.dataViz_schemaLive) DataVizBox.dataset.set(href, current);
+ else DataVizBox.dataset.set(href, DataVizBox.datasetSchemaOG.get(href)!);
+ }
+ },
+ { fireImmediately: true }
+ );
}
fetchData = () => {
- DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
- fetch('/csvData?uri=' + this.dataUrl?.url.href) //
- .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
+ if (!this.Document.dataViz_asSchema) {
+ DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
+ fetch('/csvData?uri=' + this.dataUrl?.url.href) //
+ .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res))));
+ }
};
// toggles for user to decide which chart type to view the data in
@@ -267,14 +341,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
layoutDoc: this.layoutDoc,
records: this.records,
axes: this.axes,
+ titleCol: this.titleCol,
//width: this.SidebarShown? this._props.PanelWidth()*.9/1.2: this._props.PanelWidth() * 0.9,
height: (this._props.PanelHeight() / scale - 32) /* height of 'change view' button */ * 0.9,
- width: (this._props.PanelWidth() / scale) * 0.9,
+ width: ((this._props.PanelWidth() - this.sidebarWidth()) / scale) * 0.9,
margin: { top: 10, right: 25, bottom: 75, left: 45 },
};
if (!this.records.length) return 'no data/visualization';
switch (this.dataVizView) {
- case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this._props.DocumentView} selectAxes={this.selectAxes} />;
+ case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} vizBox={this} />;
case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />;
case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)}
@@ -285,7 +360,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
onPointerDown = (e: React.PointerEvent): void => {
if ((this.Document._freeform_scale || 1) !== 1) return;
- if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
this._props.select(false);
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX, e.clientY];
@@ -323,32 +398,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
};
@action
- updateSchemaViz = () => {
- const getFrom = DocCast(this.layoutDoc.dataViz_asSchema);
- const keys = Cast(getFrom.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text');
- if (!keys) return;
- const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]);
- var current: { [key: string]: string }[] = [];
- for (let i = 0; i < children.length; i++) {
- var row: { [key: string]: string } = {};
- if (children[i]) {
- for (let j = 0; j < keys.length; j++) {
- var cell = children[i][keys[j]];
- if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, '');
- row[keys[j]] = StrCast(cell);
- }
- }
- current.push(row);
- }
- DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current);
- };
+ changeLiveSchemaCheckbox = () => {
+ this.layoutDoc.dataViz_schemaLive = !this.layoutDoc.dataViz_schemaLive
+ }
render() {
- if (this.layoutDoc && this.layoutDoc.dataViz_asSchema) {
- this.schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length;
- this.updateSchemaViz();
- }
-
const scale = this._props.NativeDimScaling?.() || 1;
return !this.records.length ? (
// displays how to get data into the DataVizBox if its empty
@@ -366,34 +420,22 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}}
onWheel={e => e.stopPropagation()}
ref={this._mainCont}>
- <div className={'datatype-button'}>
+ <div className="datatype-button">
<Toggle text={' TABLE '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} />
<Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} />
<Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} />
<Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} />
</div>
- {/* <CollectionFreeFormView
- ref={this._ffref}
- {...this._props}
- setContentView={emptyFunction}
- renderDepth={this._props.renderDepth - 1}
- fieldKey={this.annotationKey}
- styleProvider={this._props.styleProvider}
- isAnnotationOverlay={true}
- annotationLayerHostsContent={false}
- PanelWidth={this._props.PanelWidth}
- PanelHeight={this._props.PanelHeight}
- select={emptyFunction}
- isAnyChildContentActive={returnFalse}
- whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}>
- {this.renderVizView}
- </CollectionFreeFormView> */}
+ {(this.layoutDoc && this.layoutDoc.dataViz_asSchema)?(
+ <div className={'liveSchema-checkBox'} style={{ width: this._props.width }}>
+ <Checkbox color="primary" onChange={this.changeLiveSchemaCheckbox} checked={this.layoutDoc.dataViz_schemaLive as boolean} />
+ Display Live Updates to Canvas
+ </div>
+ ) : null}
{this.renderVizView}
+
<div className="dataviz-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }} onPointerDown={this.onPointerDown}>
<SidebarAnnos
ref={this._sidebarRef}
@@ -414,7 +456,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</div>
{this.sidebarHandle}
{this.annotationLayer}
- {!this._mainCont.current || !this._annotationLayer.current ? null : (
+ {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
Document={this.Document}
@@ -422,7 +464,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
scrollTop={0}
annotationLayerScrollTop={NumCast(this.Document._layout_scrollTop)}
scaling={returnOne}
- docView={this._props.DocumentView!}
+ docView={this.DocumentView}
addDocument={this.sidebarAddDocument}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss
index 2f7dd0487..41ce637ac 100644
--- a/src/client/views/nodes/DataVizBox/components/Chart.scss
+++ b/src/client/views/nodes/DataVizBox/components/Chart.scss
@@ -19,8 +19,6 @@
margin-bottom: -20px;
}
.asHistogram-checkBox {
- // display: flex;
- // flex-direction: row;
align-items: left;
align-self: left;
align-content: left;
@@ -93,7 +91,6 @@
display: flex;
flex-direction: column;
cursor: default;
- margin-top: 30px;
height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows
.tableBox-container {
overflow: scroll;
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index 4a1fb2ed1..6672603f3 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -20,6 +20,7 @@ export interface HistogramProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -63,17 +64,17 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
if (this._props.axes.length < 1) return [];
if (this._props.axes.length < 2) {
var ax0 = this._props.axes[0];
- if (/\d/.test(this._props.records[0][ax0])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])){
this.numericalXData = true;
}
return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]] }));
}
var ax0 = this._props.axes[0];
var ax1 = this._props.axes[1];
- if (/\d/.test(this._props.records[0][ax0])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax0])) {
this.numericalXData = true;
}
- if (/\d/.test(this._props.records[0][ax1])) {
+ if (!/[A-Za-z-:]/.test(this._props.records[0][ax1])) {
this.numericalYData = true;
}
return this._tableData.map(record => ({ [ax0]: record[this._props.axes[0]], [ax1]: record[this._props.axes[1]] }));
@@ -89,9 +90,6 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
@computed get parentViz() {
return DocCast(this._props.Document.dataViz_parentViz);
- // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
- // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
- // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
}
@computed get rangeVals(): { xMin?: number; xMax?: number; yMin?: number; yMax?: number } {
@@ -438,9 +436,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
this.updateBarColors();
this._histogramData;
var curSelectedBarName = '';
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_histogram_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
@@ -454,6 +452,18 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
: ''
);
selected = selected.substring(0, selected.length - 2) + ' }';
+ if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
+ selected+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
+ if (this._props.axes[1]){
+ if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
+ }
+ else selected+= each[this._props.titleCol] + ", ";
+ }
+ })
+ selected = selected.slice(0,-1).slice(0,-1);
+ }
}
var selectedBarColor;
var barColors = StrListCast(this._props.layoutDoc.histogramBarColors).map(each => each.split('::'));
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 2a9a8b354..e093ec648 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -28,6 +28,7 @@ export interface LineChartProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -46,7 +47,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
private _disposers: { [key: string]: IReactionDisposer } = {};
private _lineChartRef: React.RefObject<HTMLDivElement> = React.createRef();
private _lineChartSvg: d3.Selection<SVGGElement, unknown, null, undefined> | undefined;
- @observable _currSelected: SelectedDataPoint | undefined = undefined;
+ @observable _currSelected: any | undefined = undefined;
// TODO: nda - some sort of mapping that keeps track of the annotated points so we can easily remove when annotations list updates
constructor(props: any) {
super(props);
@@ -235,21 +236,16 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
}
- // TODO: nda - can use d3.create() to create html element instead of appending
drawChart = (dataSet: any[][], rangeVals: { xMin?: number; xMax?: number; yMin?: number; yMax?: number }, width: number, height: number) => {
// clearing tooltip and the current chart
d3.select(this._lineChartRef.current).select('svg').remove();
d3.select(this._lineChartRef.current).select('.tooltip').remove();
- const { xMin, xMax, yMin, yMax } = rangeVals;
+ var { xMin, xMax, yMin, yMax } = rangeVals;
if (xMin === undefined || xMax === undefined || yMin === undefined || yMax === undefined) {
return;
}
- // creating the x and y scales
- const xScale = scaleCreatorNumerical(xMin, xMax, 0, width);
- const yScale = scaleCreatorNumerical(0, yMax, height, 0);
-
// adding svg
const margin = this._props.margin;
const svg = (this._lineChartSvg = d3
@@ -261,24 +257,71 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`));
+ var validSecondData;
+ if (this._props.axes.length>2){ // for when there are 2 lines on the chart
+ var next = this._tableData.map(record => ({ x: Number(record[this._props.axes[0]]), y: Number(record[this._props.axes[2]]) })).sort((a, b) => (a.x < b.x ? -1 : 1));
+ validSecondData = next.filter(d => {
+ if (!d.x || Number.isNaN(d.x) || !d.y || Number.isNaN(d.y)) return false;
+ return true;
+ });
+ var secondDataRange = minMaxRange([validSecondData]);
+ if (secondDataRange.xMax!>xMax) xMax = secondDataRange.xMax;
+ if (secondDataRange.yMax!>yMax) yMax = secondDataRange.yMax;
+ if (secondDataRange.xMin!<xMin) xMin = secondDataRange.xMin;
+ if (secondDataRange.yMin!<yMin) yMin = secondDataRange.yMin;
+ }
+
+ // creating the x and y scales
+ const xScale = scaleCreatorNumerical(xMin!, xMax!, 0, width);
+ const yScale = scaleCreatorNumerical(0, yMax!, height, 0);
+ const lineGen = createLineGenerator(xScale, yScale);
+
// create x and y grids
xGrid(svg.append('g'), height, xScale);
yGrid(svg.append('g'), width, yScale);
xAxisCreator(svg.append('g'), height, xScale);
yAxisCreator(svg.append('g'), width, yScale);
+ if (validSecondData) {
+ drawLine(svg.append('path'), validSecondData, lineGen, true);
+ this.drawDataPoints(validSecondData, 0, xScale, yScale);
+ svg.append('path').attr("stroke", "red");
+
+ // legend
+ var color = d3.scaleOrdinal()
+ .range(["black", "blue"])
+ .domain([this._props.axes[1], this._props.axes[2]])
+ svg.selectAll("mydots")
+ .data([this._props.axes[1], this._props.axes[2]])
+ .enter()
+ .append("circle")
+ .attr("cx", 5)
+ .attr("cy", function(d,i){ return -30 + i*15})
+ .attr("r", 7)
+ .style("fill", function(d){ return color(d)})
+ svg.selectAll("mylabels")
+ .data([this._props.axes[1], this._props.axes[2]])
+ .enter()
+ .append("text")
+ .attr("x", 25)
+ .attr("y", function(d,i){ return -30 + i*15})
+ .style("fill", function(d){ return color(d)})
+ .text(function(d){ return d})
+ .attr("text-anchor", "left")
+ .style("alignment-baseline", "middle")
+ }
+
// get valid data points
const data = dataSet[0];
- const lineGen = createLineGenerator(xScale, yScale);
var validData = data.filter(d => {
- var valid = true;
Object.keys(data[0]).map(key => {
- if (!d[key] || Number.isNaN(d[key])) valid = false;
+ if (!d[key] || Number.isNaN(d[key])) return false;
});
- return valid;
+ return true;
});
+
// draw the plot line
- drawLine(svg.append('path'), validData, lineGen);
+ drawLine(svg.append('path'), validData, lineGen, false);
// draw the datapoint circle
this.drawDataPoints(validData, 0, xScale, yScale);
@@ -291,7 +334,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
const xPos = d3.pointer(e)[0];
const x0 = Math.min(data.length - 1, bisect(data, xScale.invert(xPos - 5))); // shift x by -5 so that you can reach points on the left-side axis
const d0 = data[x0];
- if (!d0) return;
+ if (d0) this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
this.updateTooltip(higlightFocusPt, xScale, d0, yScale, tooltip);
});
@@ -327,7 +370,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
svg.append('text')
.attr('transform', 'rotate(-90)' + ' ' + 'translate( 0, ' + -10 + ')')
.attr('x', -(height / 2))
- .attr('y', -20)
+ .attr('y', -30)
.attr('height', 20)
.attr('width', 20)
.style('text-anchor', 'middle')
@@ -351,11 +394,22 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
render() {
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_lineChart_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
+ var selectedTitle = "";
+ if (this._currSelected && this._props.titleCol){
+ selectedTitle+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ var mapThisEntry = false;
+ if (this._currSelected.x==each[this._props.axes[0]] && this._currSelected.y==each[this._props.axes[1]]) mapThisEntry = true;
+ else if (this._currSelected.y==each[this._props.axes[0]] && this._currSelected.x==each[this._props.axes[1]]) mapThisEntry = true;
+ if (mapThisEntry) selectedTitle += each[this._props.titleCol] + ", ";
+ })
+ selectedTitle = selectedTitle.slice(0,-1).slice(0,-1);
+ }
if (this._lineChartData.length > 0 || !this.parentViz || this.parentViz.length == 0) {
return this._props.axes.length >= 2 && /\d/.test(this._props.records[0][this._props.axes[0]]) && /\d/.test(this._props.records[0][this._props.axes[1]]) ? (
<div className="chart-container" style={{ width: this._props.width + this._props.margin.right }}>
@@ -375,9 +429,9 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
{selectedPt != 'none' ? (
<div className={'selected-data'}>
{`Selected: ${selectedPt}`}
+ {`${selectedTitle}`}
<Button
onClick={e => {
- console.log('test plzz');
this._props.vizBox.sidebarBtnDown;
this._props.vizBox.sidebarAddDocument;
}}></Button>
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 1259a13ff..fc23f47de 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -19,6 +19,7 @@ export interface PieChartProps {
Document: Doc;
layoutDoc: Doc;
axes: string[];
+ titleCol: string;
records: { [key: string]: any }[];
width: number;
height: number;
@@ -331,22 +332,34 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
};
render() {
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_pie_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
var selected: string;
var curSelectedSliceName = '';
if (this._currSelected) {
+ selected = '{ ';
const sliceTitle = this._currSelected[this._props.axes[0]];
curSelectedSliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/\%/g, '').replace(/\#/g, '').replace(/\</g, '') : sliceTitle;
- selected = '{ ';
Object.keys(this._currSelected).map(key => {
key != '' ? (selected += key + ': ' + this._currSelected[key] + ', ') : '';
});
selected = selected.substring(0, selected.length - 2);
selected += ' }';
+ if (this._props.titleCol!="" && (!this._currSelected["frequency"] || this._currSelected["frequency"]<10)){
+ selected+= "\n" + this._props.titleCol + ": "
+ this._tableData.forEach(each => {
+ if (this._currSelected[this._props.axes[0]]==each[this._props.axes[0]]) {
+ if (this._props.axes[1]){
+ if (this._currSelected[this._props.axes[1]]==each[this._props.axes[1]]) selected+= each[this._props.titleCol] + ", ";
+ }
+ else selected+= each[this._props.titleCol] + ", ";
+ }
+ })
+ selected = selected.slice(0,-1).slice(0,-1);
+ }
} else selected = 'none';
var selectedSliceColor;
var sliceColors = StrListCast(this._props.layoutDoc.dataViz_pie_sliceColors).map(each => each.split('::'));
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index ed44d9269..1b239b5e5 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -18,7 +18,9 @@ interface TableBoxProps {
layoutDoc: Doc;
records: { [key: string]: any }[];
selectAxes: (axes: string[]) => void;
+ selectTitleCol: (titleCol: string) => void;
axes: string[];
+ titleCol: string;
width: number;
height: number;
margin: {
@@ -83,14 +85,12 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
return this._props.docView?.()?.screenToViewTransform().Scale || 1;
}
@computed get rowHeight() {
- console.log('scale = ' + this.viewScale + ' table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
}
@computed get startID() {
return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight) - 1, 0) : 0;
}
@computed get endID() {
- console.log('start = ' + this.startID + ' container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
}
@action handleScroll = () => {
@@ -155,11 +155,18 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
},
emptyFunction,
action(e => {
- const newAxes = this._props.axes;
- if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
- else if (newAxes.length > 1) newAxes[1] = col;
- else newAxes.push(col);
- this._props.selectAxes(newAxes);
+ if (e.shiftKey){
+ if (this._props.titleCol == col) this._props.titleCol = "";
+ else this._props.titleCol = col;
+ this._props.selectTitleCol(this._props.titleCol);
+ }
+ else{
+ const newAxes = this._props.axes;
+ if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
+ else if (newAxes.length > 2) newAxes[newAxes.length-1] = col;
+ else newAxes.push(col);
+ this._props.selectAxes(newAxes);
+ }
})
);
};
@@ -213,8 +220,15 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
<th
key={this.columns.indexOf(col)}
style={{
- color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen' : this._props.axes.lastElement() === col ? 'darkred' : undefined,
- background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this._props.axes.lastElement() === col ? '#Fbdbdb' : undefined,
+ color: this._props.axes.slice().reverse().lastElement() === col ? 'darkgreen'
+ : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? 'darkred'
+ : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? 'darkblue' : undefined,
+ background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb'
+ : (this._props.axes.length>2 && this._props.axes.lastElement() === col) ? '#Fbdbdb'
+ : (this._props.axes.lastElement()===col || (this._props.axes.length>2 && this._props.axes[1]==col))? '#c6ebf7' : undefined,
+ // blue: #ADD8E6
+ // green: #E3fbdb
+ // red: #Fbdbdb
fontWeight: 'bolder',
border: '3px solid black',
}}
@@ -236,7 +250,11 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
}}>
{this.columns.map(col => {
- const colSelected = this._props.axes.length > 1 ? this._props.axes[0] == col || this._props.axes[1] == col : this._props.axes.length > 0 ? this._props.axes[0] == col : false;
+ var colSelected = false;
+ if (this._props.axes.length>2) colSelected = this._props.axes[0]==col || this._props.axes[1]==col || this._props.axes[2]==col;
+ else if (this._props.axes.length>1) colSelected = this._props.axes[0]==col || this._props.axes[1]==col;
+ else if (this._props.axes.length>0) colSelected = this._props.axes[0]==col;
+ if (this._props.titleCol==col) colSelected = true;
return (
<td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
<div className="tableBox-cell">{this._props.records[rowId][col]}</div>
diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
index 10bfb0c64..336935d23 100644
--- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
+++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts
@@ -61,6 +61,6 @@ export const yGrid = (g: d3.Selection<SVGGElement, unknown, null, undefined>, wi
);
};
-export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>) => {
- p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('class', 'line').attr('d', lineGen);
+export const drawLine = (p: d3.Selection<SVGPathElement, unknown, null, undefined>, dataPts: DataPoint[], lineGen: d3.Line<DataPoint>, extra: boolean) => {
+ p.datum(dataPts).attr('fill', 'none').attr('stroke', 'rgba(53, 162, 235, 0.5)').attr('stroke-width', 2).attr('stroke', extra? 'blue' : 'black').attr('class', 'line').attr('d', lineGen);
};
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 4cc7fe4c8..07e179246 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,7 +24,6 @@ import { YoutubeBox } from './../../apis/youtube/YoutubeBox';
import { AudioBox } from './AudioBox';
import { ComparisonBox } from './ComparisonBox';
import { DataVizBox } from './DataVizBox/DataVizBox';
-import { DocumentViewProps } from './DocumentView';
import './DocumentView.scss';
import { EquationBox } from './EquationBox';
import { FieldView, FieldViewProps } from './FieldView';
@@ -114,14 +113,11 @@ export class HTMLtag extends React.Component<HTMLtagProps> {
}
}
+export interface DocumentContentsViewProps extends FieldViewProps {
+ layoutFieldKey: string;
+}
@observer
-export class DocumentContentsView extends ObservableReactComponent<
- DocumentViewProps &
- FieldViewProps & {
- setHeight?: (height: number) => void;
- layout_fieldKey: string;
- }
-> {
+export class DocumentContentsView extends ObservableReactComponent<DocumentContentsViewProps> {
constructor(props: any) {
super(props);
makeObservable(this);
@@ -131,8 +127,8 @@ export class DocumentContentsView extends ObservableReactComponent<
TraceMobx();
if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString;
if (!this.layoutDoc) return '<p>awaiting layout</p>';
- if (this._props.layout_fieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
- const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layout_fieldKey ? this._props.layout_fieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
+ if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString());
+ const layout = Cast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')], 'string');
if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString();
if (typeof layout === 'string') return layout;
return '<p>Loading layout</p>';
@@ -140,30 +136,31 @@ export class DocumentContentsView extends ObservableReactComponent<
get layoutDoc() {
// bcz: replaced this with below : is it correct? change was made to accommodate passing fieldKey's from a layout script
- // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
+ // const template: Doc = this._props.LayoutTemplate?.() || Doc.Layout(this._props.Document, this._props.fieldKey ? Cast(this._props.Document[this._props.fieldKey], Doc, null) : undefined);
const template: Doc =
this._props.LayoutTemplate?.() ||
(this._props.LayoutTemplateString && this._props.Document) ||
- (this._props.layout_fieldKey && StrCast(this._props.Document[this._props.layout_fieldKey]) && this._props.Document) ||
- Doc.Layout(this._props.Document, this._props.layout_fieldKey ? Cast(this._props.Document[this._props.layout_fieldKey], Doc, null) : undefined);
+ (this._props.layoutFieldKey && StrCast(this._props.Document[this._props.layoutFieldKey]) && this._props.Document) ||
+ Doc.Layout(this._props.Document, this._props.layoutFieldKey ? Cast(this._props.Document[this._props.layoutFieldKey], Doc, null) : undefined);
return Doc.expandTemplateLayout(template, this._props.Document);
}
CreateBindings(onClick: Opt<ScriptField>, onInput: Opt<ScriptField>): JsxBindings {
const docOnlyProps = [
- // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
+ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews
'hideResizeHandles',
'hideTitle',
- 'contentPointerEvents',
- 'radialMenu',
+ 'bringToFront',
+ 'childContentPointerEvents',
'LayoutTemplateString',
'LayoutTemplate',
+ 'layoutFieldKey',
'dontCenter',
'contextMenuItems',
//'onClick', // don't need to omit this since it will be set
- 'onDoubleClick',
- 'onPointerDown',
- 'onPointerUp',
+ 'onDoubleClickScript',
+ 'onPointerDownScript',
+ 'onPointerUpScript',
];
const templateDataDoc = this._props.TemplateDataDocument ?? (this.layoutDoc !== this._props.Document ? this._props.Document[DocData] : undefined);
const list: BindingProps & React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLDivElement>, HTMLDivElement> = {
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index dfd610581..4a22766cc 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -25,11 +25,11 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
}
static get DocViews() {
- return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.IsLightboxDocView(v._props.docViewPath())) : DocumentManager.Instance.DocumentViews;
+ return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews;
}
render() {
const view = this._props.view;
- const { left, top, right, bottom } = view.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 };
+ const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
return (
<div
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index c549a146a..2a68d2bf6 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -17,6 +17,7 @@ import { DocumentView } from './DocumentView';
import { LinkDescriptionPopup } from './LinkDescriptionPopup';
import { TaskCompletionBox } from './TaskCompletedBox';
import { PinProps } from './trails';
+import { DocData } from '../../../fields/DocSymbols';
interface DocumentLinksButtonProps {
View: DocumentView;
@@ -152,47 +153,46 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
DocumentLinksButton.AnnotationUri = undefined;
//!this._props.StartLink
} else if (startLink !== endLink) {
- endLink = endLinkView?.docView?._componentView?.getAnchor?.(true, pinProps) || endLink;
- startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink;
+ endLink = endLinkView?.ComponentView?.getAnchor?.(true, pinProps) || endLink;
+ startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
- LinkManager.currentLink = linkDoc;
-
- if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
- // if linking from a Hypothes.is annotation
- Doc.GetProto(linkDoc as Doc).linksToAnnotation = true;
- Doc.GetProto(linkDoc as Doc).annotationId = DocumentLinksButton.AnnotationId;
- Doc.GetProto(linkDoc as Doc).annotationUri = DocumentLinksButton.AnnotationUri;
- const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
- Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
- }
+ LinkManager.Instance.currentLink = linkDoc;
if (linkDoc) {
+ if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
+ // if linking from a Hypothes.is annotation
+ const linkDocData = linkDoc[DocData];
+ linkDocData.linksToAnnotation = true;
+ linkDocData.annotationId = DocumentLinksButton.AnnotationId;
+ linkDocData.annotationUri = DocumentLinksButton.AnnotationUri;
+ const dashHyperlink = Doc.globalServerPath(startIsAnnotation ? endLink : startLink);
+ Hypothesis.makeLink(StrCast(startIsAnnotation ? endLink.title : startLink.title), dashHyperlink, DocumentLinksButton.AnnotationId, startIsAnnotation ? startLink : endLink); // edit annotation to add a Dash hyperlink to the linked doc
+ }
+
TaskCompletionBox.textDisplayed = 'Link Created';
TaskCompletionBox.popupX = screenX;
TaskCompletionBox.popupY = screenY - 133;
TaskCompletionBox.taskCompleted = true;
- if (LinkDescriptionPopup.showDescriptions === 'ON' || !LinkDescriptionPopup.showDescriptions) {
- LinkDescriptionPopup.popupX = screenX;
- LinkDescriptionPopup.popupY = screenY - 100;
- LinkDescriptionPopup.descriptionPopup = true;
+ if (LinkDescriptionPopup.Instance.showDescriptions === 'ON' || !LinkDescriptionPopup.Instance.showDescriptions) {
+ LinkDescriptionPopup.Instance.popupX = screenX;
+ LinkDescriptionPopup.Instance.popupY = screenY - 100;
+ LinkDescriptionPopup.Instance.display = true;
}
const rect = document.body.getBoundingClientRect();
- if (LinkDescriptionPopup.popupX + 200 > rect.width) {
- LinkDescriptionPopup.popupX -= 190;
+ if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) {
+ LinkDescriptionPopup.Instance.popupX -= 190;
TaskCompletionBox.popupX -= 40;
}
- if (LinkDescriptionPopup.popupY + 100 > rect.height) {
- LinkDescriptionPopup.popupY -= 40;
+ if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) {
+ LinkDescriptionPopup.Instance.popupY -= 40;
TaskCompletionBox.popupY -= 40;
}
setTimeout(
- action(() => {
- TaskCompletionBox.taskCompleted = false;
- }),
+ action(() => (TaskCompletionBox.taskCompleted = false)),
2500
);
}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index c4dab16fb..5421c1b50 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -180,7 +180,6 @@
.documentView-titleWrapper,
.documentView-titleWrapper-hover {
- overflow: hidden;
color: $black;
transform-origin: top left;
top: 0;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6cc61ec62..b0e8cf6ff 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -3,64 +3,57 @@ import { Dropdown, DropdownType, Type } from 'browndash-components';
import { Howl } from 'howler';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
-import { RefField } from '../../../fields/RefField';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { AudioField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocOptions, DocUtils, Docs, FInfo } from '../../documents/Documents';
+import { DocOptions, DocUtils, Docs } from '../../documents/Documents';
import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { FollowLinkScript } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SearchUtil } from '../../util/SearchUtil';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
import { SnappingManager } from '../../util/SnappingManager';
-import { Transform } from '../../util/Transform';
-import { UndoManager, undoBatch } from '../../util/UndoManager';
+import { UndoManager, undoBatch, undoable } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { DocComponent } from '../DocComponent';
+import { DocComponent, ViewBoxInterface } from '../DocComponent';
import { EditableView } from '../EditableView';
import { GestureOverlay } from '../GestureOverlay';
import { LightboxView } from '../LightboxView';
-import { ObservableReactComponent } from '../ObservableReactComponent';
import { StyleProp } from '../StyleProvider';
-import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
import { DocumentLinksButton } from './DocumentLinksButton';
import './DocumentView.scss';
-import { FieldViewProps } from './FieldView';
+import { FieldViewProps, FieldViewSharedProps } from './FieldView';
import { KeyValueBox } from './KeyValueBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
-import { PinProps, PresBox } from './trails/PresBox';
-
+import { FieldsDropdown } from '../FieldsDropdown';
interface Window {
MediaRecorder: MediaRecorder;
}
-
declare class MediaRecorder {
- // whatever MediaRecorder has
- constructor(e: any);
+ constructor(e: any); // whatever MediaRecorder has
}
export enum OpenWhereMod {
@@ -89,118 +82,10 @@ export enum OpenWhere {
addRightKeyvalue = 'add:right:keyValue',
}
-export interface DocFocusOptions {
- willPan?: boolean; // determines whether to pan to target document
- willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document
- zoomScale?: number; // percent of containing frame to zoom into document
- zoomTime?: number;
- didMove?: boolean; // whether a document was changed during the showDocument process
- 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)
- preview?: boolean; // whether changes should be previewed by the componentView or written to the document
- effect?: Doc; // animation effect for focus
- noSelect?: boolean; // whether target should be selected after focusing
- playAudio?: boolean; // whether to play audio annotation on focus
- playMedia?: boolean; // whether to play start target videos
- openLocation?: OpenWhere; // where to open a missing document
- zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
- toggleTarget?: boolean; // whether to toggle target on and off
- anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
- easeFunc?: 'linear' | 'ease'; // transition method for scrolling
-}
-export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => Opt<number>;
-export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => any;
-export interface DocComponentView {
- fieldKey?: string;
- annotationKey?: string;
- updateIcon?: () => void; // updates the icon representation of the document
- getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
- restoreView?: (viewSpec: Doc) => boolean;
- scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus
- brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
- getView?: (doc: Doc, options: DocFocusOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
- addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
- addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
- select?: (ctrlKey: boolean, shiftKey: boolean) => void;
- focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
- isAnyChildContentActive?: () => boolean; // is any child content of the document active
- onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
- getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
- setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
- playFrom?: (time: number, endTime?: number) => void;
- Pause?: () => void; // pause a media document (eg, audio/video)
- IsPlaying?: () => boolean; // is a media document playing
- TogglePause?: (keep?: boolean) => void; // toggle media document playing state
- setFocus?: () => void; // sets input focus to the componentView
- setData?: (data: Field | Promise<RefField | undefined>) => boolean;
- componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
- dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
- incrementalRendering?: () => void;
- infoUI?: () => JSX.Element | null;
- screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
- ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
- ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
- snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
- search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
+export function returnEmptyDocViewList() {
+ return [] as DocumentView[];
}
-// These props are passed to both FieldViews and DocumentViews
-export interface DocumentViewSharedProps {
- renderDepth: number;
- Document: Doc;
- TemplateDataDocument?: Doc;
- scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
- DocumentView?: () => DocumentView;
- CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
- fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
- isGroupActive?: () => string | undefined; // is this document part of a group that is active
- setContentView?: (view: DocComponentView) => any;
- PanelWidth: () => number;
- PanelHeight: () => number;
- docViewPath: () => DocumentView[];
- childFilters: () => string[];
- childFiltersByRanges: () => string[];
- styleProvider: Opt<StyleProviderFunc>;
- setTitleFocus?: () => void;
- focus: DocFocusFunc;
- layout_fitWidth?: (doc: Doc) => boolean | undefined;
- searchFilterDocs: () => Doc[];
- layout_showTitle?: () => string;
- whenChildContentsActiveChanged: (isActive: boolean) => void;
- rootSelected?: () => boolean; // whether the root of a template has been selected
- addDocTab: (doc: Doc, where: OpenWhere) => 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[], annotationKey?: string) => boolean;
- removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
- moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
- pinToPres: (document: Doc, pinProps: PinProps) => void;
- ScreenToLocalTransform: () => Transform;
- bringToFront: (doc: Doc, sendToBack?: boolean) => void;
- waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
- defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
- pointerEvents?: () => Opt<string>;
- treeViewDoc?: Doc;
- xPadding?: number;
- yPadding?: number;
- dontRegisterView?: boolean;
- childHideDecorationTitle?: boolean;
- childHideResizeHandles?: boolean;
- childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
- dropAction?: dropActionType;
- dragAction?: dropActionType;
- dragWhenActive?: boolean;
- dontHideOnDrag?: boolean;
- hideLinkButton?: boolean;
- hideCaptions?: boolean;
- ignoreAutoHeight?: boolean;
- forceAutoHeight?: boolean;
- suppressSetHeight?: boolean;
- disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
- onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
-}
-
-// these props are specific to DocuentViews
-export interface DocumentViewProps extends DocumentViewSharedProps {
- // properties specific to DocumentViews but not to FieldView
+export interface DocumentViewProps extends FieldViewSharedProps {
hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations 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
@@ -209,37 +94,36 @@ export interface DocumentViewProps extends DocumentViewSharedProps {
hideOpenButton?: boolean;
hideDeleteButton?: boolean;
hideLinkAnchors?: boolean;
+ hideLinkButton?: boolean;
+ hideCaptions?: boolean;
contentPointerEvents?: 'none' | 'all' | undefined; // 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
- LayoutTemplateString?: string;
dontCenter?: 'x' | 'y' | 'xy';
- isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
- isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+ childHideDecorationTitle?: boolean;
+ childHideResizeHandles?: boolean;
+ childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
+ dragWhenActive?: boolean;
+ dontHideOnDrag?: boolean;
+ suppressSetHeight?: boolean;
+ onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
+ DataTransition?: () => string | undefined;
NativeWidth?: () => number;
NativeHeight?: () => number;
- NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to FieldViewProps
- 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;
+ dragConfig?: (data: DragManager.DocumentDragData) => void;
dragStarting?: () => void;
dragEnding?: () => void;
}
-
-// these props are only available in DocumentViewIntenral
-export interface DocumentViewInternalProps extends DocumentViewProps {
- isSelected: () => boolean;
- select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
- DocumentView: () => DocumentView;
- viewPath: () => DocumentView[];
-}
-
@observer
-export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps>() {
+export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps>() {
+ // this makes mobx trace() statements more descriptive
+ public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
+ /**
+ * This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
+ * needing to know about/reference MainView
+ */
+ public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+
private _disposers: { [name: string]: IReactionDisposer } = {};
private _doubleClickTimeout: NodeJS.Timeout | undefined;
private _singleClickFunc: undefined | (() => any);
@@ -252,72 +136,51 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
private _mainCont = React.createRef<HTMLDivElement>();
private _titleRef = React.createRef<EditableView>();
private _dropDisposer?: DragManager.DragDropDisposer;
- constructor(props: any) {
+ constructor(props: FieldViewProps & DocumentViewProps) {
super(props);
makeObservable(this);
}
- @observable _componentView: Opt<DocComponentView> = undefined; // needs to be accessed from DocumentView wrapper class
+ @observable _changingTitleField = false;
+ @observable _titleDropDownInnerWidth = 0; // width of menu dropdown when setting doc title
+ @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
+ @observable _isContentActive: boolean | undefined = undefined;
+ @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
+ @observable _componentView: Opt<ViewBoxInterface> = undefined; // needs to be accessed from DocumentView wrapper class
@observable _animateScaleTime: Opt<number> = undefined; // milliseconds for animating between views. defaults to 300 if not uset
@observable _animateScalingTo = 0;
- public get animateScaleTime() {
- return this._animateScaleTime ?? 100;
- }
- public get displayName() {
- return 'DocumentViewInternal(' + this._props.Document.title + ')';
- } // this makes mobx trace() statements more descriptive
+ get _contentDiv() { return this._mainCont.current; } // prettier-ignore
+ get _docView() { return this._props.DocumentView?.(); } // prettier-ignore
+
+ animateScaleTime = () => this._animateScaleTime ?? 100;
+ style = (doc: Doc, sprop: StyleProp | string) => this._props.styleProvider?.(doc, this._props, sprop);
+ @computed get layout_showTitle() { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
+ @computed get opacity() { return this.style(this.layoutDoc, StyleProp.Opacity); } // prettier-ignore
+ @computed get boxShadow() { return this.style(this.layoutDoc, StyleProp.BoxShadow); } // prettier-ignore
+ @computed get borderRounding() { return this.style(this.layoutDoc, StyleProp.BorderRounding); } // prettier-ignore
+ @computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations); } // prettier-ignore
+ @computed get backgroundBoxColor() { return this.style(this.layoutDoc, StyleProp.BackgroundColor + ':box'); } // prettier-ignore
+ @computed get headerMargin() { return this.style(this.layoutDoc, StyleProp.HeaderMargin) ?? 0; } // prettier-ignore
+ @computed get layout_showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) ?? 0; } // prettier-ignore
+ @computed get titleHeight() { return this.style(this.layoutDoc, StyleProp.TitleHeight) ?? 0; } // prettier-ignore
+ @computed get docContents() { return this.style(this.Document, StyleProp.DocContents); } // prettier-ignore
+ @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
+ @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
- public get ContentDiv() {
- return this._mainCont.current;
- }
- public get LayoutFieldKey() {
- return Doc.LayoutFieldKey(this.layoutDoc);
- }
- @computed get layout_showTitle() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowTitle) as Opt<string>;
- }
- @computed get NativeDimScaling() {
- return this._props.NativeDimScaling?.() || 1;
- }
- @computed get thumb() {
- return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url?.href.replace('.png', '_m.png');
- }
- @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 widgetDecorations() {
- TraceMobx();
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Decorations);
- }
- @computed get backgroundBoxColor() {
- return this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor + ':box');
- }
- @computed get docContents() {
- return this._props.styleProvider?.(this.Document, this._props, StyleProp.DocContents);
- }
- @computed get headerMargin() {
- return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.HeaderMargin) || 0;
- }
- @computed get layout_showCaption() {
- return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.ShowCaption) || 0;
+ @computed get onClickHandler() {
+ return this._props.onClickScript?.() ?? this._props.onBrowseClickScript?.() ?? ScriptCast(this.Document.onClick, ScriptCast(this.layoutDoc.onClick));
}
- @computed get titleHeight() {
- return this._props?.styleProvider?.(this.layoutDoc, this._props, StyleProp.TitleHeight) || 0;
+ @computed get onDoubleClickHandler() {
+ return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick, ScriptCast(this.Document.onDoubleClick));
}
- @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined = undefined;
- @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined {
- return this._pointerEvents;
+ @computed get onPointerDownHandler() {
+ return this._props.onPointerDownScript?.() ?? ScriptCast(this.layoutDoc.onPointerDown, ScriptCast(this.Document.onPointerDown));
}
- @computed get finalLayoutKey() {
- return StrCast(this.Document.layout_fieldKey, 'layout');
+ @computed get onPointerUpHandler() {
+ return this._props.onPointerUpScript?.() ?? ScriptCast(this.layoutDoc.onPointerUp, ScriptCast(this.Document.onPointerUp));
}
+
@computed get disableClickScriptFunc() {
const onScriptDisable = this._props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable;
// prettier-ignore
@@ -327,83 +190,110 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
(onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.()))
);
}
- @computed get onClickHandler() {
- return this._props.onClick?.() ?? this._props.onBrowseClick?.() ?? Cast(this.Document.onClick, ScriptField, Cast(this.layoutDoc.onClick, ScriptField, null));
+ @computed get _rootSelected() {
+ return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
}
- @computed get onDoubleClickHandler() {
- return this._props.onDoubleClick?.() ?? Cast(this.layoutDoc.onDoubleClick, ScriptField, null) ?? this.Document.onDoubleClick;
+ /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
+ @computed get _contentPointerEvents() {
+ TraceMobx();
+ return this._props.contentPointerEvents ??
+ ((!this.disableClickScriptFunc && //
+ this.onClickHandler &&
+ !this._props.onBrowseClickScript?.() &&
+ this.isContentActive() !== true) ||
+ this.isContentActive() === false)
+ ? 'none'
+ : this._pointerEvents;
}
- @computed get onPointerDownHandler() {
- return this._props.onPointerDown?.() ?? ScriptCast(this.Document.onPointerDown);
+
+ // 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 'layout_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.Document).filter(
+ link =>
+ (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) ||
+ (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) ||
+ ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
+ ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
+ );
}
- @computed get onPointerUpHandler() {
- return this._props.onPointerUp?.() ?? ScriptCast(this.Document.onPointerUp);
+ @computed get _allLinks() {
+ TraceMobx();
+ return LinkManager.Instance.getAllRelatedLinks(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
+ }
+
+ @computed get filteredLinks() {
+ return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
}
componentWillUnmount() {
this.cleanupHandlers(true);
}
- @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI )
componentDidMount() {
runInAction(() => (this._mounted = true));
this.setupHandlers();
this._disposers.contentActive = reaction(
- () => {
+ () =>
// true - if the document has been activated directly or indirectly (by having its children selected)
// false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive
// undefined - it is not active, but it should be responsive to actions that might activate it or its contents (eg clicking)
- return this._props.isContentActive() === false || this._props.pointerEvents?.() === 'none'
+ this._props.isContentActive() === false || this._props.pointerEvents?.() === 'none'
? false
: Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
? true
- : undefined;
- },
+ : undefined,
active => (this._isContentActive = active),
{ fireImmediately: true }
);
this._disposers.pointerevents = reaction(
- () => this._props.styleProvider?.(this.Document, this._props, StyleProp.PointerEvents),
+ () => this.style(this.Document, StyleProp.PointerEvents),
pointerevents => (this._pointerEvents = pointerevents),
{ fireImmediately: true }
);
}
- preDropFunc = (e: Event, de: DragManager.DropEvent) => {
- const dropAction = this.layoutDoc.dropAction as dropActionType;
- if (de.complete.docDragData && this.isContentActive() && !this._props.treeViewDoc) {
- dropAction && (de.complete.docDragData.dropAction = dropAction);
+ preDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
+ const dragData = de.complete.docDragData;
+ if (dragData && this.isContentActive() && !this.props.dontRegisterView) {
+ dragData.dropAction = dropAction ? dropAction : dragData.dropAction;
e.stopPropagation();
}
};
setupHandlers() {
this.cleanupHandlers(false);
if (this._mainCont.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this._props.Document, this.preDropFunc);
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.Document, this.preDrop);
}
}
cleanupHandlers(unbrush: boolean) {
this._dropDisposer?.();
- unbrush && Doc.UnBrushDoc(this._props.Document);
+ unbrush && Doc.UnBrushDoc(this.Document);
Object.values(this._disposers).forEach(disposer => disposer?.());
}
startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) {
- if (this._mainCont.current) {
- const views = SelectionManager.Views.filter(dv => dv.docView?._mainCont.current);
- const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [this._props.DocumentView()];
+ const docView = this._docView;
+ if (this._mainCont.current && docView) {
+ const views = SelectionManager.Views.filter(dv => dv.ContentDiv);
+ const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [docView];
const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document));
- const screenXf = this.props.DocumentView().screenToViewTransform();
+ const screenXf = docView.screenToViewTransform();
const [left, top] = screenXf.inverse().transformPoint(0, 0);
dragData.offset = screenXf.transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
- dragData.treeViewDoc = this._props.treeViewDoc;
dragData.removeDocument = this._props.removeDocument;
dragData.moveDocument = this._props.moveDocument;
- dragData.draggedViews = [this._props.DocumentView()];
+ dragData.draggedViews = [docView];
dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
+ (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData);
DragManager.StartDocumentDrag(
- selected.map(dv => dv.docView!._mainCont.current!),
+ selected.map(dv => dv.ContentDiv!),
dragData,
x,
y,
@@ -412,46 +302,38 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
}
- defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => {
- const targetMatch =
- Doc.AreProtosEqual(anchor, this.Document) || // anchor is this document, so anchor's properties apply to this document
- (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.Document)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document
- ? true
- : false;
- return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined;
- };
-
// switches text input focus to the title bar of the document (and displays the title bar if it hadn't been)
setTitleFocus = () => {
if (!StrCast(this.layoutDoc._layout_showTitle)) this.layoutDoc._layout_showTitle = 'title';
setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
};
-
- public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
-
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
- if (!this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
+ const documentView = this._docView;
+ if (documentView && !this.Document.ignoreClick && this._props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
let stopPropagate = true;
let preventDefault = true;
- !this.layoutDoc._keepZWhenDragged && this._props.bringToFront(this.Document);
+ !this.layoutDoc._keepZWhenDragged && this._props.bringToFront?.(this.Document);
+ const scriptProps = {
+ this: this.Document,
+ _readOnly_: false,
+ scriptContext: this._props.scriptContext,
+ documentView,
+ clientX: e.clientX,
+ clientY: e.clientY,
+ shiftKey: e.shiftKey,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ value: undefined,
+ };
if (this._doubleTap) {
const defaultDblclick = this._props.defaultDoubleClick?.() || this.Document.defaultDoubleClick;
if (this.onDoubleClickHandler?.script) {
- const { clientX, clientY, shiftKey, altKey, ctrlKey } = e; // or we could call e.persist() to capture variables
- // prettier-ignore
- const func = () => this.onDoubleClickHandler.script.run( {
- this: this.Document,
- scriptContext: this._props.scriptContext,
- documentView: this._props.DocumentView(),
- clientX, clientY, altKey, shiftKey, ctrlKey,
- value: undefined,
- }, console.log );
- UndoManager.RunInBatch(() => (func().result?.select === true ? this._props.select(false) : ''), 'on double click');
- } else if (!Doc.IsSystem(this.Document) && (defaultDblclick === undefined || defaultDblclick === 'default')) {
+ UndoManager.RunInBatch(() => this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title);
+ } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') {
UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap');
SelectionManager.DeselectAll();
- Doc.UnBrushDoc(this._props.Document);
+ Doc.UnBrushDoc(this.Document);
} else {
this._singleClickFunc?.();
}
@@ -461,33 +343,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
} else {
let clickFunc: undefined | (() => any);
if (!this.disableClickScriptFunc && this.onClickHandler?.script) {
- const { clientX, clientY, shiftKey, altKey, metaKey } = e;
- const func = () => {
- // replace default add doc func with this view's add doc func.
- // to allow override behaviors for how to display links to undisplayed documents.
- // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place
- // instead of in the global lightbox
+ clickFunc = undoable(() => {
+ // use this view's add doc func to override method for following links to undisplayed documents.
+ // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in this container of in the global lightbox
const oldFunc = DocumentViewInternal.addDocTabFunc;
DocumentViewInternal.addDocTabFunc = this._props.addDocTab;
- this.onClickHandler?.script.run(
- {
- this: this.Document,
- _readOnly_: false,
- scriptContext: this._props.scriptContext,
- documentView: this._props.DocumentView(),
- clientX,
- clientY,
- shiftKey,
- altKey,
- metaKey,
- },
- console.log
- ).result?.select === true
- ? this._props.select(false)
- : '';
+ this.onClickHandler?.script.run(scriptProps, console.log).result?.select && this._props.select(false);
DocumentViewInternal.addDocTabFunc = oldFunc;
- };
- clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.Document.title);
+ }, 'click ' + this.Document.title);
} else {
// onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
if ((this.layoutDoc.onDragStart || this._props.TemplateDataDocument) && !(e.ctrlKey || e.button > 0)) {
@@ -498,8 +361,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const sendToBack = e.altKey;
this._singleClickFunc =
// prettier-ignore
- clickFunc ?? (() => (sendToBack ? this._props.DocumentView()._props.bringToFront(this.Document, true) :
- this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ??
+ clickFunc ?? (() => (sendToBack ? documentView._props.bringToFront?.(this.Document, true) :
this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
@@ -517,12 +379,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onPointerDown = (e: React.PointerEvent): void => {
if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return;
- this._longPressSelector = setTimeout(() => {
- if (DocumentView.LongPress) {
- this._props.select(false);
- }
- }, 1000);
- if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView();
+ this._longPressSelector = setTimeout(() => DocumentView.LongPress && this._props.select(false), 1000);
+ if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView;
this._downX = e.clientX;
this._downY = e.clientY;
@@ -533,16 +391,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (
// prettier-ignore
(this._props.isDocumentActive?.() || this._props.isContentActive?.()) &&
- !this._props.onBrowseClick?.() &&
+ !this._props.onBrowseClickScript?.() &&
!this.Document.ignoreClick &&
e.button === 0 &&
!Doc.IsInMyOverlay(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.Document.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault();
+ // don't preventDefault. Goldenlayout, PDF text selection and RTF text selection all need it to go though
- // listen to move events if document content isn't active or document is draggable
+ // listen to move events when document content isn't active or document is always draggable
if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.layoutDoc._dragWhenActive, this._props.dragWhenActive))) {
document.addEventListener('pointermove', this.onPointerMove);
}
@@ -557,7 +414,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
}
};
@@ -579,62 +436,61 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (DocumentView.LongPress) e.preventDefault();
};
- @undoBatch
- toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => {
+ toggleFollowLink = undoable((zoom?: boolean, setTargetToggle?: boolean): void => {
const hadOnClick = this.Document.onClick;
this.noOnClick();
this.Document.onClick = hadOnClick ? undefined : FollowLinkScript();
this.Document.waitForDoubleClickToClick = hadOnClick ? undefined : 'never';
- };
- @undoBatch
- followLinkOnClick = () => {
+ }, 'toggle follow link');
+
+ followLinkOnClick = undoable(() => {
this.Document.ignoreClick = false;
this.Document.onClick = FollowLinkScript();
this.Document.followLinkToggle = false;
this.Document.followLinkZoom = false;
this.Document.followLinkLocation = undefined;
- };
- @undoBatch
- noOnClick = () => {
- this.Document.ignoreClick = false;
- this.Document.onClick = Doc.GetProto(this.Document).onClick = undefined;
- };
-
- @undoBatch deleteClicked = () => this._props.removeDocument?.(this._props.Document);
- @undoBatch setToggleDetail = () =>
- (this.Document.onClick = ScriptField.MakeScript(
- `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
- .replace('layout_', '')
- .replace(/^layout$/, 'detail')}")`,
- { documentView: 'any' }
- ));
+ }, 'follow link on click');
- @undoBatch
- drop = (e: Event, de: DragManager.DropEvent) => {
+ noOnClick = undoable(() => {
+ this.Document.ignoreClick = false;
+ this.Document.onClick = this.Document[DocData].onClick = undefined;
+ }, 'default on click');
+
+ deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
+ setToggleDetail = undoable(
+ (defaultLayout: string, scriptFieldKey: 'onClick') =>
+ (this.Document[scriptFieldKey] = ScriptField.MakeScript(
+ `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
+ .replace('layout_', '')
+ .replace(/^layout$/, 'detail')}", "${defaultLayout}")`,
+ { documentView: 'any' }
+ )),
+ 'set toggle detail'
+ );
+
+ drop = undoable((e: Event, de: DragManager.DropEvent) => {
if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false;
- if (this._props.Document === Doc.ActiveDashboard) {
+ if (this.Document === Doc.ActiveDashboard) {
e.stopPropagation();
e.preventDefault();
- alert(
- (e.target as any)?.closest?.('*.lm_content')
- ? "You can't perform this move most likely because you didn't drag the document's title bar to enable embedding in a different document."
- : 'Linking to document tabs not yet supported. Drop link on document content.'
- );
+ alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you didn't drag the document's title bar to enable embedding in a different document." : 'Linking to document tabs not yet supported.');
return true;
}
- const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
+ const annoData = de.complete.annoDragData;
+ const linkdrag = annoData ?? de.complete.linkDragData;
if (linkdrag) {
linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor();
if (linkdrag.linkSourceDoc && linkdrag.linkSourceDoc !== this.Document) {
- if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) {
- de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined);
+ if (annoData && !annoData.dropDocument) {
+ annoData.dropDocument = annoData.dropDocCreator(undefined);
}
- if (de.complete.annoDragData || this.Document !== linkdrag.linkSourceDoc.embedContainer) {
- const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.Document;
- de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
- if (de.complete.linkDocument) {
- de.complete.linkDocument.layout_isSvg = true;
- this._props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument);
+ if (annoData || this.Document !== linkdrag.linkSourceDoc.embedContainer) {
+ const dropDoc = annoData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.Document;
+ const linkDoc = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
+ if (linkDoc) {
+ de.complete.linkDocument = linkDoc;
+ linkDoc.layout_isSvg = true;
+ DocumentManager.LinkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc);
}
}
e.stopPropagation();
@@ -642,27 +498,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
}
return false;
- };
-
- @undoBatch
- makeIntoPortal = () => {
- const portalLink = this.allLinks.find(d => d.link_anchor_1 === this._props.Document && d.link_relationship === 'portal to:portal from');
- if (!portalLink) {
- DocUtils.MakeLink(
- this._props.Document,
- Docs.Create.FreeformDocument([], {
- _width: NumCast(this.layoutDoc._width) + 10,
- _height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10),
- _isLightbox: true,
- _layout_fitWidth: true,
- title: StrCast(this._props.Document.title) + ' [Portal]',
- }),
- { link_relationship: 'portal to:portal from' }
- );
- }
- this.Document.followLinkLocation = OpenWhere.lightbox;
- this.Document.onClick = FollowLinkScript();
- };
+ }, 'drop doc');
importDocument = () => {
const input = document.createElement('input');
@@ -686,7 +522,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && this.layoutDoc._layout_hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
- //!this._props.isSelected(true) && SelectionManager.SelectView(this._props.DocumentView(), false);
+ //!this._props.isSelected(true) && SelectionManager.SelectView(this.DocumentView(), false);
}
// the touch onContextMenu is button 0, the pointer onContextMenu is button 2
if (e) {
@@ -708,7 +544,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (e && !(e.nativeEvent as any).dash) {
const onDisplay = () => {
- if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this._props.isSelected() && 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.
+ if (this.Document.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && this._props.select(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(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY));
};
if (navigator.userAgent.includes('Macintosh')) {
@@ -719,14 +555,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return;
}
- const customScripts = Cast(this._props.Document.contextMenuScripts, listSpec(ScriptField), []);
+ const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), []);
StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
);
this._props.contextMenuItems?.().forEach(item => item.label && cm.addItem({ description: item.label, event: () => item.script.script.run({ this: this.Document, scriptContext: this._props.scriptContext }), icon: item.icon as IconProp }));
- if (!this._props.Document.isFolder) {
- const templateDoc = Cast(this._props.Document[StrCast(this._props.Document.layout_fieldKey)], Doc, null);
+ if (!this.Document.isFolder) {
+ const templateDoc = Cast(this.Document[StrCast(this.Document.layout_fieldKey)], Doc, null);
const appearance = cm.findByDescription('Appearance...');
const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : [];
@@ -737,40 +573,34 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
!Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
- if (!Doc.IsSystem(this.Document) && this.Document.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._type_collection as any)) {
+ if (this._props.bringToFront) {
+ const zorders = cm.findByDescription('ZOrder...');
+ const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
+ zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' });
+ zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' });
+ zorderItems.push({
+ description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
+ event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
+ icon: 'hand-point-up',
+ });
+ !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
+ }
+
+ if (!Doc.IsSystem(this.Document) && !this.Document.hideClickBehaviors && !this._props.hideClickBehaviors) {
const existingOnClick = cm.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
- if (this._props.bringToFront !== emptyFunction) {
- const zorders = cm.findByDescription('ZOrder...');
- const zorderItems: ContextMenuProps[] = zorders && 'subitems' in zorders ? zorders.subitems : [];
- zorderItems.push({ description: 'Bring to Front', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront(dv.Document, false)), icon: 'arrow-up' });
- zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront(dv.Document, true)), icon: 'arrow-down' });
- zorderItems.push({
- description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
- event: undoBatch(action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged))),
- icon: 'hand-point-up',
- });
- !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
- }
-
- onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
+ onClicks.push({ description: 'Enter Portal', event: undoable(e => DocUtils.makeIntoPortal(this.Document, this.layoutDoc, this._allLinks), 'make into portal'), icon: 'window-restore' });
!Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' });
- if (!this._props.treeViewDoc) {
- 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.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
- !Doc.noviceMode && 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...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
- } else if (LinkManager.Links(this.Document).length) {
- onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
- onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
- !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
- }
+ if (!this.Document.annotationOn) {
+ onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
+ !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
+ cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+ } else if (LinkManager.Links(this.Document).length) {
+ onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' });
+ onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
}
}
@@ -786,15 +616,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const moreItems = more && 'subitems' in more ? more.subitems : [];
if (!Doc.IsSystem(this.Document)) {
if (!Doc.noviceMode) {
- moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this._props.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
+ moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), 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' });
+ if (Cast(Doc.GetProto(this.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: 'Export to Google Photos Album', event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.Document }).then(console.log), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Tag Child Images via Google Photos', event: () => GooglePhotos.Query.TagChildImages(this.Document), icon: 'caret-square-right' });
+ moreItems.push({ description: 'Write Back Link to Album', event: () => GooglePhotos.Transactions.AddTextEnrichment(this.Document), icon: 'caret-square-right' });
}
- moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this._props.Document)), icon: 'fingerprint' });
+ moreItems.push({ description: 'Copy ID', event: () => Utils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
}
}
@@ -802,14 +632,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
const constantItems: ContextMenuProps[] = [];
if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking) {
- constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this._props.Document) });
- (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._props.DocumentView()), icon: 'users' });
- if (this._props.removeDocument && Doc.ActiveDashboard !== this._props.Document) {
+ constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.Document) });
+ (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' });
+ if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) {
// need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
}
}
- constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this._props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
+ constantItems.push({ description: 'Show Metadata', event: () => this._props.addDocTab(this.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' });
cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
const help = cm.findByDescription('Help...');
@@ -820,7 +650,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
let documentationDescription: string | undefined = undefined;
let documentationLink: string | undefined = undefined;
- switch (this._props.Document.type) {
+ switch (this.Document.type) {
case DocumentType.COL:
documentationDescription = 'See collection documentation';
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/views/';
@@ -854,8 +684,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
documentationLink = 'https://brown-dash.github.io/Dash-Documentation/documents/dataViz/';
break;
}
- // Add link to help documentation
- if (!this._props.treeViewDoc && documentationDescription && documentationLink) {
+ // Add link to help documentation (unless the doc contents have been overriden in which case the documentation isn't relevant)
+ if (!this.docContents && documentationDescription && documentationLink) {
helpItems.push({
description: documentationDescription,
event: () => window.open(documentationLink, '_blank'),
@@ -869,69 +699,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, undefined);
};
- @computed get _rootSelected() {
- return this._props.isSelected() || BoolCast(this._props.TemplateDataDocument && this._props.rootSelected?.());
- }
rootSelected = () => this._rootSelected;
panelHeight = () => this._props.PanelHeight() - this.headerMargin;
- screenToLocalContent = () => this.ScreenToLocalBoxXf().translate(0, -this.headerMargin);
- onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler);
+ screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+ onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
- setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view));
- @observable _isContentActive: boolean | undefined = undefined;
-
+ setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
isContentActive = (): boolean | undefined => this._isContentActive;
childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
- /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive
- @computed get _contentPointerEvents() {
- TraceMobx();
- return this._props.contentPointerEvents ??
- ((!this.disableClickScriptFunc && //
- this.onClickHandler &&
- !this._props.onBrowseClick?.() &&
- this.isContentActive() !== true) ||
- this.isContentActive() === false)
- ? 'none'
- : this.pointerEvents;
- }
contentPointerEvents = () => this._contentPointerEvents;
- @computed get contents() {
- TraceMobx();
- const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
- const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
- return (
- <div
- className="documentView-contentsView"
- style={{
- pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
- height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
- }}>
- <DocumentContentsView
- key={1}
- {...this._props}
- fieldKey=""
- pointerEvents={this.contentPointerEvents}
- docViewPath={this._props.viewPath}
- setContentView={this.setContentView}
- childFilters={this.childFilters}
- PanelHeight={this.panelHeight}
- setHeight={this.setHeight}
- isContentActive={this.isContentActive}
- ScreenToLocalTransform={this.screenToLocalContent}
- rootSelected={this.rootSelected}
- onClick={this.onClickFunc}
- setTitleFocus={this.setTitleFocus}
- layout_fieldKey={this.finalLayoutKey}
- />
- {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
- </div>
- );
- }
anchorPanelWidth = () => this._props.PanelWidth() || 1;
anchorPanelHeight = () => this._props.PanelHeight() || 1;
- anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+ anchorStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string): any => {
// prettier-ignore
switch (property.split(':')[0]) {
case StyleProp.ShowTitle: return '';
@@ -944,39 +725,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
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 'layout_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.Document).filter(
- link =>
- (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) ||
- (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) ||
- ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.Document)) ||
- ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.Document))
- );
- }
- @computed get allLinks() {
- TraceMobx();
- return LinkManager.Instance.getAllRelatedLinks(this.Document);
- }
- hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false));
+
+ removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
@computed get allLinkEndpoints() {
// the small blue dots that mark the endpoints of links
- TraceMobx();
if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
- const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines);
- return filtered.map(link => (
+ return this.filteredLinks.map(link => (
<div className="documentView-anchorCont" key={link[Id]}>
<DocumentView
{...this._props}
isContentActive={returnFalse}
Document={link}
- docViewPath={this._props.viewPath}
+ containerViewPath={this._props.docViewPath}
PanelWidth={this.anchorPanelWidth}
PanelHeight={this.anchorPanelHeight}
dontRegisterView={false}
@@ -984,7 +744,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
hideCaptions={true}
hideLinkAnchors={true}
layout_fitWidth={returnTrue}
- removeDocument={this.hideLink(link)}
+ removeDocument={this.removeLinkByHiding(link)}
styleProvider={this.anchorStyleProvider}
LayoutTemplate={undefined}
LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.Document)}`)}
@@ -993,152 +753,78 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
));
}
- static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
- let gumStream: any;
- let recorder: any;
- navigator.mediaDevices
- .getUserMedia({
- audio: true,
- })
- .then(function (stream) {
- let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
- if (audioTextAnnos) audioTextAnnos.push('');
- else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
- DictationManager.Controls.listen({
- interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
- continuous: { indefinite: false },
- }).then(results => {
- if (results && [DictationManager.Controls.Infringed].includes(results)) {
- DictationManager.Controls.stop();
- }
- onEnd?.();
- });
-
- gumStream = stream;
- recorder = new MediaRecorder(stream);
- recorder.ondataavailable = async (e: any) => {
- const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
- if (!(result instanceof Error)) {
- const audioField = new AudioField(result.accessPaths.agnostic.client);
- const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
- if (audioAnnos === undefined) {
- dataDoc[field + '_audioAnnotations'] = new List([audioField]);
- } else {
- audioAnnos.push(audioField);
- }
- }
- };
- //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
- recorder.start();
- const stopFunc = () => {
- recorder.stop();
- DictationManager.Controls.stop(false);
- runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
- gumStream.getAudioTracks()[0].stop();
- };
- if (onRecording) onRecording(stopFunc);
- else setTimeout(stopFunc, 5000);
- });
- }
- playAnnotation = () => {
- const self = this;
- const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
- const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
- const anno = audioAnnos?.lastElement();
- if (anno instanceof AudioField) {
- switch (audioAnnoState) {
- case 'stopped':
- this.dataDoc[AudioPlay] = new Howl({
- src: [anno.url.href],
- format: ['mp3'],
- autoplay: true,
- loop: false,
- volume: 0.5,
- onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
- });
- this.dataDoc.audioAnnoState = 'playing';
- break;
- case 'playing':
- this.dataDoc[AudioPlay]?.stop();
- this.dataDoc.audioAnnoState = 'stopped';
- break;
- }
- }
- };
-
- captionStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
- @observable _changingTitleField = false;
- @observable _dropDownInnerWidth = 0;
- fieldsDropdown = (inputOptions: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
- const filteredOptions = new Set(inputOptions);
- const scaling = this.titleHeight / 30; /* height of Dropdown */
- Object.entries(DocOptions)
- .filter(opts => opts[1].filterable)
- .forEach((pair: [string, FInfo]) => filteredOptions.add(pair[0]));
- filteredOptions.add(StrCast(this.layoutDoc.layout_showTitle));
- const options = Array.from(filteredOptions)
- .filter(f => f)
- .map(facet => ({ val: facet, text: facet }));
- return (
- <div style={{ width: dropdownWidth }}>
- <div
- ref={action((r: any) => r && (this._dropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
- onPointerDown={action(e => (this._changingTitleField = true))}
- style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${scaling})` }}>
- <Dropdown
- activeChanged={action(isOpen => !isOpen && (this._changingTitleField = false))}
- selectedVal={placeholder}
- setSelectedVal={onChange}
- color={SettingsManager.userColor}
- background={SettingsManager.userVariantColor}
- type={Type.TERT}
- closeOnSelect={true}
- dropdownType={DropdownType.SELECT}
- items={options}
- width={100}
- fillWidth
- />
- </div>
- </div>
- );
- };
- @computed get innards() {
+ @computed get viewBoxContents() {
TraceMobx();
- const showTitle = this.layout_showTitle?.split(':')[0];
- const showTitleHover = this.layout_showTitle?.includes(':hover');
- const captionView = !this.layout_showCaption ? null : (
+ const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
+ const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
+ return (
<div
- className="documentView-captionWrapper"
+ className="documentView-contentsView"
style={{
- pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
- background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
- color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
+ pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
+ height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
- <FormattedTextBox
+ <DocumentContentsView
{...this._props}
- yPadding={10}
- xPadding={10}
- fieldKey={this.layout_showCaption}
- styleProvider={this.captionStyleProvider}
- dontRegisterView={true}
- noSidebar={true}
- dontScale={true}
- renderDepth={this._props.renderDepth}
+ layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
+ pointerEvents={this.contentPointerEvents}
+ setContentViewBox={this.setContentView}
+ childFilters={this.childFilters}
+ PanelHeight={this.panelHeight}
+ setHeight={this.setHeight}
isContentActive={this.isContentActive}
+ ScreenToLocalTransform={this.screenToLocalContent}
+ rootSelected={this.rootSelected}
+ onClickScript={this.onClickFunc}
+ setTitleFocus={this.setTitleFocus}
+ hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
+ />
+ {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
+ </div>
+ );
+ }
+
+ captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
+ fieldsDropdown = (placeholder: string) => {
+ return (
+ <div
+ ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
+ onPointerDown={action(e => (this._changingTitleField = true))}
+ style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
+ <FieldsDropdown
+ Document={this.Document}
+ placeholder={placeholder}
+ selectFunc={action((field: string | number) => {
+ if (this.layoutDoc.layout_showTitle) {
+ this.layoutDoc._layout_showTitle = field;
+ } else if (!this._props.layout_showTitle) {
+ Doc.UserDoc().layout_showTitle = field;
+ }
+ this._changingTitleField = false;
+ })}
+ menuClose={action(() => (this._changingTitleField = false))}
/>
</div>
);
+ };
+ /**
+ * displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
+ * setting layout_showTitle using the format: field1[;field2[...][:hover]]
+ * from the UI, this is done by clicking the title field and prefixin the format with '#'. eg., #field1[;field2;...][:hover]
+ **/
+ @computed get titleView() {
+ const showTitle = this.layout_showTitle?.split(':')[0];
+ const showTitleHover = this.layout_showTitle?.includes(':hover');
+
const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.Document;
const background = StrCast(
this.layoutDoc.layout_headingColor,
StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))
);
- const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._dropDownInnerWidth * this.titleHeight) / 30) : 0;
+ const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0;
const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', '');
- // displays a 'title' at the top of a document. The title contents default to the 'title' field, but can be changed to one or more fields by
- // setting layout_showTitle using the format: field1[;field2[...][:hover]]
- // from the UI, this is done by clicking the title field and prefixin the format with '#'. eg., #field1[;field2;...][:hover]
- const titleView = !showTitle ? null : (
+
+ return !showTitle ? null : (
<div
className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`}
key="title"
@@ -1151,22 +837,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
- {!dropdownWidth
- ? null
- : this.fieldsDropdown(
- [],
- dropdownWidth,
- StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
- action((field: string | number) => {
- if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = field;
- } else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = field;
- }
- this._changingTitleField = false;
- }),
- action(() => (this._changingTitleField = false))
- )}
+ {!dropdownWidth ? null : <div style={{ width: dropdownWidth }}>{this.fieldsDropdown(showTitle)}</div>}
<div
style={{
width: `calc(100% - ${dropdownWidth}px)`,
@@ -1176,10 +847,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}}>
<EditableView
ref={this._titleRef}
- contents={showTitle
- .split(';')
- .map(field => targetDoc[field.trim()]?.toString())
- .join(' \\ ')}
+ contents={
+ showTitle
+ .split(';')
+ .map(field => targetDoc[field.trim()]?.toString())
+ .join(' \\ ') || '-unset-'
+ }
display="block"
oneLine={true}
fontSize={(this.titleHeight / 15) * 10}
@@ -1200,19 +873,36 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>
</div>
);
- return this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
- this.contents
- ) : (
- <div className="documentView-styleWrapper">
- {titleView}
- {this.contents}
- {captionView}
+ }
+
+ @computed get captionView() {
+ return !this.layout_showCaption ? null : (
+ <div
+ className="documentView-captionWrapper"
+ style={{
+ pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
+ background: StrCast(this.layoutDoc._backgroundColor, 'rgba(0,0,0,0.2)'),
+ color: lightOrDark(StrCast(this.layoutDoc._backgroundColor, 'black')),
+ }}>
+ <FormattedTextBox
+ {...this._props}
+ yPadding={10}
+ xPadding={10}
+ fieldKey={this.layout_showCaption}
+ styleProvider={this.captionStyleProvider}
+ dontRegisterView={true}
+ noSidebar={true}
+ dontScale={true}
+ renderDepth={this._props.renderDepth}
+ isContentActive={this.isContentActive}
+ />
</div>
);
}
renderDoc = (style: object) => {
TraceMobx();
+ const showTitle = this.layout_showTitle?.split(':')[0];
return !DocCast(this.Document) || GetEffectiveAcl(this.dataDoc) === AclPrivate
? null
: this.docContents ?? (
@@ -1228,59 +918,31 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'),
fontSize: Cast(this.Document._text_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'}`,
+ transition: !this._animateScalingTo ? this._props.DataTransition?.() : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
}}>
- {this.innards}
+ {this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
+ this.viewBoxContents
+ ) : (
+ <div className="documentView-styleWrapper">
+ {this.titleView}
+ {this.viewBoxContents}
+ {this.captionView}
+ </div>
+ )}
{this.widgetDecorations ?? null}
</div>
);
};
- /**
- * returns an entrance animation effect function to wrap a JSX element
- * @param presEffectDoc presentation effects document that specifies the animation effect parameters
- * @returns a function that will wrap a JSX animation element wrapping any JSX element
- */
- public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
- const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
- const effectProps = {
- left: dir === PresEffectDirection.Left,
- right: dir === PresEffectDirection.Right,
- top: dir === PresEffectDirection.Top,
- bottom: dir === PresEffectDirection.Bottom,
- opposite: true,
- delay: 0,
- duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
- };
- //prettier-ignore
- switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
- default:
- case PresEffect.None: return renderDoc;
- case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
- case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>;
- case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>;
- case PresEffect.Rotate: return <Rotate {...effectProps}>{renderDoc}</Rotate>;
- case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
- case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
- case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
- }
- }
- @computed get highlighting() {
- return this._props.styleProvider?.(this.Document, this._props, StyleProp.Highlighting);
- }
- @computed get borderPath() {
- return this._props.styleProvider?.(this.Document, this._props, StyleProp.BorderPath);
- }
render() {
TraceMobx();
const highlighting = this.highlighting;
const borderPath = this.borderPath;
- const boxShadow =
- this._props.treeViewDoc || !highlighting
- ? this.boxShadow
- : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
- ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
- : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
+ const boxShadow = !highlighting
+ ? this.boxShadow
+ : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed'
+ ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}`
+ : this.boxShadow || (this.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined);
const renderDoc = this.renderDoc({
borderRadius: this.borderRounding,
outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px',
@@ -1298,10 +960,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onClick={this.onClick}
onPointerEnter={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
onPointerOver={e => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
- onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
+ onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
style={{
borderRadius: this.borderRounding,
- pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
+ pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
}}>
<>
{this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
@@ -1310,157 +972,125 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>
);
}
-}
-@observer
-export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
- public static ROOT_DIV = 'documentView-effectsWrapper';
-
- constructor(props: any) {
- super(props);
- makeObservable(this);
+ /**
+ * returns an entrance animation effect function to wrap a JSX element
+ * @param presEffectDoc presentation effects document that specifies the animation effect parameters
+ * @returns a function that will wrap a JSX animation element wrapping any JSX element
+ */
+ public static AnimationEffect(renderDoc: JSX.Element, presEffectDoc: Opt<Doc>, root: Doc) {
+ const dir = presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection;
+ const effectProps = {
+ left: dir === PresEffectDirection.Left,
+ right: dir === PresEffectDirection.Right,
+ top: dir === PresEffectDirection.Top,
+ bottom: dir === PresEffectDirection.Bottom,
+ opposite: true,
+ delay: 0,
+ duration: Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null)),
+ };
+ //prettier-ignore
+ switch (StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect))) {
+ default:
+ case PresEffect.None: return renderDoc;
+ case PresEffect.Zoom: return <Zoom {...effectProps}>{renderDoc}</Zoom>;
+ case PresEffect.Fade: return <Fade {...effectProps}>{renderDoc}</Fade>;
+ case PresEffect.Flip: return <Flip {...effectProps}>{renderDoc}</Flip>;
+ case PresEffect.Rotate: return <Rotate {...effectProps}>{renderDoc}</Rotate>;
+ case PresEffect.Bounce: return <Bounce {...effectProps}>{renderDoc}</Bounce>;
+ case PresEffect.Roll: return <Roll {...effectProps}>{renderDoc}</Roll>;
+ case PresEffect.Lightspeed: return <JackInTheBox {...effectProps}>{renderDoc}</JackInTheBox>;
+ }
}
+ public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+ let gumStream: any;
+ let recorder: any;
+ navigator.mediaDevices
+ .getUserMedia({
+ audio: true,
+ })
+ .then(function (stream) {
+ let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+ if (audioTextAnnos) audioTextAnnos.push('');
+ else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
+ DictationManager.Controls.listen({
+ interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+ continuous: { indefinite: false },
+ }).then(results => {
+ if (results && [DictationManager.Controls.Infringed].includes(results)) {
+ DictationManager.Controls.stop();
+ }
+ onEnd?.();
+ });
- @observable _selected = false;
- public get IsSelected() {
- return this._selected;
- }
- public set IsSelected(val) {
- runInAction(() => (this._selected = val));
- }
- @observable public static LongPress = false;
- @computed public static get exploreMode() {
- return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
+ gumStream = stream;
+ recorder = new MediaRecorder(stream);
+ recorder.ondataavailable = async (e: any) => {
+ const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
+ if (!(result instanceof Error)) {
+ const audioField = new AudioField(result.accessPaths.agnostic.client);
+ const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+ if (audioAnnos === undefined) {
+ dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+ } else {
+ audioAnnos.push(audioField);
+ }
+ }
+ };
+ //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
+ recorder.start();
+ const stopFunc = () => {
+ recorder.stop();
+ DictationManager.Controls.stop(false);
+ runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
+ gumStream.getAudioTracks()[0].stop();
+ };
+ if (onRecording) onRecording(stopFunc);
+ else setTimeout(stopFunc, 5000);
+ });
}
- @observable public docView: DocumentViewInternal | undefined | null = undefined;
- @observable public textHtmlOverlay: Opt<string> = undefined;
- @observable public textHtmlOverlayTime: Opt<number> = undefined;
- @observable private _isHovering = false;
+}
- public htmlOverlayEffect: Opt<Doc>;
- public get displayName() {
- return 'DocumentView(' + this._props.Document?.title + ')';
- } // this makes mobx trace() statements more descriptive
+@observer
+export class DocumentView extends DocComponent<DocumentViewProps>() {
+ public static ROOT_DIV = 'documentView-effectsWrapper';
+ public get displayName() { return 'DocumentView(' + this.Document?.title + ')'; } // prettier-ignore
public ContentRef = React.createRef<HTMLDivElement>();
- public ViewTimer: NodeJS.Timeout | undefined; // timer for res
- public AnimEffectTimer: NodeJS.Timeout | undefined; // timer for res
+ private _htmlOverlayEffect: Opt<Doc>;
private _disposers: { [name: string]: IReactionDisposer } = {};
- public clearViewTransition = () => {
- this.ViewTimer && clearTimeout(this.ViewTimer);
- this.layoutDoc._viewTransition = undefined;
- };
- public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource);
-
- public showContextMenu = (pageX: number, pageY: number) => this.docView?.onContextMenu(undefined, pageX, pageY);
+ private _viewTimer: NodeJS.Timeout | undefined;
+ private _animEffectTimer: NodeJS.Timeout | undefined;
+ public Guid = Utils.GenerateGuid(); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
- public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
- this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer);
- this.Document[Animation] = presEffect;
- this.AnimEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
- };
- public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
- this.layoutDoc._viewTransition = `${transProp} ${timeInMs}ms`;
- if (dataTrans) this.Document._dataTransition = `${transProp} ${timeInMs}ms`;
- this.ViewTimer && clearTimeout(this.ViewTimer);
- return (this.ViewTimer = setTimeout(() => {
- this.layoutDoc._viewTransition = undefined;
- this.Document._dataTransition = 'inherit';
- afterTrans?.();
- }, timeInMs + 10));
- };
- public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) {
- docs.forEach(doc => {
- doc._viewTransition = `${transProp} ${timeInMs}ms`;
- dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
- });
- return setTimeout(
- () =>
- docs.forEach(doc => {
- doc._viewTransition = undefined;
- dataTrans && (doc.dataTransition = 'inherit');
- afterTrans?.();
- }),
- timeInMs + 10
- );
+ @computed public static get exploreMode() {
+ return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
}
- // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
- public static showBackLinks(linkAnchor: Doc) {
- const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
- // prettier-ignore
- DocServer.GetRefField(docId).then(docx =>
- LightboxView.Instance.SetLightboxDoc(
- (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
- Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
- )
- );
+ constructor(props: DocumentViewProps) {
+ super(props);
+ makeObservable(this);
}
- get Document() {
- return this._props.Document;
- }
- get topMost() {
- return this._props.renderDepth === 0;
- }
- get dataDoc() {
- return this.docView?.dataDoc ?? this.Document;
- }
- get ContentDiv() {
- return this.docView?.ContentDiv;
- }
- get ComponentView() {
- return this.docView?._componentView;
- }
- get allLinks() {
- return (this.docView?.allLinks || []).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document);
- }
- get LayoutFieldKey() {
- return this.docView?.LayoutFieldKey || 'layout';
- }
- @computed get layout_fitWidth() {
- return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
- }
- @computed get anchorViewDoc() {
- return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
- }
- @computed get hideLinkButton() {
- return (
- this._props.hideLinkButton ||
- this._props.renderDepth === -1 || //
- (this.IsSelected && this._props.renderDepth) ||
- !this._isHovering ||
- (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
- SnappingManager.IsDragging ||
- SnappingManager.IsResizing
- );
- }
- hideLinkCount = () => (this.hideLinkButton ? true : false);
+ // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
+ // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
+ // so we need to first activate the div, then, after a render timeout, start the opacity transition.
+ @observable private _enableHtmlOverlayTransitions: boolean = false;
+ @observable private _docViewInternal: DocumentViewInternal | undefined | null = undefined;
+ @observable private _htmlOverlayText: Opt<string> = undefined;
+ @observable private _isHovering = false;
+ @observable private _selected = false;
+ @observable public static LongPress = false;
- @computed get linkCountView() {
- return <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
- }
- @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._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
- }
- @computed get nativeHeight() {
- return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
- }
- @computed get shouldNotScale() {
+ @computed private get shouldNotScale() {
return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any);
}
- @computed get effectiveNativeWidth() {
+ @computed private get effectiveNativeWidth() {
return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width);
}
- @computed get effectiveNativeHeight() {
+ @computed private get effectiveNativeHeight() {
return this.shouldNotScale ? 0 : this.nativeHeight || NumCast(this.layoutDoc.height);
}
- @computed get nativeScaling() {
+ @computed private get nativeScaling() {
if (this.shouldNotScale) return 1;
const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
@@ -1468,19 +1098,19 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
}
return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
}
- @computed get panelWidth() {
+ @computed private get panelWidth() {
return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
}
- @computed get panelHeight() {
+ @computed private get panelHeight() {
if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
return Math.min(this._props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling);
}
return this._props.PanelHeight();
}
- @computed get Xshift() {
+ @computed private get Xshift() {
return this.effectiveNativeWidth ? Math.max(0, (this._props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2) : 0;
}
- @computed get Yshift() {
+ @computed private get Yshift() {
return this.effectiveNativeWidth &&
this.effectiveNativeHeight &&
Math.abs(this.Xshift) < 0.001 &&
@@ -1488,45 +1118,105 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
? Math.max(0, (this._props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2)
: 0;
}
- @computed get centeringX() {
- return this._props.dontCenter?.includes('x') ? 0 : this.Xshift;
+ @computed private get hideLinkButton() {
+ return (
+ this._props.hideLinkButton ||
+ this._props.renderDepth === -1 || //
+ (this.IsSelected && this._props.renderDepth) ||
+ !this._isHovering ||
+ (!this.IsSelected && this.layoutDoc.layout_hideLinkButton) ||
+ SnappingManager.IsDragging ||
+ SnappingManager.IsResizing
+ );
+ }
+
+ componentDidMount() {
+ runInAction(() => this.Document[DocViews].add(this));
+ this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction);
+ !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
+ }
+
+ componentWillUnmount() {
+ this._viewTimer && clearTimeout(this._viewTimer);
+ runInAction(() => this.Document[DocViews].delete(this));
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+ !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
}
- @computed get centeringY() {
- return this._props.dontCenter?.includes('y') ? 0 : this.Yshift;
+
+ public set IsSelected(val) { runInAction(() => (this._selected = val)); } // prettier-ignore
+ public get IsSelected() { return this._selected; } // prettier-ignore
+ public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore
+ public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore
+ public get ComponentView() { return this._docViewInternal?._componentView; } // prettier-ignore
+ public get allLinks() { return this._docViewInternal?._allLinks ?? []; } // prettier-ignore
+
+ get LayoutFieldKey() {
+ return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString);
}
- @computed get CollectionFreeFormView() {
- return this.CollectionFreeFormDocumentView?.CollectionFreeFormView;
+ @computed get layout_fitWidth() {
+ return this._props.layout_fitWidth?.(this.layoutDoc) ?? this.layoutDoc?.layout_fitWidth;
}
- @computed get CollectionFreeFormDocumentView() {
- return this._props.CollectionFreeFormDocumentView?.();
+ @computed get anchorViewDoc() {
+ return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
}
- public toggleNativeDimensions = () => this.docView && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this._props.PanelWidth(), this._props.PanelHeight());
- public getBounds = () => {
- if (!this.docView?.ContentDiv || this._props.treeViewDoc || Doc.AreProtosEqual(this._props.Document, Doc.UserDoc())) {
+ @computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> {
+ if (!this.ContentDiv || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
return undefined;
}
- if (this.docView._componentView?.screenBounds?.()) {
- return this.docView._componentView.screenBounds();
+ if (this.ComponentView?.screenBounds?.()) {
+ return this.ComponentView.screenBounds();
}
- const xf = this.docView.ScreenToLocalBoxXf().scale(this.nativeScaling).inverse();
+ const xf = this.screenToContentsTransform().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 };
+ if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
+ const docuBox = this.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+ if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined };
}
- return { left, top, right, bottom };
+ // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox
+ const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.());
+ return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() };
+ }
+
+ @computed get nativeWidth() {
+ return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+ }
+ @computed get nativeHeight() {
+ return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth));
+ }
+ @computed public get centeringX() { return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; } // prettier-ignore
+ @computed public get centeringY() { return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; } // prettier-ignore
+
+ /**
+ * path of DocumentViews hat contains this DocumentView (does not includes this DocumentView thouhg)
+ */
+ public get containerViewPath() { return this._props.containerViewPath; } // prettier-ignore
+ public get CollectionFreeFormView() { return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; } // prettier-ignore
+ public get CollectionFreeFormDocumentView() { return this._props.CollectionFreeFormDocumentView?.(); } // prettier-ignore
+
+ public clearViewTransition = () => {
+ this._viewTimer && clearTimeout(this._viewTimer);
+ this.layoutDoc._viewTransition = undefined;
};
+ public noOnClick = () => this._docViewInternal?.noOnClick();
+ public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
+ public setToggleDetail = (defaultLayout = '', scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(defaultLayout, scriptFieldKey);
+ public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
+ public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
+ public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
+ public showContextMenu = (pageX: number, pageY: number) => this._docViewInternal?.onContextMenu(undefined, pageX, pageY);
+
+ public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.NativeDimScaling() ?? 1, this._props.PanelWidth(), this._props.PanelHeight());
public iconify(finished?: () => void, animateTime?: number) {
this.ComponentView?.updateIcon?.();
- const animTime = this.docView?._animateScaleTime;
- runInAction(() => this.docView && animateTime !== undefined && (this.docView._animateScaleTime = animateTime));
+ const animTime = this._docViewInternal?.animateScaleTime();
+ runInAction(() => this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime));
const finalFinished = action(() => {
finished?.();
- this.docView && (this.docView._animateScaleTime = animTime);
+ this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime);
});
const layout_fieldKey = Cast(this.Document.layout_fieldKey, 'string', null);
if (layout_fieldKey !== 'layout_icon') {
@@ -1536,17 +1226,62 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null);
this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished);
this.Document.deiconifyLayout = undefined;
- this._props.bringToFront(this.Document);
+ this._props.bringToFront?.(this.Document);
}
}
- @undoBatch
- setCustomView = (custom: boolean, layout: string): void => {
- Doc.setNativeView(this._props.Document);
- custom && DocUtils.makeCustomViewClicked(this._props.Document, Docs.Create.StackingDocument, layout, undefined);
+
+ public playAnnotation = () => {
+ const self = this;
+ const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
+ const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
+ const anno = audioAnnos?.lastElement();
+ if (anno instanceof AudioField) {
+ switch (audioAnnoState) {
+ case 'stopped':
+ this.dataDoc[AudioPlay] = new Howl({
+ src: [anno.url.href],
+ format: ['mp3'],
+ autoplay: true,
+ loop: false,
+ volume: 0.5,
+ onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
+ });
+ this.dataDoc.audioAnnoState = 'playing';
+ break;
+ case 'playing':
+ this.dataDoc[AudioPlay]?.stop();
+ this.dataDoc.audioAnnoState = 'stopped';
+ break;
+ }
+ }
+ };
+
+ public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
+ this._htmlOverlayText = text;
+ this._htmlOverlayEffect = effect;
+ });
+ public setAnimateScaling = action((scale: number, time?: number) => {
+ if (this._docViewInternal) {
+ this._docViewInternal._animateScalingTo = scale;
+ this._docViewInternal._animateScaleTime = time;
+ }
+ });
+ public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => {
+ this._animEffectTimer && clearTimeout(this._animEffectTimer);
+ this.Document[Animation] = presEffect;
+ this._animEffectTimer = setTimeout(() => (this.Document[Animation] = undefined), timeInMs);
+ };
+ public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
+ this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
};
- switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
- runInAction(() => this.docView && (this.docView._animateScalingTo = 0.1)); // shrink doc
+ public setCustomView = undoable((custom: boolean, layout: string): void => {
+ Doc.setNativeView(this.Document);
+ custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
+ }, 'set custom view');
+
+ public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => {
+ runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc
setTimeout(
action(() => {
if (useExistingLayout && custom && this.Document['layout_' + view]) {
@@ -1554,22 +1289,25 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
} else {
this.setCustomView(custom, view);
}
- this.docView && (this.docView._animateScalingTo = 1); // expand it
+ this._docViewInternal && (this._docViewInternal._animateScalingTo = 1); // expand it
setTimeout(
action(() => {
- this.docView && (this.docView._animateScalingTo = 0);
+ this._docViewInternal && (this._docViewInternal._animateScalingTo = 0);
finished?.();
}),
- this.docView ? Math.max(0, this.docView.animateScaleTime - 10) : 0
+ Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
);
}),
- this.docView ? Math.max(0, this.docView?.animateScaleTime - 10) : 0
+ Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10)
);
};
+ /**
+ * @returns a hierarchy path through the nested DocumentViews that display this view. The last element of the path is this view.
+ */
+ public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
layout_fitWidthFunc = (doc: Doc) => BoolCast(this.layout_fitWidth);
screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
- docViewPathFunc = () => this.docViewPath;
isSelected = () => this.IsSelected;
select = (extendSelection: boolean, focusSelection?: boolean) => {
if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this);
@@ -1584,12 +1322,14 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
}
}
};
+ DataTransition = () => this._props.DataTransition?.() || StrCast(this.Document.dataTransition);
ShouldNotScale = () => this.shouldNotScale;
NativeWidth = () => this.effectiveNativeWidth;
NativeHeight = () => this.effectiveNativeHeight;
PanelWidth = () => this.panelWidth;
PanelHeight = () => this.panelHeight;
NativeDimScaling = () => this.nativeScaling;
+ hideLinkCount = () => (this.hideLinkButton ? true : false);
selfView = () => this;
/**
* @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
@@ -1604,57 +1344,30 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
.translate(-this.centeringX, -this.centeringY)
.scale(1 / this.nativeScaling);
- componentDidMount() {
- runInAction(() => this.Document[DocViews].add(this));
- this._disposers.updateContentsScript = reaction(() => ScriptCast(this.Document.updateContentsScript)?.script?.run({ this: this.Document }).result, emptyFunction);
- this._disposers.height = reaction(
- // increase max auto height if document has been resized to be greater than current max
- () => NumCast(this.layoutDoc._height),
- action(height => {
- const docMax = NumCast(this.layoutDoc.layout_maxAutoHeight);
- if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height;
- })
- );
- !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this);
- }
-
- componentWillUnmount() {
- this.Document[DocViews].delete(this);
- Object.values(this._disposers).forEach(disposer => disposer?.());
- !BoolCast(this._props.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
- }
- // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed.
- // unfortunately, CSS can't transition animate any properties for something that is display 'none'.
- // so we need to first activate the div, then, after a render timeout, start the opacity transition.
- @observable enableHtmlOverlayTransitions: boolean = false;
- @computed get htmlOverlay() {
- const effect = StrCast(this.htmlOverlayEffect?.presentation_effect, StrCast(this.htmlOverlayEffect?.followLinkAnimEffect));
+ htmlOverlay = () => {
+ const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
return (
<div
className="documentView-htmlOverlay"
ref={r => {
const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition
- if (r && val !== this.enableHtmlOverlayTransitions) {
- setTimeout(action(() => (this.enableHtmlOverlayTransitions = val)));
+ if (r && val !== this._enableHtmlOverlayTransitions) {
+ setTimeout(action(() => (this._enableHtmlOverlayTransitions = val)));
}
}}
- style={{ display: !this.textHtmlOverlay ? 'none' : undefined }}>
- <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this.enableHtmlOverlayTransitions ? 0.9 : 0 }}>
+ style={{ display: !this._htmlOverlayText ? 'none' : undefined }}>
+ <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}>
{DocumentViewInternal.AnimationEffect(
<div className="webBox-textHighlight">
- <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} />
+ <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this._htmlOverlayText)} />
</div>,
- { ...(this.htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
+ { ...(this._htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc,
this.Document
)}
</div>
</div>
);
- }
-
- @computed get infoUI() {
- return this.ComponentView?.infoUI?.();
- }
+ };
render() {
TraceMobx();
@@ -1662,21 +1375,22 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
- <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
- {!this._props.Document || !this._props.PanelWidth() ? null : (
+ <div id={this.Guid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
+ {!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
ref={this.ContentRef}
style={{
- transition: 'inherit', // this._props.dataTransition,
transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`,
height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`),
}}>
<DocumentViewInternal
{...this._props}
+ fieldKey={this.LayoutFieldKey}
+ DataTransition={this.DataTransition}
DocumentView={this.selfView}
- viewPath={this.docViewPathFunc}
+ docViewPath={this.docViewPath}
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
NativeWidth={this.NativeWidth}
@@ -1687,17 +1401,46 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
layout_fitWidth={this.layout_fitWidthFunc}
ScreenToLocalTransform={this.screenToContentsTransform}
focus={this._props.focus || emptyFunction}
- ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))}
+ ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))}
/>
- {this.htmlOverlay}
- {this.infoUI}
+ {this.htmlOverlay()}
+ {this.ComponentView?.infoUI?.()}
</div>
)}
-
- {this.linkCountView}
+ {/* display link count button */}
+ <DocumentLinksButton hideCount={this.hideLinkCount} View={this} scaling={this.screenToLocalScale} OnHover={true} Bottom={this.topMost} ShowCount={true} />
</div>
);
}
+
+ public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, afterTrans?: () => void, dataTrans = false) {
+ docs.forEach(doc => {
+ doc._viewTransition = `${transProp} ${timeInMs}ms`;
+ dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
+ });
+ timer && clearTimeout(timer);
+ return setTimeout(
+ () =>
+ docs.forEach(doc => {
+ doc._viewTransition = undefined;
+ dataTrans && (doc.dataTransition = 'inherit');
+ afterTrans?.();
+ }),
+ timeInMs + 10
+ );
+ }
+
+ // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
+ public static showBackLinks(linkAnchor: Doc) {
+ const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish';
+ // prettier-ignore
+ DocServer.GetRefField(docId).then(docx =>
+ LightboxView.Instance.SetLightboxDoc(
+ (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection
+ Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId)
+ )
+ );
+ }
}
ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) {
@@ -1709,13 +1452,13 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView
LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
});
-ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
- if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout');
+ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string, defaultLayout = '') {
+ if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
});
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
- const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
+ const collectedLinks = DocListCast(linkCollection[DocData].data);
let wid = NumCast(linkSource._width);
let embedding: Doc | undefined;
const links = LinkManager.Links(linkSource);
@@ -1734,3 +1477,29 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
return links;
});
+ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
+ const tag = StrCast(collection.title).split('-->')[1];
+ const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
+ const collectionDocs = DocListCast(collection[DocData].data).concat(collection);
+ let wid = 100;
+ let created = false;
+ const matchedDocs = matchedTags
+ .filter(tagDoc => !Doc.AreProtosEqual(collection, tagDoc))
+ .reduce((aset, tagDoc) => {
+ let embedding = Array.from(aset).find(doc => Doc.AreProtosEqual(tagDoc, doc)) ?? collectionDocs.find(doc => Doc.AreProtosEqual(tagDoc, doc));
+ if (!embedding) {
+ embedding = Doc.MakeEmbedding(tagDoc);
+ embedding.x = wid;
+ embedding.y = 0;
+ embedding._lockedPosition = false;
+ wid += NumCast(tagDoc._width);
+ created = true;
+ }
+ Doc.SetContainer(embedding, collection);
+ aset.add(embedding);
+ return aset;
+ }, new Set<Doc>());
+
+ created && (collection[DocData].data = new List<Doc>(Array.from(matchedDocs)));
+ return true;
+});
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index ff92c701f..2e03a766a 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -20,14 +20,14 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static SelectOnLoad: string = '';
_ref: React.RefObject<EquationEditor> = React.createRef();
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
componentDidMount() {
- this._props.setContentView?.(this);
- if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+ this._props.setContentViewBox?.(this);
+ if (EquationBox.SelectOnLoad === this.Document[Id] && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
this._props.select(false);
this._ref.current!.mathField.focus();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 008f10f26..8a49b4757 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,43 +1,119 @@
-import { computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DateField } from '../../../fields/DateField';
-import { Doc, Field, FieldResult, Opt } from '../../../fields/Doc';
+import { Doc, Field, Opt } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ScriptField } from '../../../fields/ScriptField';
import { WebField } from '../../../fields/URLField';
-import { DocumentViewSharedProps } from './DocumentView';
+import { dropActionType } from '../../util/DragManager';
+import { Transform } from '../../util/Transform';
+import { ViewBoxInterface } from '../DocComponent';
+import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { PinProps } from './trails';
+export interface FocusViewOptions {
+ willPan?: boolean; // determines whether to pan to target document
+ willZoomCentered?: boolean; // determines whether to zoom in on target document. if zoomScale is 0, this just centers the document
+ zoomScale?: number; // percent of containing frame to zoom into document
+ zoomTime?: number;
+ didMove?: boolean; // whether a document was changed during the showDocument process
+ 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)
+ preview?: boolean; // whether changes should be previewed by the componentView or written to the document
+ effect?: Doc; // animation effect for focus
+ noSelect?: boolean; // whether target should be selected after focusing
+ playAudio?: boolean; // whether to play audio annotation on focus
+ playMedia?: boolean; // whether to play start target videos
+ openLocation?: OpenWhere; // where to open a missing document
+ zoomTextSelections?: boolean; // whether to display a zoomed overlay of anchor text selections
+ toggleTarget?: boolean; // whether to toggle target on and off
+ anchorDoc?: Doc; // doc containing anchor info to apply at end of focus to target doc
+ easeFunc?: 'linear' | 'ease'; // transition method for scrolling
+}
+export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt<number>;
+export type StyleProviderFuncType = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => any;
//
// these properties get assigned through the render() method of the DocumentView when it creates this node.
// However, that only happens because the properties are "defined" in the markup for the field view.
// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc.
//
-export interface FieldViewProps extends DocumentViewSharedProps {
- // FieldView specific props that are not part of DocumentView props
- fieldKey: string;
+export interface FieldViewSharedProps {
+ Document: Doc;
+ TemplateDataDocument?: Doc;
+ LayoutTemplateString?: string;
+ LayoutTemplate?: () => Opt<Doc>;
+ renderDepth: number;
+ scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+ xPadding?: number;
+ yPadding?: number;
+ dontRegisterView?: boolean;
+ dropAction?: dropActionType;
+ dragAction?: dropActionType;
+ forceAutoHeight?: boolean;
+ ignoreAutoHeight?: boolean;
+ disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over.
+ hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors
+ CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
+ containerViewPath?: () => DocumentView[];
+ fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document
+ isGroupActive?: () => string | undefined; // is this document part of a group that is active
+ setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
+ isContentActive: () => boolean | undefined; // whether document contents should handle pointer events
+ childFilters: () => string[];
+ childFiltersByRanges: () => string[];
+ styleProvider: Opt<StyleProviderFuncType>;
+ setTitleFocus?: () => void;
+ focus: FocusFuncType;
+ onClickScript?: () => ScriptField;
+ onDoubleClickScript?: () => ScriptField;
+ onPointerDownScript?: () => ScriptField;
+ onPointerUpScript?: () => ScriptField;
+ onBrowseClickScript?: () => ScriptField | undefined;
+ onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
+ layout_fitWidth?: (doc: Doc) => boolean | undefined;
+ searchFilterDocs: () => Doc[];
+ layout_showTitle?: () => string;
+ whenChildContentsActiveChanged: (isActive: boolean) => void;
+ rootSelected?: () => boolean; // whether the root of a template has been selected
+ addDocTab: (doc: Doc, where: OpenWhere) => 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[], annotationKey?: string) => boolean;
+ removeDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean;
+ moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => boolean;
+ pinToPres: (document: Doc, pinProps: PinProps) => void;
+ ScreenToLocalTransform: () => Transform;
+ bringToFront?: (doc: Doc, sendToBack?: boolean) => void;
+ waitForDoubleClickToClick?: () => 'never' | 'always' | undefined;
+ defaultDoubleClick?: () => 'default' | 'ignore' | undefined;
+ pointerEvents?: () => Opt<string>;
+}
- select: (isCtrlPressed: boolean) => void;
- isContentActive: (outsideReaction?: boolean) => boolean | undefined;
- isDocumentActive?: () => boolean | undefined;
+/**
+ * FieldView specific props that are not shared with DocumentView props
+ * */
+export interface FieldViewProps extends FieldViewSharedProps {
+ DocumentView?: () => DocumentView;
+ fieldKey: string;
isSelected: () => boolean;
+ select: (ctrlPressed: boolean, shiftPress?: boolean) => void;
+ docViewPath: () => DocumentView[];
setHeight?: (height: number) => void;
- NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps
- onBrowseClick?: () => ScriptField | undefined;
- onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined;
- pointerEvents?: () => Opt<string>;
+ NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal
// properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React)
// See currentUserUtils headerTemplate for examples of creating text boxes from html which set some of these fields
// Also, see InkingStroke for examples of creating text boxes from render() methods which set some of these fields
backgroundColor?: string;
- treeViewDoc?: Doc;
color?: string;
height?: number;
width?: number;
+ dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
noSidebar?: boolean;
dontScale?: boolean;
- dontSelectOnLoad?: boolean; // suppress selecting (e.g.,. text box) when loaded (and mark as not being associated with scrollTop document field)
}
@observer
@@ -46,13 +122,8 @@ export class FieldView extends React.Component<FieldViewProps> {
return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={'data'} />"
}
- @computed
- get field(): FieldResult {
- const { Document, fieldKey: fieldKey } = this.props;
- return Document[fieldKey];
- }
render() {
- const field = this.field;
+ const field = this.props.Document[this.props.fieldKey];
// prettier-ignore
if (field instanceof Doc) return <p> <b>{field.title?.toString()}</b></p>;
if (field === undefined) return <p>{'<null>'}</p>;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index db2ffa756..2db285910 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -1,5 +1,15 @@
@import '../../global/globalCssVariables.module.scss';
+// bcz: something's messed up with the IconButton css. this mostly fixes the fit-all button, the color buttons, the undo +/- expander and the dropdown doc type list (eg 'text')
+.iconButton-container {
+ width: unset !important;
+ min-width: 30px !important;
+ height: unset !important;
+ min-height: 30px;
+ .color {
+ height: 3px !important;
+ }
+}
.menuButton {
height: 100%;
display: flex;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 5a8665aaf..91b6de80b 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
@@ -46,7 +46,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
return FieldView.LayoutString(FontIconBox, fieldKey);
}
- constructor(props: any) {
+ constructor(props: ButtonProps) {
super(props);
makeObservable(this);
}
@@ -73,7 +73,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
};
specificContextMenu = (): void => {
- if (!Doc.noviceMode) {
+ if (!Doc.noviceMode && Cast(this.layoutDoc.dragFactory, Doc, null)) {
const cm = ContextMenu.Instance;
cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' });
cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' });
@@ -314,6 +314,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const tooltip = StrCast(this.Document.toolTip);
const script = ScriptCast(this.Document.onClick);
+ const double = ScriptCast(this.Document.onDoubleClick);
const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
@@ -330,7 +331,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
//background={SettingsManager.userBackgroundColor}
icon={this.Icon(color)!}
label={this.label}
- onPointerDown={() => script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false })}
+ onPointerDown={e =>
+ setupMoveUpEvents(this, e, returnTrue, emptyFunction, (e, doubleTap) => {
+ !doubleTap && script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
+ doubleTap && double.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
+ })
+ }
/>
);
}
@@ -366,7 +372,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
);
}
- render() {
+ renderButton = () => {
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
const tooltip = StrCast(this.Document.toolTip);
const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, self: this.Document, _readOnly_: false });
@@ -381,7 +387,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
case ButtonType.ColorButton: return this.colorButton;
case ButtonType.MultiToggleButton: return this.multiToggleButton;
case ButtonType.ToggleButton: return this.toggleButton;
- case ButtonType.ClickButton:
+ case ButtonType.ClickButton:return <IconButton {...btnProps} size={Size.MEDIUM} color={color} />;
case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
case ButtonType.TextButton: return <Button {...btnProps} color={color}
background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
@@ -389,5 +395,13 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
background={SettingsManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />;
}
return this.defaultButton;
+ };
+
+ render() {
+ return (
+ <div style={{ margin: 'auto', width: '100%' }} onContextMenu={this.specificContextMenu}>
+ {this.renderButton()}
+ </div>
+ );
}
}
diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx
index c26579e66..2e7a2120e 100644
--- a/src/client/views/nodes/FunctionPlotBox.tsx
+++ b/src/client/views/nodes/FunctionPlotBox.tsx
@@ -31,7 +31,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>
}
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
reaction(
() => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange],
() => this.createGraph()
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index a5853499f..923aead64 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -21,17 +21,17 @@ import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from '../../views/ContextMenu';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
@observer
-export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ImageBox, fieldKey);
}
@@ -55,10 +55,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
private _marqueeref = React.createRef<MarqueeAnnotator>();
@observable _curSuffix = '';
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -158,7 +158,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@undoBatch
setNativeSize = action(() => {
- const scaling = (this._props.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
+ const scaling = (this.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
const nscale = NumCast(this._props.PanelWidth()) / scaling;
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
@@ -186,9 +186,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
crop = (region: Doc | undefined, addCrop?: boolean) => {
if (!region) return;
const cropping = Doc.MakeCopy(region, true);
- Doc.GetProto(region).lockedPosition = true;
- Doc.GetProto(region).title = 'region:' + this.Document.title;
- Doc.GetProto(region).followLinkToggle = true;
+ const regionData = region[DocData];
+ regionData.lockedPosition = true;
+ regionData.title = 'region:' + this.Document.title;
+ regionData.followLinkToggle = true;
this.addDocument(region);
const anchx = NumCast(cropping.x);
const anchy = NumCast(cropping.y);
@@ -201,7 +202,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
cropping.onClick = undefined;
- const croppingProto = Doc.GetProto(cropping);
+ const croppingProto = cropping[DocData];
croppingProto.annotationOn = undefined;
croppingProto.isDataDoc = true;
croppingProto.backgroundColor = undefined;
@@ -226,7 +227,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._props.addDocTab(cropping, OpenWhere.inParent);
}
DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
- this._props.bringToFront(cropping);
+ this._props.bringToFront?.(cropping);
return cropping;
};
@@ -368,7 +369,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -389,7 +390,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
this._marqueeref.current?.onTerminateSelection();
this._props.select(false);
};
- focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
+ focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));
_ffref = React.createRef<CollectionFreeFormView>();
savedAnnotations = () => this._savedAnnotations;
@@ -421,7 +422,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
<CollectionFreeFormView
ref={this._ffref}
{...this._props}
- setContentView={emptyFunction}
+ setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
renderDepth={this._props.renderDepth + 1}
@@ -444,7 +445,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{this.content}
</CollectionFreeFormView>
{this.annotationLayer}
- {!this._mainCont.current || !this._annotationLayer.current ? null : (
+ {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
Document={this.Document}
ref={this._marqueeref}
@@ -452,7 +453,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
annotationLayerScrollTop={0}
scaling={returnOne}
annotationLayerScaling={this._props.NativeDimScaling}
- docView={this._props.DocumentView!}
+ docView={this.DocumentView}
addDocument={this.addDocument}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 73fdc3a23..39a45693e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -44,7 +44,7 @@ export class KeyValueBox extends ObservableReactComponent<FieldViewProps> {
private _valInput = React.createRef<HTMLInputElement>();
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
isKeyValueBox = returnTrue;
able = returnAlways;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 7bc9d3f85..f9e8ce4f3 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -12,8 +12,7 @@ import { ContextMenu } from '../ContextMenu';
import { EditableView } from '../EditableView';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider } from '../StyleProvider';
-import { OpenWhere } from './DocumentView';
-import { FieldViewProps } from './FieldView';
+import { OpenWhere, returnEmptyDocViewList } from './DocumentView';
import { KeyValueBox } from './KeyValueBox';
import './KeyValueBox.scss';
import './KeyValuePair.scss';
@@ -61,33 +60,11 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
};
render() {
- const props: FieldViewProps = {
- Document: this._props.doc,
- childFilters: returnEmptyFilter,
- childFiltersByRanges: returnEmptyFilter,
- searchFilterDocs: returnEmptyDoclist,
- styleProvider: DefaultStyleProvider,
- docViewPath: returnEmptyDoclist,
- fieldKey: this._props.keyName,
- isSelected: returnFalse,
- setHeight: returnFalse,
- select: emptyFunction,
- bringToFront: emptyFunction,
- renderDepth: 1,
- isContentActive: returnFalse,
- whenChildContentsActiveChanged: emptyFunction,
- ScreenToLocalTransform: Transform.Identity,
- focus: emptyFunction,
- PanelWidth: this._props.PanelWidth,
- PanelHeight: this._props.PanelHeight,
- addDocTab: returnFalse,
- pinToPres: returnZero,
- };
// let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
let protoCount = 0;
- let doc: Doc | undefined = props.Document;
+ let doc = this._props.doc;
while (doc) {
- if (Object.keys(doc).includes(props.fieldKey)) {
+ if (Object.keys(doc).includes(this._props.keyName)) {
break;
}
protoCount++;
@@ -106,17 +83,17 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
style={hover}
className="keyValuePair-td-key-delete"
onClick={undoBatch(() => {
- if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
- delete props.Document[props.fieldKey];
- } else delete DocCast(props.Document.proto)?.[props.fieldKey];
+ if (Object.keys(this._props.doc).indexOf(this._props.keyName) !== -1) {
+ delete this._props.doc[this._props.keyName];
+ } else delete DocCast(this._props.doc.proto)?.[this._props.keyName];
})}>
X
</button>
<input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} />
- <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === props.fieldKey)?.[1].description ?? ''}>
- <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}>
+ <Tooltip title={Object.entries(new DocumentOptions()).find((pair: [string, FInfo]) => pair[0].replace(/^_/, '') === this._props.keyName)?.[1].description ?? ''}>
+ <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (this._props.keyName.match(/_/g)?.length || 0), color: keyStyle }}>
{'('.repeat(parenCount)}
- {props.fieldKey}
+ {this._props.keyName}
{')'.repeat(parenCount)}
</div>
</Tooltip>
@@ -124,7 +101,32 @@ export class KeyValuePair extends ObservableReactComponent<KeyValuePairProps> {
</td>
<td className="keyValuePair-td-value" style={{ width: `${100 - this._props.keyWidth}%` }} onContextMenu={this.onContextMenu}>
<div className="keyValuePair-td-value-container">
- <EditableView contents={undefined} fieldContents={props} GetValue={() => Field.toKeyValueString(props.Document, props.fieldKey)} SetValue={(value: string) => KeyValueBox.SetField(props.Document, props.fieldKey, value)} />
+ <EditableView
+ contents={undefined}
+ fieldContents={{
+ Document: this._props.doc,
+ childFilters: returnEmptyFilter,
+ childFiltersByRanges: returnEmptyFilter,
+ searchFilterDocs: returnEmptyDoclist,
+ styleProvider: DefaultStyleProvider,
+ docViewPath: returnEmptyDocViewList,
+ fieldKey: this._props.keyName,
+ isSelected: returnFalse,
+ setHeight: returnFalse,
+ select: emptyFunction,
+ renderDepth: 1,
+ isContentActive: returnFalse,
+ whenChildContentsActiveChanged: emptyFunction,
+ ScreenToLocalTransform: Transform.Identity,
+ focus: emptyFunction,
+ PanelWidth: this._props.PanelWidth,
+ PanelHeight: this._props.PanelHeight,
+ addDocTab: returnFalse,
+ pinToPres: returnZero,
+ }}
+ GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)}
+ SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)}
+ />
</div>
</td>
</tr>
diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss
index 42e158584..0b195713d 100644
--- a/src/client/views/nodes/LabelBox.scss
+++ b/src/client/views/nodes/LabelBox.scss
@@ -18,6 +18,9 @@
display: inline-block;
margin: auto;
text-overflow: ellipsis;
+ > span {
+ max-height: 100%; // make sure top of text is in view, otherwise it would center on middle of large text span
+ }
}
.labelBox-params {
@@ -29,4 +32,4 @@
width: 100%;
background: lightgray;
border: dimgray solid 1px;
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 934bce448..fd3074a88 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -1,7 +1,7 @@
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Field } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
@@ -14,13 +14,15 @@ import { StyleProp } from '../StyleProvider';
import { FieldView, FieldViewProps } from './FieldView';
import BigText from './LabelBigText';
import './LabelBox.scss';
+import { PinProps, PresBox } from './trails';
+import { Docs } from '../../documents/Documents';
-export interface LabelBoxProps {
+export interface LabelBoxProps extends FieldViewProps {
label?: string;
}
@observer
-export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProps>() {
+export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(LabelBox, fieldKey);
}
@@ -30,20 +32,20 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
private dropDisposer?: DragManager.DragDropDisposer;
private _timeout: any;
- constructor(props: any) {
+ constructor(props: LabelBoxProps) {
super(props);
makeObservable(this);
}
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
componentWillUnMount() {
this._timeout && clearTimeout(this._timeout);
}
@computed get Title() {
- return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : typeof this.dataDoc[this.fieldKey] === 'string' ? StrCast(this.dataDoc[this.fieldKey]) : StrCast(this.Document.title);
+ return this.dataDoc.title_custom ? StrCast(this.Document.title) : this._props.label ? this._props.label : Field.toString(this.dataDoc[this.fieldKey] as Field);
}
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -86,9 +88,22 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp
@observable _mouseOver = false;
@computed get hoverColor() {
- return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
+ return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
}
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+ if (!pinProps) return this.Document;
+ const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.Document.title), annotationOn: this.Document });
+
+ if (anchor) {
+ if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
+ // addAsAnnotation && this.addDocument(anchor);
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}) } }, this.Document);
+ return anchor;
+ }
+ return anchor;
+ };
+
fitTextToBox = (
r: any
):
diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx
index b86ba72a0..00e1f04c5 100644
--- a/src/client/views/nodes/LinkAnchorBox.tsx
+++ b/src/client/views/nodes/LinkAnchorBox.tsx
@@ -1,4 +1,5 @@
import { action, computed, makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
import * as React from 'react';
import { Utils, emptyFunction, setupMoveUpEvents } from '../../../Utils';
import { Doc } from '../../../fields/Doc';
@@ -13,6 +14,7 @@ import { FieldView, FieldViewProps } from './FieldView';
import './LinkAnchorBox.scss';
import { LinkInfo } from './LinkDocPreview';
const { default: { MEDIUM_GRAY }, } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
+@observer
export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(LinkAnchorBox, fieldKey);
@@ -23,24 +25,26 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
_isOpen = false;
_timeout: NodeJS.Timeout | undefined;
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
@computed get linkSource() {
- return this._props.docViewPath()[this._props.docViewPath().length - 2].Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
+ return this.DocumentView?.().containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource);
}
onPointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
- if (doubleTap) LinkFollower.FollowLink(this.Document, this.linkSource, false);
- else this._props.select(false);
- });
+ const linkSource = this.linkSource;
+ linkSource &&
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (e, doubleTap) => {
+ if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false);
+ else this._props.select(false);
+ });
};
onPointerMove = action((e: PointerEvent, down: number[], delta: number[]) => {
const cdiv = this._ref?.current?.parentElement;
@@ -86,7 +90,8 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() {
className={`linkAnchorBox-cont${small ? '-small' : ''}`}
onPointerEnter={e =>
LinkInfo.SetLinkInfo({
- docProps: this._props,
+ DocumentView: this.DocumentView,
+ styleProvider: this._props.styleProvider,
linkSrc: this.linkSource,
linkDoc: this.Document,
showHeader: true,
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 767f0291b..484fb301e 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -5,3 +5,28 @@
.linkBox-container {
width: 100%;
}
+
+.linkBox {
+ transition: inherit;
+ pointer-events: none;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ path {
+ transition: inherit;
+ fill: transparent;
+ }
+ svg {
+ transition: inherit;
+ overflow: visible;
+ }
+ text {
+ cursor: default;
+ text-anchor: middle;
+ font-size: 12;
+ stroke: black;
+ }
+ circle {
+ cursor: default;
+ }
+}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 7f1d41547..decdbb240 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,14 +1,17 @@
-import { Bezier } from 'bezier-js';
-import { computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import Xarrow from 'react-xarrows';
+import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
+import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
-import { Transform } from '../../util/Transform';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { LinkManager } from '../../util/LinkManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
+import { EditableView } from '../EditableView';
+import { LightboxView } from '../LightboxView';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
import { FieldView, FieldViewProps } from './FieldView';
@@ -19,154 +22,182 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string = 'link') {
return FieldView.LayoutString(LinkBox, fieldKey);
}
+ disposer: IReactionDisposer | undefined;
+ @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
+ @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
+ @computed get anchor1() { return this.anchor(1); } // prettier-ignore
+ @computed get anchor2() { return this.anchor(2); } // prettier-ignore
- onClickScriptDisable = returnAlways;
- @computed get anchor1() {
- const anchor1 = DocCast(this.dataDoc.link_anchor_1);
- const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
- return DocumentManager.Instance.getDocumentView(anchor_1, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
- }
- @computed get anchor2() {
- const anchor2 = DocCast(this.dataDoc.link_anchor_2);
- const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
- return DocumentManager.Instance.getDocumentView(anchor_2, this._props.docViewPath()[this._props.docViewPath().length - 2]); // this._props.docViewPath().lastElement());
- }
- screenBounds = () => {
- if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
- const a_invXf = this.anchor1.screenToViewTransform().inverse();
- const b_invXf = this.anchor2.screenToViewTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._height)) };
-
- const pts = [] as number[][];
- pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
- pts.push(Utils.getNearestPointInPerimeter(a_scrBds.tl[0], a_scrBds.tl[1], a_scrBds.br[0] - a_scrBds.tl[0], a_scrBds.br[1] - a_scrBds.tl[1], (b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2));
- pts.push(Utils.getNearestPointInPerimeter(b_scrBds.tl[0], b_scrBds.tl[1], b_scrBds.br[0] - b_scrBds.tl[0], b_scrBds.br[1] - b_scrBds.tl[1], (a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2));
- pts.push([(b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2]);
- const agg = aggregateBounds(
- pts.map(pt => ({ x: pt[0], y: pt[1] })),
- 0,
- 0
- );
- return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined };
- }
- return undefined;
+ anchor = (which: number) => {
+ const anch = DocCast(this.dataDoc['link_anchor_' + which]);
+ const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
+ return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
};
- disposer: IReactionDisposer | undefined;
+ componentWillUnmount() {
+ this.disposer?.();
+ }
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
this.disposer = reaction(
- () => {
- if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
- const a = (this.anchor1 ?? this.anchor2)!;
- const b = (this.anchor2 ?? this.anchor1)!;
-
- const parxf = this._props.docViewPath()[this._props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
- const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
- const a_invXf = a.screenToViewTransform().inverse();
- const b_invXf = b.screenToViewTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._height)) };
- const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
- const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
-
- const ppt1 = [(a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2];
- const pt1 = Utils.getNearestPointInPerimeter(a_bds.tl[0], a_bds.tl[1], a_bds.br[0] - a_bds.tl[0], a_bds.br[1] - a_bds.tl[1], (b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2);
- const pt2 = Utils.getNearestPointInPerimeter(b_bds.tl[0], b_bds.tl[1], b_bds.br[0] - b_bds.tl[0], b_bds.br[1] - b_bds.tl[1], (a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2);
- const ppt2 = [(b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2];
-
- const pts = [ppt1, pt1, pt2, ppt2].map(pt => [pt[0], pt[1]]);
- const [lx, rx, ty, by] = [Math.min(pt1[0], pt2[0]), Math.max(pt1[0], pt2[0]), Math.min(pt1[1], pt2[1]), Math.max(pt1[1], pt2[1])];
- return { pts, lx, rx, ty, by };
- }
- return undefined;
- },
- params => {
- this.renderProps = params;
- if (params) {
- if (
- Math.abs(params.lx - NumCast(this.layoutDoc.x)) > 1e-5 ||
- Math.abs(params.ty - NumCast(this.layoutDoc.y)) > 1e-5 ||
- Math.abs(params.rx - params.lx - NumCast(this.layoutDoc._width)) > 1e-5 ||
- Math.abs(params.by - params.ty - NumCast(this.layoutDoc._height)) > 1e-5
- ) {
- this.layoutDoc.x = params?.lx;
- this.layoutDoc.y = params?.ty;
- this.layoutDoc._width = params.rx - params?.lx;
- this.layoutDoc._height = params?.by - params?.ty;
- }
- }
+ () => ({ drag: SnappingManager.IsDragging }),
+ ({ drag }) => {
+ !LightboxView.Contains(this.DocumentView?.()) &&
+ setTimeout(
+ // need to wait for drag manager to set 'hidden' flag on dragged DOM elements
+ action(() => {
+ const a = this.anchor1,
+ b = this.anchor2;
+ let a1 = a && document.getElementById(a.Guid);
+ let a2 = b && document.getElementById(b.Guid);
+ // test whether the anchors themselves are hidden,...
+ if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
+ else {
+ // .. or whether and of their DOM parents are hidden
+ for (; a1 && !a1.hidden; a1 = a1.parentElement);
+ for (; a2 && !a2.hidden; a2 = a2.parentElement);
+ this._hide = a1 || a2 ? true : false;
+ }
+ })
+ );
},
{ fireImmediately: true }
);
}
- componentWillUnmount(): void {
- this.disposer?.();
- }
- @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined = undefined;
+
render() {
- if (this.renderProps) {
+ if (this._hide) return null;
+ const a = this.anchor1;
+ const b = this.anchor2;
+ this._forceAnimate;
+ const docView = this._props.docViewPath().lastElement();
+
+ if (a && b && !LightboxView.Contains(docView)) {
+ // text selection bounds are not directly observable, so we have to
+ // force an update when anything that could affect them changes (text edits causing reflow, scrolling)
+ a.Document[a.LayoutFieldKey];
+ b.Document[b.LayoutFieldKey];
+ a.Document.layout_scrollTop;
+ b.Document.layout_scrollTop;
+
+ const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
+ const bxf = b.screenToViewTransform();
+ const scale = docView?.screenToViewTransform().Scale ?? 1;
+ const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
+ const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
+
+ // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
+ // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+ // otherwise, we just use the computed nearest point on the document boundary to the target Document
+ const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
+ const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+
+ const aid = targetAhyperlink?.id || a.Document[Id];
+ const bid = targetBhyperlink?.id || b.Document[Id];
+ if (!document.getElementById(aid) || !document.getElementById(bid)) {
+ setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+ return null;
+ }
+
+ if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
+ const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
+ const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
+ const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
- const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] })));
- const text = bez.get(0.5);
- const linkDesc = StrCast(this.dataDoc.link_description) || 'description';
- const strokeWidth = NumCast(this.dataDoc.stroke_width, 4);
- const dash = StrCast(this.Document.stroke_dash);
- const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
- const { pts, lx, ty, rx, by } = this.renderProps;
+ const strokeWidth = NumCast(stroke_width, 4);
+ const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
+ const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
return (
- <div style={{ transition: 'inherit', pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
- <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ transition: 'inherit', overflow: 'visible' }}>
- <defs>
- <filter x="0" y="0" width="1" height="1" id={`${this.Document[Id] + 'background'}`}>
- <feFlood floodColor={`${StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
- <feMerge>
- <feMergeNode in="bg" />
- <feMergeNode in="SourceGraphic" />
- </feMerge>
- </filter>
- </defs>
- <path
- className="collectionfreeformlinkview-linkLine"
- style={{
- pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
- stroke: highlightColor ?? 'lightblue',
- strokeDasharray,
- strokeWidth,
- transition: 'inherit',
- }}
- d={`M ${pts[1][0] - lx} ${pts[1][1] - ty} C ${pts[1][0] + pts[1][0] - pts[0][0] - lx} ${pts[1][1] + pts[1][1] - pts[0][1] - ty},
- ${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
+ <>
+ {!highlightColor ? null : (
+ <Xarrow
+ divContainerStyle={{ transform: `scale(${scale})` }}
+ start={aid}
+ end={bid} //
+ strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
+ showHead={stroke_startMarker ? true : false}
+ showTail={stroke_endMarker ? true : false}
+ headSize={NumCast(stroke_markerScale, 3)}
+ tailSize={NumCast(stroke_markerScale, 3)}
+ tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ color={highlightColor}
/>
- <text
- filter={`url(#${this.Document[Id] + 'background'})`}
- style={{ pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
- x={text.x - lx}
- y={text.y - ty}>
- <tspan>&nbsp;</tspan>
- <tspan dy="2">{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}</tspan>
- <tspan dy="2">&nbsp;</tspan>
- </text>
- </svg>
- </div>
+ )}
+ <Xarrow
+ divContainerStyle={{ transform: `scale(${scale})` }}
+ start={aid}
+ end={bid} //
+ strokeWidth={strokeWidth}
+ dashness={Number(stroke_dash) ? true : false}
+ showHead={stroke_startMarker ? true : false}
+ showTail={stroke_endMarker ? true : false}
+ headSize={NumCast(stroke_markerScale, 3)}
+ tailSize={NumCast(stroke_markerScale, 3)}
+ tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ color={color}
+ labels={
+ <div
+ style={{
+ borderRadius: '8px',
+ pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
+ fontSize,
+ fontFamily /*, fontStyle: 'italic'*/,
+ color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
+ paddingLeft: 4,
+ paddingRight: 4,
+ paddingTop: 3,
+ paddingBottom: 3,
+ background: DashColor((!docView?.isSelected() && highlightColor) || color)
+ .fade(0.5)
+ .toString(),
+ }}>
+ <EditableView
+ key="editableView"
+ oneLine
+ contents={labelText}
+ height={fontSize + 4}
+ fontSize={fontSize}
+ GetValue={() => linkDesc}
+ SetValue={action(val => {
+ this.Document[DocData].link_description = val;
+ return true;
+ })}
+ />
+
+ {/* <EditableText
+ placeholder={labelText}
+ background={color}
+ color={fontColor || lightOrDark(DashColor(color).fade(0.5).toString())}
+ type={Type.PRIM}
+ val={StrCast(this.Document[DocData].link_description)}
+ setVal={action(val => (this.Document[DocData].link_description = val))}
+ fillWidth
+ /> */}
+ </div>
+ }
+ passProps={{}}
+ />
+ </>
);
}
return (
<div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
- {...this._props}
+ {...this.props} //
fieldKey="link_anchor"
setHeight={emptyFunction}
dontRegisterView={true}
renderDepth={this._props.renderDepth + 1}
- isContentActive={this._props.isContentActive}
addDocument={returnFalse}
removeDocument={returnFalse}
moveDocument={returnFalse}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 8ad0b7dde..1645d0813 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,20 +1,28 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { LinkManager } from '../../util/LinkManager';
import './LinkDescriptionPopup.scss';
import { TaskCompletionBox } from './TaskCompletedBox';
+import { StrCast } from '../../../fields/Types';
@observer
export class LinkDescriptionPopup extends React.Component<{}> {
- @observable public static descriptionPopup: boolean = false;
- @observable public static showDescriptions: string = 'ON';
- @observable public static popupX: number = 700;
- @observable public static popupY: number = 350;
+ public static Instance: LinkDescriptionPopup;
+ @observable public display: boolean = false;
+ @observable public showDescriptions: string = 'ON';
+ @observable public popupX: number = 700;
+ @observable public popupY: number = 350;
@observable description: string = '';
@observable popupRef = React.createRef<HTMLDivElement>();
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ LinkDescriptionPopup.Instance = this;
+ }
+
@action
descriptionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
this.description = e.currentTarget.value;
@@ -22,23 +30,28 @@ export class LinkDescriptionPopup extends React.Component<{}> {
@action
onDismiss = (add: boolean) => {
- LinkDescriptionPopup.descriptionPopup = false;
+ this.display = false;
if (add) {
- LinkManager.currentLink && (Doc.GetProto(LinkManager.currentLink).link_description = this.description);
+ LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[DocData].link_description = this.description);
}
+ this.description = '';
};
@action
onClick = (e: PointerEvent) => {
if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
- LinkDescriptionPopup.descriptionPopup = false;
+ this.display = false;
+ this.description = '';
TaskCompletionBox.taskCompleted = false;
}
};
- @action
componentDidMount() {
document.addEventListener('pointerdown', this.onClick, true);
+ reaction(
+ () => this.display,
+ display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description))
+ );
}
componentWillUnmount() {
@@ -46,20 +59,22 @@ export class LinkDescriptionPopup extends React.Component<{}> {
}
render() {
- return (
+ return !this.display ? null : (
<div
className="linkDescriptionPopup"
ref={this.popupRef}
style={{
- left: LinkDescriptionPopup.popupX ? LinkDescriptionPopup.popupX : 700,
- top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350,
+ left: this.popupX ? this.popupX : 700,
+ top: this.popupY ? this.popupY : 350,
}}>
<input
className="linkDescriptionPopup-input"
onKeyDown={e => e.stopPropagation()}
onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)}
- placeholder={'(Optional) Enter link description...'}
- onChange={e => this.descriptionChanged(e)}></input>
+ value={this.description}
+ placeholder={this.description || '(Optional) Enter link description...'}
+ onChange={e => this.descriptionChanged(e)}
+ />
<div className="linkDescriptionPopup-btn">
<div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
{' '}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index ea23ecbea..ae25ff179 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -18,7 +18,8 @@ import { SearchUtil } from '../../util/SearchUtil';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
import { ObservableReactComponent } from '../ObservableReactComponent';
-import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { StyleProviderFuncType } from './FieldView';
import './LinkDocPreview.scss';
export class LinkInfo {
@@ -43,7 +44,8 @@ export class LinkInfo {
interface LinkDocPreviewProps {
linkDoc?: Doc;
linkSrc?: Doc;
- docProps: DocumentViewSharedProps;
+ DocumentView?: () => DocumentView;
+ styleProvider?: StyleProviderFuncType;
location: number[];
hrefs?: string[];
showHeader?: boolean;
@@ -150,9 +152,9 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
returnFalse,
emptyFunction,
action(() => {
- LinkManager.currentLink = this._linkDoc;
- LinkManager.currentLinkAnchor = this._linkSrc;
- this._props.docProps.DocumentView?.().select(false);
+ LinkManager.Instance.currentLink = this._linkDoc;
+ LinkManager.Instance.currentLinkAnchor = this._linkSrc;
+ this._props.DocumentView?.().select(false);
if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
SettingsManager.Instance.propertiesWidth = 250;
}
@@ -182,7 +184,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false);
} else if (this._props.hrefs?.length) {
const webDoc =
- Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0]).keys()).lastElement() ??
+ Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys()).lastElement() ??
Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true });
DocumentManager.Instance.showDocument(webDoc, {
openLocation: OpenWhere.lightbox,
@@ -269,8 +271,8 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
}}
Document={this._targetDoc!}
moveDocument={returnFalse}
- styleProvider={this._props.docProps?.styleProvider}
- docViewPath={returnEmptyDoclist}
+ styleProvider={this._props.styleProvider}
+ containerViewPath={returnEmptyDoclist}
ScreenToLocalTransform={Transform.Identity}
isDocumentActive={returnFalse}
isContentActive={returnFalse}
@@ -291,7 +293,6 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
focus={emptyFunction}
whenChildContentsActiveChanged={returnFalse}
ignoreAutoHeight={true} // need to ignore layout_autoHeight otherwise layout_autoHeight text boxes will expand beyond the preview panel size.
- bringToFront={returnFalse}
NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined}
NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined}
/>
diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx
index 3575b21e4..c185c66fc 100644
--- a/src/client/views/nodes/MapBox/MapBox.tsx
+++ b/src/client/views/nodes/MapBox/MapBox.tsx
@@ -23,12 +23,12 @@ import { DragManager } from '../../../util/DragManager';
import { LinkManager } from '../../../util/LinkManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../trails';
import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
@@ -96,7 +96,7 @@ type MapMarker = {
// });
@observer
-export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(MapBox, fieldKey);
}
@@ -107,7 +107,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _disposers: { [key: string]: IReactionDisposer } = {};
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
@@ -248,7 +248,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
componentDidMount() {
this._unmounting = false;
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
_unmounting = false;
@@ -379,16 +379,16 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
});
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
- const docView = this._props.DocumentView?.();
+ const docView = this.DocumentView?.();
docView &&
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
+ e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
@@ -504,7 +504,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
};
- getView = async (doc: Doc, options: DocFocusOptions) => {
+ getView = async (doc: Doc, options: FocusViewOptions) => {
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
this.toggleSidebar();
options.didMove = true;
diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx
index 9734d9db1..9825824bd 100644
--- a/src/client/views/nodes/MapBox/MapBox2.tsx
+++ b/src/client/views/nodes/MapBox/MapBox2.tsx
@@ -12,7 +12,7 @@
// import { SnappingManager } from '../../../util/SnappingManager';
// import { UndoManager } from '../../../util/UndoManager';
// import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
-// import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+// import { ViewBoxAnnotatableComponent } from '../../DocComponent';
// import { Colors } from '../../global/globalEnums';
// import { AnchorMenu } from '../../pdf/AnchorMenu';
// import { Annotation } from '../../pdf/Annotation';
@@ -83,7 +83,7 @@
// } as google.maps.places.AutocompleteOptions;
// @observer
-// export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps & Partial<GoogleMapProps>>() {
+// export class MapBox2 extends ViewBoxAnnotatableComponent<FieldViewProps & Partial<GoogleMapProps>>() {
// private _dropDisposer?: DragManager.DragDropDisposer;
// private _disposers: { [name: string]: IReactionDisposer } = {};
// private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -137,7 +137,7 @@
// // iterate allMarkers to size, center, and zoom map to contain all markers
// private fitBounds = (map: google.maps.Map) => {
-// const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds();
+// const curBounds = map.getBounds ?? new window.google.maps.LatLngBounds();
// const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean);
// !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds()));
// };
@@ -257,7 +257,7 @@
// map.setZoom(NumCast(this.dataDoc.map_zoom, 2.5));
// map.setCenter(new google.maps.LatLng(NumCast(this.dataDoc.mapLat), NumCast(this.dataDoc.mapLng)));
// setTimeout(() => {
-// if (this._loadPending && this._map.getBounds()) {
+// if (this._loadPending && this._map.getBounds) {
// this._loadPending = false;
// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
// }
@@ -272,7 +272,7 @@
// @action
// centered = () => {
-// if (this._loadPending && this._map.getBounds()) {
+// if (this._loadPending && this._map.getBounds) {
// this._loadPending = false;
// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
// }
@@ -282,7 +282,7 @@
// @action
// zoomChanged = () => {
-// if (this._loadPending && this._map.getBounds()) {
+// if (this._loadPending && this._map.getBounds) {
// this._loadPending = false;
// this.layoutDoc.freeform_fitContentsToBox && this.fitBounds(this._map);
// }
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index a9c6ba22c..6ccbbbe1c 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -9,19 +9,18 @@
// import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
// import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView';
// import { CollectionStackingView } from '../../collections/CollectionStackingView';
-// import { ViewBoxAnnotatableProps } from '../../DocComponent';
// import { FieldViewProps } from '../FieldView';
// import { FormattedTextBox } from '../formattedText/FormattedTextBox';
// import './MapBox.scss';
-// interface MapBoxInfoWindowProps {
+// interface MapBoxInfoWindowProps extends FieldViewProps {
// place: Doc;
// renderDepth: number;
// markerMap: { [id: string]: google.maps.Marker };
// isAnyChildContentActive: () => boolean;
// }
// @observer
-// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & ViewBoxAnnotatableProps & FieldViewProps> {
+// export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps> {
// @action
// private handleInfoWindowClose = () => {
// if (this.props.place.infoWindowOpen) {
diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
index 34e237007..fc5b4dd18 100644
--- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx
+++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx
@@ -18,10 +18,10 @@ export class MapPushpinBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
get mapBoxView() {
- return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as MapBox;
+ return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBox;
}
get mapBox() {
- return this._props.DocumentView?.()._props.docViewPath().lastElement()?.Document;
+ return this.DocumentView?.().containerViewPath?.().lastElement()?.Document;
}
render() {
diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
index ea8496c99..8a5bd7ce6 100644
--- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
+++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, EditableText, IconButton, Type } from 'browndash-components';
-import { IReactionDisposer, ObservableMap, action, computed, observable, reaction, runInAction } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MapProvider, Map as MapboxMap } from 'react-map-gl';
@@ -16,18 +16,17 @@ import { LinkManager } from '../../../util/LinkManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { UndoManager, undoable } from '../../../util/UndoManager';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { MapAnchorMenu } from '../MapBox/MapAnchorMenu';
import { FormattedTextBox } from '../formattedText/FormattedTextBox';
import { PinProps, PresBox } from '../trails';
import './MapBox.scss';
-// amongus
/**
* MapBox architecture:
* Main component: MapBox.tsx
@@ -61,7 +60,7 @@ const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing
// });
@observer
-export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class MapBoxContainer extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(MapBoxContainer, fieldKey);
}
@@ -71,6 +70,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
private _disposers: { [key: string]: IReactionDisposer } = {};
private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void);
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ }
+
@observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
@computed get allSidebarDocs() {
return DocListCast(this.dataDoc[this.SidebarKey]);
@@ -97,7 +101,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
componentDidMount() {
this._unmounting = false;
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
_unmounting = false;
@@ -228,16 +232,16 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
});
const targetCreator = (annotationOn: Doc | undefined) => {
- const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow');
+ const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, undefined, annotationOn, 'yellow');
FormattedTextBox.SetSelectOnLoad(target);
return target;
};
- const docView = this._props.DocumentView?.();
+ const docView = this.DocumentView?.();
docView &&
DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
dragComplete: e => {
if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
- e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
+ e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.Document;
e.annoDragData.linkSourceDoc.followLinkZoom = false;
}
},
@@ -374,7 +378,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
}
};
- getView = async (doc: Doc, options: DocFocusOptions) => {
+ getView = async (doc: Doc, options: FocusViewOptions) => {
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
this.toggleSidebar();
options.didMove = true;
@@ -787,8 +791,8 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata
NativeWidth={returnOne}
NativeHeight={returnOne}
onKey={undefined}
- onDoubleClick={undefined}
- onBrowseClick={undefined}
+ onDoubleClickScript={undefined}
+ onBrowseClickScript={undefined}
childFilters={returnEmptyFilter}
childFiltersByRanges={returnEmptyFilter}
searchFilterDocs={returnEmptyDoclist}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 959d5d88d..1274220b6 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -5,6 +5,7 @@ import * as Pdfjs from 'pdfjs-dist';
import 'pdfjs-dist/web/pdf_viewer.css';
import * as React from 'react';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ComputedField } from '../../../fields/ScriptField';
@@ -22,19 +23,19 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm';
import { CollectionStackingView } from '../collections/CollectionStackingView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { Colors } from '../global/globalEnums';
import { CreateImage } from '../nodes/WebBoxRenderer';
import { PDFViewer } from '../pdf/PDFViewer';
import { SidebarAnnos } from '../SidebarAnnos';
-import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { ImageBox } from './ImageBox';
import './PDFBox.scss';
import { PinProps, PresBox } from './trails';
@observer
-export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(PDFBox, fieldKey);
}
@@ -58,7 +59,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url;
}
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927;
@@ -97,12 +98,13 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
crop = (region: Doc | undefined, addCrop?: boolean) => {
if (!region) return;
const cropping = Doc.MakeCopy(region, true);
- Doc.GetProto(region).lockedPosition = true;
- Doc.GetProto(region).title = 'region:' + this.Document.title;
- Doc.GetProto(region).followLinkToggle = true;
+ const regionData = region[DocData];
+ regionData.lockedPosition = true;
+ regionData.title = 'region:' + this.Document.title;
+ regionData.followLinkToggle = true;
this.addDocument(region);
- const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
+ const docViewContent = this.DocumentView?.().ContentDiv!;
const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
newDiv.style.width = NumCast(this.layoutDoc._width).toString();
newDiv.style.height = NumCast(this.layoutDoc._height).toString();
@@ -120,7 +122,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
cropping._width = anchw;
cropping._height = anchh;
cropping.onClick = undefined;
- const croppingProto = Doc.GetProto(cropping);
+ const croppingProto = cropping[DocData];
croppingProto.annotationOn = undefined;
croppingProto.isDataDoc = true;
croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
@@ -132,7 +134,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
- this._props.bringToFront(cropping);
+ this._props.bringToFront?.(cropping);
CreateImage(
'',
@@ -163,7 +165,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
updateIcon = () => {
// currently we render pdf icons as text labels
- const docViewContent = this._props.docViewPath().lastElement().ContentDiv!;
+ const docViewContent = this.DocumentView?.().ContentDiv!;
const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime();
this._pdfViewer?._mainCont.current &&
CollectionFreeFormView.UpdateIcon(
@@ -191,7 +193,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
Object.values(this._disposers).forEach(disposer => disposer?.());
}
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
this._disposers.select = reaction(
() => this._props.isSelected(),
() => {
@@ -219,12 +221,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
return this._props.addDocTab(doc, where);
};
- focus = (anchor: Doc, options: DocFocusOptions) => {
+ focus = (anchor: Doc, options: FocusViewOptions) => {
this._initialScrollTarget = anchor;
return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options);
};
- getView = async (doc: Doc, options: DocFocusOptions) => {
+ getView = async (doc: Doc, options: FocusViewOptions) => {
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
options.didMove = true;
this.toggleSidebar(false);
@@ -304,7 +306,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setPdfViewer = (pdfViewer: PDFViewer) => {
this._pdfViewer = pdfViewer;
- const docView = this._props.DocumentView?.();
+ const docView = this.DocumentView?.();
if (this._initialScrollTarget && docView) {
this.focus(this._initialScrollTarget, { instant: true });
this._initialScrollTarget = undefined;
@@ -525,10 +527,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
removeDocument={this.removeDocument}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
<ComponentTag
{...this._props}
- setContentView={emptyFunction} // override setContentView to do nothing
+ setContentViewBox={emptyFunction} // override setContentView to do nothing
NativeWidth={this.sidebarNativeWidthFunc}
NativeHeight={this.sidebarNativeHeightFunc}
PanelHeight={this._props.PanelHeight}
@@ -581,6 +583,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}}>
<PDFViewer
{...this._props}
+ pdfBox={this}
sidebarAddDoc={this.sidebarAddDocument}
addDocTab={this.sidebarAddDocTab}
layoutDoc={this.layoutDoc}
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index c04a81f7d..f6d94ce05 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -1,4 +1,4 @@
-import { action, observable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DateField } from '../../../../fields/DateField';
@@ -21,6 +21,7 @@ import { media_state } from '../AudioBox';
import { FieldView, FieldViewProps } from '../FieldView';
import { VideoBox } from '../VideoBox';
import { RecordingView } from './RecordingView';
+import { DocData } from '../../../../fields/DocSymbols';
@observer
export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -30,8 +31,13 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ }
+
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
Doc.SetNativeWidth(this.dataDoc, 1280);
Doc.SetNativeHeight(this.dataDoc, 720);
}
@@ -100,7 +106,7 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
screengrabber.overlayX = 70; //was -400
screengrabber.overlayY = 590; //was 0
- Doc.GetProto(screengrabber)[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+ screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index f74e6fb2b..1e3933ac3 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -21,12 +21,13 @@ import { TrackMovements } from '../../util/TrackMovements';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { media_state } from './AudioBox';
import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import './ScreenshotBox.scss';
import { VideoBox } from './VideoBox';
+import { DocData } from '../../../fields/DocSymbols';
declare class MediaRecorder {
constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -109,7 +110,7 @@ declare class MediaRecorder {
// }
@observer
-export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ScreenshotBox, fieldKey);
}
@@ -121,7 +122,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
return Cast(this.dataDoc[this._props.fieldKey + '_recordingStart'], DateField)?.date.getTime();
}
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
this.setupDictation();
@@ -144,7 +145,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
componentDidMount() {
this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = 0;
- this._props.setContentView?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+ this._props.setContentViewBox?.(this); // this tells the DocumentView that this ScreenshotBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
// this.layoutDoc.videoWall && reaction(() => ({ width: this._props.PanelWidth(), height: this._props.PanelHeight() }),
// ({ width, height }) => {
// if (this._camera) {
@@ -281,7 +282,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
const textField = Doc.LayoutFieldKey(dictationText);
dictationText._layout_autoHeight = false;
- const dictationTextProto = Doc.GetProto(dictationText);
+ const dictationTextProto = dictationText[DocData];
dictationTextProto[`${textField}_recordingSource`] = this.dataDoc;
dictationTextProto[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`);
dictationTextProto.mediaState = ComputedField.MakeFunction(`this.${textField}_recordingSource.mediaState`);
@@ -297,7 +298,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
<div style={{ position: 'relative', height: this.videoPanelHeight() }}>
<CollectionFreeFormView
{...this._props}
- setContentView={emptyFunction}
+ setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
PanelHeight={this.videoPanelHeight}
diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx
index 73ad3a004..89650889d 100644
--- a/src/client/views/nodes/ScriptingBox.tsx
+++ b/src/client/views/nodes/ScriptingBox.tsx
@@ -14,7 +14,7 @@ import { ScriptManager } from '../../util/ScriptManager';
import { CompileScript, ScriptParam } from '../../util/Scripting';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { ContextMenu } from '../ContextMenu';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { EditableView } from '../EditableView';
import { OverlayView } from '../OverlayView';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
@@ -23,7 +23,7 @@ import './ScriptingBox.scss';
const _global = (window /* browser */ || global) /* node */ as any;
@observer
-export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
private dropDisposer?: DragManager.DragDropDisposer;
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(ScriptingBox, fieldStr);
@@ -56,7 +56,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
@observable private _scriptSuggestedParams: any = '';
@observable private _scriptParamsText: any = '';
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
if (!this.compileParams.length) {
@@ -116,7 +116,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable
@action
componentDidMount() {
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
this.rawText = this.rawScript;
const observer = new _global.ResizeObserver(
action((entries: any) => {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index fb42286af..b2ae7201c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,10 +4,11 @@ import { observer } from 'mobx-react';
import { basename } from 'path';
import * as React from 'react';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { AudioField, ImageField, VideoField } from '../../../fields/URLField';
import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
@@ -21,15 +22,16 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
-import { DocFocusOptions, DocumentView } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
+import { dropActionType } from '../../util/DragManager';
/**
* VideoBox
@@ -44,7 +46,7 @@ import './VideoBox.scss';
*/
@observer
-export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(VideoBox, fieldKey);
}
@@ -61,10 +63,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
private _playRegionTimer: any = null; // timeout for playback
private _controlsFadeTimer: any = null; // timeout for controls fade
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
}
@observable _stackedTimeline: CollectionStackedTimeline | undefined = undefined; // CollectionStackedTimeline ref
@@ -115,7 +117,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
componentDidMount() {
this.unmounting = false;
- this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
+ this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link.
this.player && this.setPlayheadTime(this.timeline?.clipStart || 0);
document.addEventListener('keydown', this.keyEvents, true);
@@ -329,12 +331,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
_height: (height / width) * 150,
title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
});
- Doc.SetNativeWidth(Doc.GetProto(imageSnapshot), Doc.NativeWidth(this.layoutDoc));
- Doc.SetNativeHeight(Doc.GetProto(imageSnapshot), Doc.NativeHeight(this.layoutDoc));
+ Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(this.layoutDoc));
+ Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(this.layoutDoc));
this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
- link && (Doc.GetProto(link.link_anchor_2 as Doc).timecodeToHide = NumCast((link.link_anchor_2 as Doc).timecodeToShow) + 3);
- setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
+ link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
+ setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
@@ -368,7 +370,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
!this.unmounting && this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime);
};
- getView = (doc: Doc, options: DocFocusOptions) => {
+ getView = (doc: Doc, options: FocusViewOptions) => {
if (this._stackedTimeline?.makeDocUnfiltered(doc)) {
if (this.heightPercent === 100) {
this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent;
@@ -608,7 +610,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// removes from currently playing display
@action
removeCurrentlyPlaying = () => {
- const docView = this._props.DocumentView?.();
+ const docView = this.DocumentView?.();
if (CollectionStackedTimeline.CurrentlyPlaying && docView) {
const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView);
index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1);
@@ -617,7 +619,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// adds doc to currently playing display
@action
addCurrentlyPlaying = () => {
- const docView = this._props.DocumentView?.();
+ const docView = this.DocumentView?.();
if (!CollectionStackedTimeline.CurrentlyPlaying) {
CollectionStackedTimeline.CurrentlyPlaying = [];
}
@@ -738,7 +740,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
zoom = (zoom: number) => this.timeline?.setZoom(zoom);
// plays link
- playLink = (doc: Doc, options: DocFocusOptions) => {
+ playLink = (doc: Doc, options: FocusViewOptions) => {
const startTime = Math.max(0, NumCast(doc.config_clipStart, this._stackedTimeline?.anchorStart(doc) || 0));
const endTime = this.timeline?.anchorEnd(doc);
if (startTime !== undefined) {
@@ -749,7 +751,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
// starts marquee selection
marqueeDown = (e: React.PointerEvent) => {
- if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) === 1 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -854,7 +856,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
renderDepth={this._props.renderDepth + 1}
startTag={'_timecodeToShow' /* videoStart */}
endTag={'_timecodeToHide' /* videoEnd */}
- bringToFront={emptyFunction}
playFrom={this.playFrom}
setTime={this.setPlayheadTime}
playing={this.playing}
@@ -885,10 +886,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
crop = (region: Doc | undefined, addCrop?: boolean) => {
if (!region) return;
const cropping = Doc.MakeCopy(region, true);
- Doc.GetProto(region).backgroundColor = 'transparent';
- Doc.GetProto(region).lockedPosition = true;
- Doc.GetProto(region).title = 'region:' + this.Document.title;
- Doc.GetProto(region).followLinkToggle = true;
+ const regionData = region[DocData];
+ regionData.backgroundColor = 'transparent';
+ regionData.lockedPosition = true;
+ regionData.title = 'region:' + this.Document.title;
+ regionData.followLinkToggle = true;
region._timecodeToHide = NumCast(region._timecodeToShow) + 0.0001;
this.addDocument(region);
const anchx = NumCast(cropping.x);
@@ -904,7 +906,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
cropping.timecodeToHide = undefined;
cropping.timecodeToShow = undefined;
cropping.onClick = undefined;
- const croppingProto = Doc.GetProto(cropping);
+ const croppingProto = cropping[DocData];
croppingProto.annotationOn = undefined;
croppingProto.isDataDoc = true;
croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
@@ -926,7 +928,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
if (addCrop) {
DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
}
- this._props.bringToFront(cropping);
+ this._props.bringToFront?.(cropping);
return cropping;
};
savedAnnotations = () => this._savedAnnotations;
@@ -941,7 +943,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
style={{
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
borderRadius,
- overflow: this._props.docViewPath?.().slice(-1)[0].layout_fitWidth ? 'auto' : undefined,
+ overflow: this.DocumentView?.().layout_fitWidth ? 'auto' : undefined,
}}>
<div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
<div
@@ -955,7 +957,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}}>
<CollectionFreeFormView
{...this._props}
- setContentView={emptyFunction}
+ setContentViewBox={emptyFunction}
NativeWidth={returnZero}
NativeHeight={returnZero}
renderDepth={this._props.renderDepth + 1}
@@ -978,7 +980,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
</CollectionFreeFormView>
</div>
{this.annotationLayer}
- {!this._mainCont.current || !this._annotationLayer.current ? null : (
+ {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
<MarqueeAnnotator
ref={this._marqueeref}
Document={this.Document}
@@ -986,7 +988,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
annotationLayerScrollTop={0}
scaling={returnOne}
annotationLayerScaling={this._props.NativeDimScaling}
- docView={this._props.DocumentView!}
+ docView={this.DocumentView}
containerOffset={this.marqueeOffset}
addDocument={this.addDocWithTimecode}
finishMarquee={this.finishMarquee}
@@ -1004,7 +1006,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
@computed get UIButtons() {
- const bounds = this._props.docViewPath().lastElement().getBounds();
+ const bounds = this.DocumentView?.().getBounds;
const width = (bounds?.right || 0) - (bounds?.left || 0);
const curTime = NumCast(this.layoutDoc._layout_currentTimecode);
return (
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c3be2b390..2c5398e40 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -14,7 +14,7 @@ import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -24,7 +24,7 @@ import { MarqueeOptionsMenu } from '../collections/collectionFreeForm';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { Colors } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
@@ -33,15 +33,15 @@ import { Annotation } from '../pdf/Annotation';
import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
import { SidebarAnnos } from '../SidebarAnnos';
import { StyleProp } from '../StyleProvider';
-import { DocComponentView, DocFocusOptions, DocumentView, DocumentViewInternalProps, DocumentViewProps, OpenWhere } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentView, OpenWhere } from './DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { LinkInfo } from './LinkDocPreview';
import { PinProps, PresBox } from './trails';
import './WebBox.scss';
const { CreateImage } = require('./WebBoxRenderer');
const _global = (window /* browser */ || global) /* node */ as any;
@observer
-export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
+export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(WebBox, fieldKey);
}
@@ -83,7 +83,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return this.webField?.toString() || '';
}
@computed get _urlHash() {
- return this._url ? WebBox.urlHash(this._url) + '' : '';
+ return ""+ (stringHash(this._url)??'');
}
@computed get scrollHeight() {
return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight);
@@ -98,7 +98,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return Cast(this.Document[this._props.fieldKey], WebField)?.url;
}
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
this._webUrl = this._url; // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
@@ -172,7 +172,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
componentDidMount() {
- this._props.setContentView?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
+ this._props.setContentViewBox?.(this); // this tells the DocumentView that this WebBox is the "content" of the document. this allows the DocumentView to call WebBox relevant methods to configure the UI (eg, show back/forward buttons)
runInAction(() => {
this._annotationKeySuffix = () => (this._urlHash ? this._urlHash + '_' : '') + 'annotations';
@@ -250,7 +250,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
createTextAnnotation = (sel: Selection, selRange: Range | undefined) => {
if (this._mainCont.current && selRange) {
- if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this._props.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
+ if (this.dataDoc[this._props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.DocumentView!().screenToContentsTransform().RotateDeg)}deg)`;
const clientRects = selRange.getClientRects();
for (let i = 0; i < clientRects.length; i++) {
const rect = clientRects.item(i);
@@ -274,12 +274,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
// clear selection
this._textAnnotationCreator = undefined;
- if (sel.empty) sel.empty(); // Chrome
+ if (sel.empty)
+ sel.empty(); // Chrome
else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox
return this._savedAnnotations;
};
- focus = (anchor: Doc, options: DocFocusOptions) => {
+ focus = (anchor: Doc, options: FocusViewOptions) => {
if (anchor !== this.Document && this._outerRef.current) {
const windowHeight = this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight));
@@ -296,9 +297,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
@action
- getView = (doc: Doc, options: DocFocusOptions) => {
- if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.()));
- if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify();
+ getView = (doc: Doc, options: FocusViewOptions) => {
+ if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this.DocumentView?.()));
+ if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify();
const webUrl = WebCast(doc.config_data)?.url;
if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href);
if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false);
@@ -343,7 +344,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
iframeUp = (e: PointerEvent) => {
this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value
this._textAnnotationCreator = undefined;
- this._props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
+ this.DocumentView?.()?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here.
if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) {
const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!);
const scale = (this._props.NativeDimScaling?.() || 1) * mainContBounds.scale;
@@ -364,7 +365,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
e.stopPropagation();
const sel = window.getSelection();
this._textAnnotationCreator = undefined;
- if (sel?.empty) sel.empty(); // Chrome
+ if (sel?.empty)
+ sel.empty(); // Chrome
else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
// bcz: NEED TO unrotate e.clientX and e.clientY
const word = getWordAtPoint(e.target, e.clientX, e.clientY);
@@ -556,7 +558,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
clearStyleSheetRules(WebBox.webStyleSheet);
this._scrollTimer = undefined;
const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop;
- if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()))) {
+ if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) {
this.layoutDoc.thumb = undefined;
this.layoutDoc.thumbScrollTop = undefined;
this.layoutDoc.thumbNativeWidth = undefined;
@@ -622,15 +624,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
return false;
};
- static urlHash = (s: string) => {
- const split = s.split('');
- return Math.abs(
- split.reduce((a: any, b: any) => {
- a = (a << 5) - a + b.charCodeAt(0);
- return a & a;
- }, 0)
- );
- };
@action
submitURL = (preview?: boolean, dontUpdateIframe?: boolean) => {
try {
@@ -723,10 +716,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onMarqueeDown = (e: React.PointerEvent) => {
const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
this._textAnnotationCreator = undefined;
- if (sel?.empty) sel.empty(); // Chrome
+ if (sel?.empty)
+ sel.empty(); // Chrome
else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
this.marqueeing = [e.clientX, e.clientY];
- if (!e.altKey && e.button === 0 && this._props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
setupMoveUpEvents(
this,
e,
@@ -750,7 +744,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.marqueeing = undefined;
this._textAnnotationCreator = undefined;
const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection();
- if (sel?.empty) sel.empty(); // Chrome
+ if (sel?.empty)
+ sel.empty(); // Chrome
else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox
this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.Document);
if (x !== undefined && y !== undefined) {
@@ -762,7 +757,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
setTimeout(() => {
// if menu comes up right away, the down event can still be active causing a menu item to be selected
this.specificContextMenu(undefined as any);
- this._props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y);
+ this.DocumentView?.().onContextMenu(undefined, x, y);
});
}
}
@@ -908,7 +903,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
});
@action
onZoomWheel = (e: React.WheelEvent) => {
- if (this._props.isContentActive(true)) {
+ if (this._props.isContentActive()) {
e.stopPropagation();
}
};
@@ -920,7 +915,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
};
_innerCollectionView: CollectionFreeFormView | undefined;
zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1;
- setInnerContent = (component: DocComponentView) => (this._innerCollectionView = component as CollectionFreeFormView);
+ setInnerContent = (component: ViewBoxInterface) => (this._innerCollectionView = component as CollectionFreeFormView);
@computed get content() {
const interactive = this._props.isContentActive() && this._props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None;
@@ -962,7 +957,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
renderAnnotations = (childFilters: () => string[]) => (
<CollectionFreeFormView
{...this._props}
- setContentView={this.setInnerContent}
+ setContentViewBox={this.setInnerContent}
NativeWidth={returnZero}
NativeHeight={returnZero}
originTopLeft={false}
@@ -979,7 +974,6 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
childFilters={childFilters}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
- bringToFront={emptyFunction}
styleProvider={this.childStyleProvider}
whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
removeDocument={this.removeDocument}
@@ -1065,7 +1059,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
transparentFilter = () => [...this._props.childFilters(), Utils.TransparentBackgroundFilter];
opaqueFilter = () => [...this._props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.CanEmbed ? [] : [Utils.OpaqueBackgroundFilter])];
- childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+ childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
if (doc instanceof Doc && property === StyleProp.PointerEvents) {
if (this.inlineTextAnnotations.includes(doc)) return 'none';
}
@@ -1099,7 +1093,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
onContextMenu={this.specificContextMenu}>
{this.webpage}
</div>
- {!this._mainCont.current || !this._annotationLayer.current ? null : (
+ {!this._mainCont.current || !this.DocumentView || !this._annotationLayer.current ? null : (
<div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}>
<MarqueeAnnotator
ref={this._marqueeref}
@@ -1109,7 +1103,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
annotationLayerScrollTop={0}
scaling={this._props.NativeDimScaling}
addDocument={this.addDocumentWrapper}
- docView={this._props.DocumentView!}
+ docView={this.DocumentView}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotationsCreator}
selectionText={this.selectionText}
@@ -1151,5 +1145,5 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
}
ScriptingGlobals.add(function urlHash(url: string) {
- return url ? WebBox.urlHash(url) : 0;
+ return stringHash(url);
});
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 4fb1a7f17..748c3322e 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -18,6 +18,11 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
return FieldView.LayoutString(CalendarBox, fieldKey);
}
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ }
+
componentDidMount(): void {}
componentWillUnmount(): void {}
@@ -105,11 +110,6 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
});
}
- constructor(props: any) {
- super(props);
- makeObservable(this);
- }
-
render() {
return (
<div className="calendar-box-conatiner">
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index b7d2a24c2..a72ed1813 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -3,6 +3,8 @@ import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
import * as React from 'react';
+import { IReactionDisposer, computed, reaction } from 'mobx';
+import { NumCast } from '../../../../fields/Types';
// creates an inline comment in a note when '>>' is typed.
// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
@@ -10,8 +12,10 @@ import * as React from 'react';
export class DashDocCommentView {
dom: HTMLDivElement; // container for label and value
root: any;
+ node: any;
constructor(node: any, view: any, getPos: any) {
+ this.node = node;
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
@@ -32,10 +36,14 @@ export class DashDocCommentView {
};
this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} docId={node.attrs.docId} />);
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
(this as any).dom = this.dom;
}
+ setHeight = (hgt: number) => {
+ !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
+ };
+
destroy() {
this.root.unmount();
}
@@ -51,9 +59,15 @@ interface IDashDocCommentViewInternal {
docId: string;
view: any;
getPos: any;
+ setHeight: (height: number) => void;
}
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
+ _reactionDisposer: IReactionDisposer | undefined;
+
+ @computed get _dashDoc() {
+ return DocServer.GetRefField(this.props.docId);
+ }
constructor(props: any) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -61,15 +75,32 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this);
this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this);
}
+ componentDidMount(): void {
+ this._reactionDisposer?.();
+ this._dashDoc.then(
+ doc =>
+ doc instanceof Doc &&
+ (this._reactionDisposer = reaction(
+ () => NumCast((doc as Doc)._height),
+ hgt => this.props.setHeight(hgt),
+ {
+ fireImmediately: true,
+ }
+ ))
+ );
+ }
+ componentWillUnmount(): void {
+ this._reactionDisposer?.();
+ }
onPointerLeaveCollapsed(e: any) {
- DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+ this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
}
onPointerEnterCollapsed(e: any) {
- DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+ this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
}
@@ -82,7 +113,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
- expand && DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
} catch (e) {}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 6a2cd359c..7335c9286 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -11,7 +11,8 @@ import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { Transform } from '../../../util/Transform';
import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DocFocusOptions, DocumentView } from '../DocumentView';
+import { DocumentView } from '../DocumentView';
+import { FocusViewOptions } from '../FieldView';
import { FormattedTextBox } from './FormattedTextBox';
var horizPadding = 3; // horizontal padding to container to allow cursor to show up on either side.
@@ -159,7 +160,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
return new Transform(-translateX, -translateY, 1).scale(1 / scale);
};
- outerFocus = (target: Doc, options: DocFocusOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
+ outerFocus = (target: Doc, options: FocusViewOptions) => this._textBox.focus(target, options); // ideally, this would scroll to show the focus target
onKeyDown = (e: any) => {
e.stopPropagation();
@@ -207,7 +208,7 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
isDocumentActive={returnFalse}
isContentActive={this.isContentActive}
styleProvider={this._textBox._props.styleProvider}
- docViewPath={this._textBox._props.docViewPath}
+ containerViewPath={this._textBox.DocumentView?.().docViewPath}
ScreenToLocalTransform={this.getDocTransform}
addDocTab={this._textBox._props.addDocTab}
pinToPres={returnFalse}
@@ -216,7 +217,6 @@ export class DashDocViewInternal extends ObservableReactComponent<IDashDocViewIn
PanelHeight={this._dashDoc[Height]}
focus={this.outerFocus}
whenChildContentsActiveChanged={this._props.tbox.whenChildContentsActiveChanged}
- bringToFront={emptyFunction}
dontRegisterView={false}
childFilters={this._props.tbox?._props.childFilters}
childFiltersByRanges={this._props.tbox?._props.childFiltersByRanges}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 3426ba1a7..7a0ff8776 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -5,6 +5,16 @@
display: inline-flex;
align-items: center;
+ select {
+ display: none;
+ }
+
+ &:hover {
+ select {
+ display: unset;
+ }
+ }
+
.dashFieldView-enumerables {
width: 10px;
height: 10px;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 555b752f0..b49e7dcf0 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,24 +1,27 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import { Doc } from '../../../../fields/Doc';
+import Select from 'react-select';
+import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast } from '../../../../fields/Types';
+import { Cast, StrCast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
+import { undoable, undoBatch } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
+import { FilterPanel } from '../../FilterPanel';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -97,7 +100,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
_reactionDisposer: IReactionDisposer | undefined;
_textBoxDoc: Doc;
_fieldKey: string;
- _fieldStringRef = React.createRef<HTMLSpanElement>();
+ _fieldRef = React.createRef<HTMLDivElement>();
@observable _dashDoc: Doc | undefined = undefined;
@observable _expanded = false;
@@ -114,6 +117,13 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}
}
+ componentDidMount() {
+ this._reactionDisposer = reaction(
+ () => (this._dashDoc ? Field.toKeyValueString(this._dashDoc, this._props.fieldKey) : undefined),
+ keyvalue => keyvalue && this._props.tbox.tryUpdateDoc(true)
+ );
+ }
+
componentWillUnmount() {
this._reactionDisposer?.();
}
@@ -148,7 +158,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}
createPivotForField = (e: React.MouseEvent) => {
- let container = this._props.tbox._props.DocumentView?.()._props.docViewPath().lastElement();
+ let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement();
if (container) {
const embedding = Doc.MakeEmbedding(container.Document);
embedding._type_collection = CollectionViewType.Time;
@@ -173,10 +183,22 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
});
};
+ @undoBatch
+ selectVal = (event: React.ChangeEvent<HTMLSelectElement> | undefined) => {
+ event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value);
+ };
+
+ @computed get values() {
+ const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
+
+ return vals.strings.map(facet => ({ value: facet, label: facet }));
+ }
+
render() {
return (
<div
className="dashFieldView"
+ ref={this._fieldRef}
style={{
width: this._props.width,
height: this._props.height,
@@ -184,11 +206,17 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
}}>
{this._props.hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
- {this._fieldKey}
+ {(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey}
</span>
)}
-
{this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+ {!this.values.length ? null : (
+ <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}>
+ {this.values.map(val => (
+ <option value={val.value}>{val.label}</option>
+ ))}
+ </select>
+ )}
</div>
);
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index ae26f170b..56008de8e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,8 +13,8 @@ import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -42,7 +42,7 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView
import { CollectionTreeView } from '../../collections/CollectionTreeView';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { AnchorMenu } from '../../pdf/AnchorMenu';
@@ -50,8 +50,8 @@ import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup';
import { SidebarAnnos } from '../../SidebarAnnos';
import { StyleProp } from '../../StyleProvider';
import { media_state } from '../AudioBox';
-import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { LinkInfo } from '../LinkDocPreview';
import { PinProps, PresBox } from '../trails';
import { DashDocCommentView } from './DashDocCommentView';
@@ -67,11 +67,10 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
+import Select from 'react-select';
// import * as applyDevTools from 'prosemirror-dev-tools';
-
-export interface FormattedTextBoxProps {}
@observer
-export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() {
+export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldStr: string) {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
@@ -91,6 +90,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
public _applyingChange: string = '';
+ private _inDrop = false;
private _finishingLink = false;
private _searchIndex = 0;
private _lastTimedMark: Mark | undefined = undefined;
@@ -123,7 +123,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
@computed get noSidebar() {
- return this._props.docViewPath().lastElement()?._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
+ return this.DocumentView?.()._props.hideDecorationTitle || this._props.noSidebar || this.Document._layout_noSidebar;
}
@computed get layout_sidebarWidthPercent() {
return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%');
@@ -202,7 +202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docId
}
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
FormattedTextBox.Instance = this;
@@ -271,14 +271,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (target) {
anchor.followLinkAudio = true;
let stopFunc: any;
- Doc.GetProto(target).mediaState = media_state.Recording;
- Doc.GetProto(target).audioAnnoState = 'recording';
- DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+ const targetData = target[DocData];
+ targetData.mediaState = media_state.Recording;
+ targetData.audioAnnoState = 'recording';
+ DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
let reactionDisposer = reaction(
() => target.mediaState,
action(dictation => {
if (!dictation) {
- Doc.GetProto(target).audioAnnoState = 'stopped';
+ targetData.audioAnnoState = 'stopped';
stopFunc();
reactionDisposer();
}
@@ -310,7 +311,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return target;
};
- DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this._props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
+ DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.DocumentView?.()!, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY);
});
const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to);
this._props.rootSelected?.() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom);
@@ -325,13 +326,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
} catch (e) {}
};
+ leafText = (node: Node) => {
+ if (node.type === this._editorView?.state.schema.nodes.dashField) {
+ const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc);
+ return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field);
+ }
+ return '';
+ };
dispatchTransaction = (tx: Transaction) => {
if (this._editorView && (this._editorView as any).docView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
+ this.tryUpdateDoc(false);
+ }
+ };
+ tryUpdateDoc = (force: boolean) => {
+ if (this._editorView && (this._editorView as any).docView) {
+ const state = this._editorView.state;
const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
- const newText = state.doc.textBetween(0, state.doc.content.size, ' \n');
+ const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText);
const newJson = JSON.stringify(state.toJSON());
const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box
const templateData = this.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template
@@ -347,16 +361,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
accumTags.push(node.attrs.fieldKey);
}
});
- dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined;
+ if (accumTags.some(atag => !StrListCast(dataDoc.tags).includes(atag))) {
+ dataDoc.tags = new List<string>(Array.from(new Set<string>(accumTags)));
+ }
let unchanged = true;
- if (this._applyingChange !== this.fieldKey && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+ if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
this._applyingChange = this.fieldKey;
const textChange = newText !== prevData?.Text;
textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now())));
- if ((!prevData && !protoData) || newText || (!newText && !templateData)) {
+ if ((!prevData && !protoData) || newText || (!newText && !protoData)) {
// if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended)
- if ((this._finishingLink || this._props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) {
+ if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) {
const numstring = NumCast(dataDoc[this.fieldKey], null);
dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
@@ -433,7 +449,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
autoLink = () => {
const newAutoLinks = new Set<Doc>();
- const oldAutoLinks = LinkManager.Links(this._props.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
+ const oldAutoLinks = LinkManager.Links(this.Document).filter(link => link.link_relationship === LinkManager.AutoKeywords);
if (this._editorView?.state.doc.textContent) {
const isNodeSel = this._editorView.state.selection instanceof NodeSelection;
const f = this._editorView.state.selection.from;
@@ -451,7 +467,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
updateTitle = () => {
const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text);
if (
- !this._props.dontRegisterView && // (this._props.Document.isTemplateForField === "text" || !this._props.Document.isTemplateForField) && // only update the title if the data document's data field is changing
+ !this._props.dontRegisterView && // (this.Document.isTemplateForField === "text" || !this.Document.isTemplateForField) && // only update the title if the data document's data field is changing
(title.startsWith('-') || title.startsWith('@')) &&
this._editorView &&
!this.dataDoc.title_custom &&
@@ -473,14 +489,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
// creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
+ /**
+ * Searches the text for occurences of any strings that match the names of 'published' documents. These document
+ * names will begin with an '@' prefix. However, valid matches within the text can have any of the following formats:
+ * name, @<name>, or ^@<name>
+ * The last of these is interpreted as an include directive when converting the text into evaluated code in the paint
+ * function of a freeform view that is driven by the text box's text. The include directive will copy the code of the published
+ * document into the code being evaluated.
+ */
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
const editorView = this._editorView;
if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
var alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
- const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
- if (!sel.$anchor.pos || editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim() === autoLinkTerm) {
+ if (
+ !sel.$anchor.pos ||
+ autoLinkTerm ===
+ editorView.state.doc
+ .textBetween(sel.$anchor.pos - 1, sel.$to.pos)
+ .trim()
+ .replace(/[\^@]+/, '')
+ ) {
+ const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
tr = tr.addMark(sel.from, sel.to, splitter);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
@@ -494,7 +525,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
DocUtils.MakeLink(this.Document, target, { link_relationship: LinkManager.AutoKeywords })!);
newAutoLinks.add(alink);
// DocCast(alink.link_anchor_1).followLinkLocation = 'add:right';
- const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this._props.Document[Id] }];
+ const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.Document[Id] }];
allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' });
tr = tr.addMark(pos, pos + node.nodeSize, link);
@@ -572,18 +603,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
if (dragData) {
const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc;
const effectiveAcl = GetEffectiveAcl(dataDoc);
- const draggedDoc = dragData.draggedDocuments.lastElement();
+ const draggedDoc = dragData.droppedDocuments.lastElement();
let added: Opt<boolean>;
+ const dropAction = dragData.dropAction || dragData.userDropAction;
if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) {
// replace text contents when dragging with Alt
if (de.altKey) {
const fieldKey = Doc.LayoutFieldKey(draggedDoc);
- if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this._props.Document)) {
+ if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.Document)) {
Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]);
}
// embed document when drag marked as embed
- } else if (de.embedKey) {
+ } else if (de.embedKey || dropAction) {
const node = schema.nodes.dashDoc.create({
width: NumCast(draggedDoc._width),
height: NumCast(draggedDoc._height),
@@ -591,20 +623,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
docId: draggedDoc[Id],
float: 'unset',
});
- if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) {
+ if (!['embed', 'copy'].includes((dropAction ?? '') as any)) {
added = dragData.removeDocument?.(draggedDoc) ? true : false;
+ } else {
+ added = true;
}
if (added) {
draggedDoc._freeform_fitContentsToBox = true;
Doc.SetContainer(draggedDoc, this.Document);
const view = this._editorView!;
try {
+ this._inDrop = true;
const pos = view.posAtCoords({ left: de.x, top: de.y })?.pos;
pos && view.dispatch(view.state.tr.insert(pos, node));
added = pos ? true : false; // pos will be null if you don't drop onto an actual text location
} catch (e) {
console.log('Drop failed', e);
added = false;
+ } finally {
+ this._inDrop = false;
}
}
}
@@ -647,12 +684,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let index = 0,
foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
- const regexp = new RegExp(find.replace('*', ''), 'i');
+ const regexp = new RegExp(find, 'i');
if (regexp) {
- while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
- const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
- ret.push(sel);
- index = index + foundAt + find.length;
+ var blockOffset = 0;
+ for (var i = 0; i < node.childCount; i++) {
+ var textContent = '';
+ while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) {
+ textContent += node.child(i).textContent;
+ i++;
+ }
+ while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
+ const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
+ ret.push(sel);
+ index = index + foundAt + find.length;
+ }
+ blockOffset += textContent.length;
+ if (i < node.childCount) blockOffset += node.child(i).nodeSize;
}
}
} else {
@@ -991,7 +1038,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
if (link) {
- Doc.GetProto(link).isDictation = true;
+ link[DocData].isDictation = true;
const audioanchor = Cast(link.link_anchor_2, Doc, null);
const textanchor = Cast(link.link_anchor_1, Doc, null);
if (audioanchor) {
@@ -1001,7 +1048,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
audioId: audioanchor[Id],
textId: textanchor[Id],
});
- Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
+ textanchor[DocData].title = 'dictation:' + audiotag.attrs.timeCode;
const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
@@ -1056,7 +1103,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return anchorDoc ?? this.Document;
}
- getView = async (doc: Doc, options: DocFocusOptions) => {
+ getView = async (doc: Doc, options: FocusViewOptions) => {
if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) {
if (!this.SidebarShown) {
this.toggleSidebar(false);
@@ -1066,7 +1113,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)));
};
- focus = (textAnchor: Doc, options: DocFocusOptions) => {
+ focus = (textAnchor: Doc, options: FocusViewOptions) => {
const focusSpeed = options.zoomTime ?? 500;
const textAnchorId = textAnchor[Id];
const findAnchorFrag = (frag: Fragment, editor: EditorView) => {
@@ -1141,12 +1188,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return Doc.NativeAspect(this.Document, this.dataDoc, false) ? this._props.NativeDimScaling?.() || 1 : 1;
}
componentDidMount() {
- !this._props.dontSelectOnLoad && this._props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+ !this._props.dontSelectOnLoad && this._props.setContentViewBox?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this._cachedLinks = LinkManager.Links(this.Document);
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
- () => this.layout_autoHeight,
- layout_autoHeight => layout_autoHeight && this.tryUpdateScrollHeight()
+ () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize }),
+ (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
);
this._disposers.highlights = reaction(
() => Array.from(FormattedTextBox._globalHighlights).slice(),
@@ -1410,7 +1457,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
(this._editorView as any).TextView = this;
}
- const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this._props.docViewPath()));
+ const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, FormattedTextBox.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()));
if (this._editorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) {
const selLoadChar = FormattedTextBox.SelectOnLoadChar;
FormattedTextBox.SelectOnLoad = undefined;
@@ -1507,15 +1554,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._downX = e.clientX;
this._downY = e.clientY;
this._downTime = Date.now();
- this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
// stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
e.stopPropagation(); // if the text box's content is active, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
+ (this.ProseRef?.children?.[0] as any).focus();
}
}
+ this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
@@ -1531,7 +1579,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- else if (this._props.isContentActive(true)) {
+ else if (this._props.isContentActive()) {
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
let xpos = pcords?.pos || 0;
while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
@@ -1676,7 +1724,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
FormattedTextBox.LiveTextUndo = undefined;
const state = this._editorView!.state;
- const curText = state.doc.textBetween(0, state.doc.content.size, ' \n');
if (StrCast(this.Document.title).startsWith('@') && !this.dataDoc.title_custom) {
UndoManager.RunInBatch(() => {
this.dataDoc.title_custom = true;
@@ -1764,7 +1811,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
};
const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
- const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight);
+ const scrollHeight = this.ProseRef && proseHeight;
if (this._props.setHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
const setScrollHeight = () => (this.dataDoc[this.fieldKey + '_scrollHeight'] = scrollHeight);
@@ -1777,7 +1824,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
}
};
- fitContentsToBox = () => BoolCast(this._props.Document._freeform_fitContentsToBox);
+ fitContentsToBox = () => BoolCast(this.Document._freeform_fitContentsToBox);
sidebarContentScaling = () => (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => {
if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
@@ -1852,7 +1899,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
setHeight={this.setSidebarHeight}
/>
) : (
- <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this._props.DocumentView?.()!, false), true)}>
+ <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}>
<ComponentTag
{...this._props}
ref={this._sidebarTagRef as any}
@@ -1972,7 +2019,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
render() {
TraceMobx();
- const scale = (this._props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1);
+ const scale = this._props.NativeDimScaling?.() || 1; // * NumCast(this.layoutDoc._freeform_scale, 1);
const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : '';
setTimeout(() => !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide);
const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0);
@@ -2011,7 +2058,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
cursor: this._props.isContentActive() ? 'text' : undefined,
height: this._props.height ? 'max-content' : undefined,
overflow: this.layout_autoHeight ? 'hidden' : undefined,
- pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClick?.() ? undefined : 'none',
+ pointerEvents: Doc.ActiveTool === InkTool.None && !this._props.onBrowseClickScript?.() ? undefined : 'none',
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyDown}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index be8736525..ce17af6ca 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -134,7 +134,8 @@ export class FormattedTextBoxComment {
//nbef &&
naft &&
LinkInfo.SetLinkInfo({
- docProps: textBox.props,
+ DocumentView: textBox.DocumentView,
+ styleProvider: textBox._props.styleProvider,
linkSrc: textBox.Document,
linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined,
location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))),
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 08bad2d57..47527847b 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -303,7 +303,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
const cr = state.selection.$from.node().textContent.endsWith('\n');
- if (cr || !newlineInCode(state, dispatch as any)) {
+ if (/*cr ||*/ !newlineInCode(state, dispatch as any)) {
if (
!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => {
const tx3 = updateBullets(tx2, schema);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 5858c3b11..cd0cdaa74 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -141,12 +141,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeSizes = active.activeSizes;
const activeColors = active.activeColors;
const activeHighlights = active.activeHighlights;
+ const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
+ const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
this.activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
@@ -358,8 +360,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
}
- } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
- SelectionManager.Views.forEach(dv => (dv.Document._text_fontSize = fontSize));
+ } else if (SelectionManager.Views.length) {
+ SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
} else Doc.UserDoc().fontSize = fontSize;
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
@@ -369,6 +371,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family });
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
+ } else if (SelectionManager.Views.length) {
+ SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
} else Doc.UserDoc().fontFamily = family;
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
@@ -387,6 +391,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
this.view.focus();
+ } else if (SelectionManager.Views.length) {
+ SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
} else Doc.UserDoc().fontColor = color;
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index 456ed4732..9bd41f42c 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -4,8 +4,7 @@ import { Doc, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { NumCast } from '../../../../fields/Types';
+import { NumCast, StrCast } from '../../../../fields/Types';
import { Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
@@ -13,6 +12,9 @@ import { FormattedTextBox } from './FormattedTextBox';
import { wrappingInputRule } from './prosemirrorPatches';
import { RichTextMenu } from './RichTextMenu';
import { schema } from './schema_rts';
+import { CollectionView } from '../../collections/CollectionView';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { ContextMenu } from '../../ContextMenu';
export class RichTextRules {
public Document: Doc;
@@ -66,7 +68,21 @@ export class RichTextRules {
),
// ``` create code block
- textblockTypeInputRule(/^```$/, schema.nodes.code_block),
+ new InputRule(/^```$/, (state, match, start, end) => {
+ let $start = state.doc.resolve(start);
+ if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null;
+
+ // this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script
+ this.TextBox.layoutDoc.type_collection = CollectionViewType.Freeform; // make it a freeform when rendered as a collection since those are the only views that know about the paint function
+ const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted'; // make a layout field key for storing the CollectionView jsx string pointing to the textbox's text
+ this.TextBox.dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey);
+ const layoutFieldKey = StrCast(this.TextBox.layoutDoc.layout_fieldKey); // save the current layout fieldkey
+ this.TextBox.layoutDoc.layout_fieldKey = paintedField; // setup the paint layout field key
+ this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''), 'onPaint'); // create the script to toggle between the painted and regular view
+ this.TextBox.layoutDoc.layout_fieldKey = layoutFieldKey; // restore the layout field key to text
+
+ return state.tr.delete(start, end).setBlockType(start, start, schema.nodes.code_block);
+ }),
// %<font-size> set the font size
new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => {
@@ -75,6 +91,32 @@ export class RichTextRules {
}),
//Create annotation to a field on the text document
+ new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+ const creator = (doc: Doc) => {
+ const textDoc = this.Document[DocData];
+ const numInlines = NumCast(textDoc.inlineTextCount);
+ textDoc.inlineTextCount = numInlines + 1;
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docId: doc[Id], reflow: false });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: doc[Id], float: 'right' });
+ const sm = state.storedMarks || undefined;
+ this.TextBox.EditorView?.dispatch(
+ node
+ ? this.TextBox.EditorView.state.tr
+ .insert(start, newNode)
+ .replaceRangeWith(start + 1, end + 2, dashDoc)
+ .insertText(' ', start + 2)
+ .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ : this.TextBox.EditorView.state.tr
+ );
+ };
+ DocUtils.addDocumentCreatorMenuItems(creator, creator, 200, 200);
+ const cm = ContextMenu.Instance;
+ cm.displayMenu(200, 200, undefined, true);
+
+ return null;
+ }),
+ //Create annotation to a field on the text document
new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
@@ -98,7 +140,7 @@ export class RichTextRules {
textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
textDoc[inlineFieldKey] = ''; // set a default value for the annotation
const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id] });
+ const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id], reflow: true });
const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' });
const sm = state.storedMarks || undefined;
const replaced = node
@@ -222,7 +264,7 @@ export class RichTextRules {
return null;
}),
- // stop using active style
+ // toggle alternate text UI %/
new InputRule(new RegExp(/%\//), (state, match, start, end) => {
setTimeout(this.TextBox.cycleAlternateText);
return state.tr.deleteRange(start, end);
@@ -246,49 +288,57 @@ export class RichTextRules {
// [[fieldKey]] => show field
// [[fieldKey=value]] => show field and also set its value
// [[fieldKey:docTitle]] => show field of doc
- new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
- const fieldKey = match[1];
- const docTitle = match[3]?.replace(':', '');
- const value = match[2]?.substring(1);
- const linkToDoc = (target: Doc) => {
- const rstate = this.TextBox.EditorView?.state;
- const selection = rstate?.selection.$from.pos;
- if (rstate) {
- this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
- }
+ new InputRule(
+ new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-z,A-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1];
+ const docTitle = match[3]?.replace(':', '');
+ const value = match[2]?.substring(1);
+ const linkToDoc = (target: Doc) => {
+ const rstate = this.TextBox.EditorView?.state;
+ const selection = rstate?.selection.$from.pos;
+ if (rstate) {
+ this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+ }
- DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
+ DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
- const fstate = this.TextBox.EditorView?.state;
- if (fstate && selection) {
- this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
- }
- };
- const getTitledDoc = (docTitle: string) => {
- if (!DocServer.FindDocByTitle(docTitle)) {
- Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
- }
- const titledDoc = DocServer.FindDocByTitle(docTitle);
- return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
- };
- if (!fieldKey) {
- if (docTitle) {
- const target = getTitledDoc(docTitle);
- if (target) {
- setTimeout(() => linkToDoc(target));
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate && selection) {
+ this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
}
+ };
+ const getTitledDoc = (docTitle: string) => {
+ if (!DocServer.FindDocByTitle(docTitle)) {
+ Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
+ }
+ const titledDoc = DocServer.FindDocByTitle(docTitle);
+ return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
+ };
+ if (!fieldKey) {
+ if (docTitle) {
+ const target = getTitledDoc(docTitle);
+ if (target) {
+ setTimeout(() => linkToDoc(target));
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ }
+ }
+ return state.tr;
}
- return state.tr;
- }
- if (value !== '' && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
- }
- const target = getTitledDoc(docTitle);
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
- return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
- }),
+ if (value?.includes(',')) {
+ const values = value.split(',');
+ const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
+ this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
+ } else if (value !== '' && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+ }
+ const target = getTitledDoc(docTitle);
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
+ return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
+ },
+ { inCode: true }
+ ),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// wiki:title
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index a342285b0..a141ef041 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,7 @@
import * as React from 'react';
import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
import { Doc } from '../../../../fields/Doc';
+import { Utils } from '../../../../Utils';
const emDOM: DOMOutputSpec = ['em', 0];
const strongDOM: DOMOutputSpec = ['strong', 0];
@@ -44,7 +45,7 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM(node: any) {
const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
- return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+ return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
},
},
noAutoLinkAnchor: {
@@ -104,7 +105,7 @@ export const marks: { [index: string]: MarkSpec } = {
node.attrs.title,
],
]
- : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
+ : ['a', { id: '' + Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
},
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index d023020e1..4706a97fa 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -2,6 +2,8 @@ import * as React from 'react';
import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model';
import { listItem, orderedList } from 'prosemirror-schema-list';
import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec';
+import { DocServer } from '../../../DocServer';
+import { Doc, Field } from '../../../../fields/Doc';
const blockquoteDOM: DOMOutputSpec = ['blockquote', 0],
hrDOM: DOMOutputSpec = ['hr'],
@@ -177,6 +179,7 @@ export const nodes: { [index: string]: NodeSpec } = {
dashComment: {
attrs: {
docId: { default: '' },
+ reflow: { default: true },
},
inline: true,
group: 'inline',
@@ -264,6 +267,18 @@ export const nodes: { [index: string]: NodeSpec } = {
hideKey: { default: false },
editable: { default: true },
},
+ leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field),
+ group: 'inline',
+ draggable: false,
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+ return ['div', { ...node.attrs, ...attrs }];
+ },
+ },
+
+ paintButton: {
+ inline: true,
+ attrs: {},
group: 'inline',
draggable: false,
toDOM(node) {
diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx
index 7d0086c0c..6e7c3e612 100644
--- a/src/client/views/nodes/importBox/ImportElementBox.tsx
+++ b/src/client/views/nodes/importBox/ImportElementBox.tsx
@@ -1,4 +1,4 @@
-import { computed } from 'mobx';
+import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { returnFalse } from '../../../../Utils';
@@ -12,6 +12,10 @@ export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ImportElementBox, fieldKey);
}
+ constructor(props: FieldViewProps) {
+ super(props);
+ makeObservable(this);
+ }
screenToLocalXf = () => this.ScreenToLocalBoxXf().scale(1 * (this._props.NativeDimScaling?.() || 1));
@computed get mainItem() {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 6fa64a765..918987034 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -3,8 +3,8 @@ import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { Doc, DocListCast, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
-import { Animation } from '../../../../fields/DocSymbols';
+import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
import { Copy, Id } from '../../../../fields/FieldSymbols';
import { InkField } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -13,7 +13,7 @@ import { listSpec } from '../../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { AudioField } from '../../../../fields/URLField';
-import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils';
+import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent, stringHash } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -31,11 +31,12 @@ import { TreeView } from '../../collections/TreeView';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
-import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
+import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { dropActionType } from '../../../util/DragManager';
export interface pinDataTypes {
scrollable?: boolean;
dataviz?: number[];
@@ -70,7 +71,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
static navigateToDocScript: ScriptField;
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
if (!PresBox.navigateToDocScript) {
@@ -184,7 +185,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
},
{ fireImmediately: true }
);
- this._props.setContentView?.(this);
+ this._props.setContentViewBox?.(this);
this._unmounting = false;
this.turnOffEdit(true);
this._disposers.selection = reaction(
@@ -294,13 +295,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
listItemDoc.presentation_transition = 500;
targetView?.setAnimEffect(listItemDoc, 500);
- if (targetView?.docView && this.activeItem.presBulletExpand) {
- targetView.docView._animateScalingTo = 1.2;
- targetView.docView._animateScaleTime = 400;
- Doc.AddUnHighlightWatcher(() => {
- targetView.docView!._animateScaleTime = undefined;
- targetView!.docView!._animateScalingTo = 0;
- });
+ if (targetView && this.activeItem.presBulletExpand) {
+ targetView.setAnimateScaling(1.2, 400);
+ Doc.AddUnHighlightWatcher(() => targetView?.setAnimateScaling(0, undefined));
}
listItemDoc.opacity = undefined;
this.activeItem.presentation_indexed = presIndexed + 1;
@@ -418,7 +415,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
bestTarget.rotation = NumCast(activeItem.config_rotation, NumCast(bestTarget.rotation));
bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
- setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
+ bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]);
+ bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10);
changed = true;
}
}
@@ -442,9 +440,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const setData = bestTargetView?.ComponentView?.setData;
if (setData) setData(activeItem.config_data);
else {
- const current = Doc.GetProto(bestTarget)[fkey];
- Doc.GetProto(bestTarget)[fkey + '_' + Date.now()] = current instanceof ObjectField ? current[Copy]() : current;
- Doc.GetProto(bestTarget)[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
+ const bestTargetData = bestTarget[DocData];
+ const current = bestTargetData[fkey];
+ const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined;
+ if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
+ bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
}
bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
@@ -502,11 +502,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
if (bestTarget.fillColor !== activeItem.config_fillColor) {
- Doc.GetProto(bestTarget).fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
+ bestTarget[DocData].fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
changed = true;
}
if (bestTarget.color !== activeItem.config_color) {
- Doc.GetProto(bestTarget).color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
+ bestTarget[DocData].color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
changed = true;
}
if (bestTarget.width !== activeItem.width) {
@@ -563,7 +563,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
return doc;
});
const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]);
- Doc.GetProto(bestTarget)[fkey + '_annotations'] = newList;
+ bestTarget[DocData][fkey + '_annotations'] = newList;
}
if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) {
changed = true;
@@ -584,9 +584,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
data.fill && (doc._fillColor = data.fill);
doc._width = data.w;
doc._height = data.h;
- data.data && (Doc.GetProto(doc).data = field);
- data.text && (Doc.GetProto(doc).text = tfield);
- Doc.AddDocToList(Doc.GetProto(bestTarget), layoutField, doc);
+ data.data && (doc[DocData].data = field);
+ data.text && (doc[DocData].text = tfield);
+ Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
}
});
setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
@@ -652,7 +652,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
if (pinProps.pinData.dataannos) {
const fkey = Doc.LayoutFieldKey(targetDoc);
- pinDoc.config_annotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
+ pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
}
if (pinProps.pinData.inkable) {
pinDoc.config_fillColor = targetDoc.fillColor;
@@ -767,7 +767,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
}
const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined;
const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500);
- const options: DocFocusOptions = {
+ const options: FocusViewOptions = {
willPan: activeItem.presentation_movement !== PresMovement.None,
willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center,
zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.config_zoom, 1),
@@ -1075,7 +1075,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
- removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
+ removeDocument = (doc: Doc | Doc[]) => !(doc instanceof Doc ? [doc] : doc).map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)).some(p => !p);
getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
panelHeight = () => this._props.PanelHeight() - 40;
/**
@@ -1119,7 +1119,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
presDocView && SelectionManager.SelectView(presDocView, false);
};
- focusElement = (doc: Doc, options: DocFocusOptions) => {
+ focusElement = (doc: Doc, options: FocusViewOptions) => {
this.selectElement(doc);
return undefined;
};
@@ -2608,8 +2608,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
childIgnoreNativeSize={true}
moveDocument={returnFalse}
ignoreUnrendered={true}
- childDragAction="move"
- setContentView={emptyFunction}
+ childDragAction={dropActionType.move}
+ setContentViewBox={emptyFunction}
//childLayoutFitWidth={returnTrue}
childOpacity={returnOne}
childClickScript={PresBox.navigateToDocScript}
@@ -2622,7 +2622,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
ScreenToLocalTransform={this.getTransform}
AddToMap={this.AddToMap}
RemFromMap={this.RemFromMap}
- hierarchyIndex={emptyPath}
+ hierarchyIndex={emptyPath as any as number[]}
/>
) : null}
</div>
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index 4945d66c8..5b2aa1cde 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -19,7 +19,7 @@ import { TreeView } from '../../collections/TreeView';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { Colors } from '../../global/globalEnums';
-import { DocumentView, DocumentViewInternalProps, DocumentViewProps } from '../../nodes/DocumentView';
+import { DocumentView } from '../../nodes/DocumentView';
import { FieldView, FieldViewProps } from '../../nodes/FieldView';
import { StyleProp } from '../../StyleProvider';
import { PresBox } from './PresBox';
@@ -41,7 +41,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
readonly expandViewHeight = 100;
readonly collapsedHeight = 35;
- constructor(props: any) {
+ constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
@@ -50,7 +50,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
// the presentation view that renders this slide
@computed get presBoxView() {
- return this._props.DocumentView?.()?._props.docViewPath().lastElement()?.ComponentView as PresBox;
+ return this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as PresBox;
}
// the presentation view document that renders this slide
@@ -97,7 +97,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
presExpandDocumentClick = () => (this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton);
embedHeight = () => this.collapsedHeight + this.expandViewHeight;
embedWidth = () => this._props.PanelWidth() / 2;
- styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any => {
+ styleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string): any => {
return property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property);
};
/**
@@ -116,7 +116,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
hideLinkButton={true}
ScreenToLocalTransform={Transform.Identity}
renderDepth={this._props.renderDepth + 1}
- docViewPath={returnEmptyDoclist}
+ containerViewPath={returnEmptyDoclist}
childFilters={this._props.childFilters}
childFiltersByRanges={this._props.childFiltersByRanges}
searchFilterDocs={this._props.searchFilterDocs}
@@ -128,7 +128,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
whenChildContentsActiveChanged={returnFalse}
addDocTab={returnFalse}
pinToPres={returnFalse}
- bringToFront={returnFalse}
/>
</div>
);
@@ -196,7 +195,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const dragArray = this.presBoxView?._dragArray ?? [];
const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []);
if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.slideDoc);
- dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this._props.DocumentView?.()?._props.treeViewDoc;
+ dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.DocumentView?.()?._props.treeViewDoc;
dragData.moveDocument = this._props.moveDocument;
const dragItem: HTMLElement[] = [];
const classesToRestore = new Map<HTMLElement, string>();