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.tsx27
-rw-r--r--src/client/views/nodes/ButtonBox.scss13
-rw-r--r--src/client/views/nodes/ButtonBox.tsx19
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.scss5
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx70
-rw-r--r--src/client/views/nodes/ColorBox.tsx4
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.scss24
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx126
-rw-r--r--src/client/views/nodes/DocuLinkBox.tsx35
-rw-r--r--src/client/views/nodes/DocumentBox.scss15
-rw-r--r--src/client/views/nodes/DocumentBox.tsx112
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx18
-rw-r--r--src/client/views/nodes/DocumentView.scss61
-rw-r--r--src/client/views/nodes/DocumentView.tsx587
-rw-r--r--src/client/views/nodes/FaceRectangle.tsx2
-rw-r--r--src/client/views/nodes/FaceRectangles.tsx8
-rw-r--r--src/client/views/nodes/FieldView.tsx9
-rw-r--r--src/client/views/nodes/FontIconBox.scss10
-rw-r--r--src/client/views/nodes/FontIconBox.tsx13
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss197
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx624
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.scss1
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx200
-rw-r--r--src/client/views/nodes/IconBox.tsx6
-rw-r--r--src/client/views/nodes/ImageBox.scss66
-rw-r--r--src/client/views/nodes/ImageBox.tsx211
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx50
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx10
-rw-r--r--src/client/views/nodes/PDFBox.scss304
-rw-r--r--src/client/views/nodes/PDFBox.tsx158
-rw-r--r--src/client/views/nodes/PresBox.tsx38
-rw-r--r--src/client/views/nodes/VideoBox.scss6
-rw-r--r--src/client/views/nodes/VideoBox.tsx129
-rw-r--r--src/client/views/nodes/WebBox.tsx24
34 files changed, 2057 insertions, 1125 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 86bd23b67..95c765e8a 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -8,7 +8,6 @@ import { DocExtendableComponent } from "../DocComponent";
import { makeInterface, createSchema } from "../../../new_fields/Schema";
import { documentSchema } from "../../../new_fields/documentSchemas";
import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils";
-import { RouteStore } from "../../../server/RouteStore";
import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
import { DateField } from "../../../new_fields/DateField";
import { SelectionManager } from "../../util/SelectionManager";
@@ -57,19 +56,19 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID,
scrollLinkId => {
scrollLinkId && DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => {
- let la1 = l.anchor1 as Doc;
- let linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode);
+ const la1 = l.anchor1 as Doc;
+ const linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode);
setTimeout(() => { this.playFrom(linkTime); Doc.linkFollowHighlight(l); }, 250);
});
scrollLinkId && Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
}, { fireImmediately: true });
this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(),
selected => {
- let sel = selected.length ? selected[0].props.Document : undefined;
+ const sel = selected.length ? selected[0].props.Document : undefined;
this.Document.playOnSelect && sel && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFrom(DateCast(sel.creationTime).date.getTime());
});
this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, timeInMillisecondsFrom1970 => {
- let start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart);
+ const start = this.extensionDoc && DateCast(this.extensionDoc.recordingStart);
start && this.playFrom((timeInMillisecondsFrom1970 - start.date.getTime()) / 1000);
});
}
@@ -128,7 +127,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
recordAudioAnnotation = () => {
let gumStream: any;
- let self = this;
+ const self = this;
const extensionDoc = this.extensionDoc;
extensionDoc && navigator.mediaDevices.getUserMedia({
audio: true
@@ -140,7 +139,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
self._recorder.ondataavailable = async function (e: any) {
const formData = new FormData();
formData.append("file", e.data);
- const res = await fetch(Utils.prepend(RouteStore.upload), {
+ const res = await fetch(Utils.prepend("/upload"), {
method: 'POST',
body: formData
});
@@ -161,7 +160,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
}
specificContextMenu = (e: React.MouseEvent): void => {
- let funcs: ContextMenuProps[] = [];
+ const funcs: ContextMenuProps[] = [];
funcs.push({ description: (this.Document.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.Document.playOnSelect = !this.Document.playOnSelect, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Audio Funcs...", subitems: funcs, icon: "asterisk" });
@@ -171,7 +170,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
this._recorder.stop();
this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000;
this._audioState = "recorded";
- let ind = AudioBox.ActiveRecordings.indexOf(this.props.Document);
+ const ind = AudioBox.ActiveRecordings.indexOf(this.props.Document);
ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1));
});
@@ -199,13 +198,13 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
}
@computed get path() {
- let field = Cast(this.props.Document[this.props.fieldKey], AudioField);
- let path = (field instanceof AudioField) ? field.url.href : "";
+ const field = Cast(this.props.Document[this.props.fieldKey], AudioField);
+ const path = (field instanceof AudioField) ? field.url.href : "";
return path === nullAudio ? "" : path;
}
@computed get audio() {
- let interactive = this.active() ? "-interactive" : "";
+ const interactive = this.active() ? "-interactive" : "";
return <audio ref={this.setRef} className={`audiobox-control${interactive}`}>
<source src={this.path} type="audio/mpeg" />
Not supported.
@@ -213,7 +212,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
}
render() {
- let interactive = this.active() ? "-interactive" : "";
+ const interactive = this.active() ? "-interactive" : "";
return (!this.extensionDoc ? (null) :
<div className={`audiobox-container`} onContextMenu={this.specificContextMenu}
onClick={!this.path ? this.recordClick : undefined}>
@@ -229,7 +228,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
<div className="audiobox-timeline" onClick={e => e.stopPropagation()}
onPointerDown={e => {
if (e.button === 0 && !e.ctrlKey) {
- let rect = (e.target as any).getBoundingClientRect();
+ const rect = (e.target as any).getBoundingClientRect();
this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
this.pause();
e.stopPropagation();
diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/ButtonBox.scss
index e8a3d1479..7c3825978 100644
--- a/src/client/views/nodes/ButtonBox.scss
+++ b/src/client/views/nodes/ButtonBox.scss
@@ -3,7 +3,7 @@
height: 100%;
pointer-events: all;
border-radius: inherit;
- display:flex;
+ display: flex;
flex-direction: column;
}
@@ -15,19 +15,22 @@
display: table;
overflow: hidden;
text-overflow: ellipsis;
+ letter-spacing: 2px;
+ text-transform: uppercase;
}
+
.buttonBox-mainButtonCenter {
height: 100%;
- display:table-cell;
+ display: table-cell;
vertical-align: middle;
}
.buttonBox-params {
- display:flex;
- flex-direction: row;
+ display: flex;
+ flex-direction: row;
}
.buttonBox-missingParam {
- width:100%;
+ width: 100%;
background: lightgray;
} \ No newline at end of file
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index beb2b30fd..d1272c266 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -46,15 +46,15 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
this.dropDisposer();
}
if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
}
}
specificContextMenu = (e: React.MouseEvent): void => {
- let funcs: ContextMenuProps[] = [];
+ const funcs: ContextMenuProps[] = [];
funcs.push({
description: "Clear Script Params", event: () => {
- let params = FieldValue(this.Document.buttonParams);
+ const params = FieldValue(this.Document.buttonParams);
params && params.map(p => this.props.Document[p] = undefined);
}, icon: "trash"
});
@@ -65,21 +65,22 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData && e.target) {
- this.props.Document[(e.target as any).textContent] = new List<Doc>(de.data.droppedDocuments.map((d, i) =>
- d.onDragStart ? de.data.draggedDocuments[i] : d));
+ const docDragData = de.complete.docDragData;
+ if (docDragData && e.target) {
+ this.props.Document[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) =>
+ d.onDragStart ? docDragData.draggedDocuments[i] : d));
e.stopPropagation();
}
}
// (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
render() {
- let params = this.Document.buttonParams;
- let missingParams = params && params.filter(p => this.props.Document[p] === undefined);
+ const params = this.Document.buttonParams;
+ const missingParams = params && params.filter(p => this.props.Document[p] === undefined);
params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ...
return (
<div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
- <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black" }} >
+ <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black", fontSize: this.Document.fontSize }} >
<div className="buttonBox-mainButtonCenter">
{(this.Document.text || this.Document.title)}
</div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index af9232c2f..da287649e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -2,6 +2,7 @@
transform-origin: left top;
position: absolute;
background-color: transparent;
- top:0;
- left:0;
+ touch-action: manipulation;
+ top: 0;
+ left: 0;
} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index c2c402819..614a68e7a 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,4 +1,4 @@
-import { random } from "animejs";
+import anime from "animejs";
import { computed, IReactionDisposer, observable, reaction, trace } from "mobx";
import { observer } from "mobx-react";
import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
@@ -10,38 +10,46 @@ import "./CollectionFreeFormDocumentView.scss";
import { DocumentView, DocumentViewProps } from "./DocumentView";
import React = require("react");
import { PositionDocument } from "../../../new_fields/documentSchemas";
+import { TraceMobx } from "../../../new_fields/util";
+import { returnFalse } from "../../../Utils";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
+ dataProvider?: (doc: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
x?: number;
y?: number;
width?: number;
height?: number;
jitterRotation: number;
transition?: string;
+ fitToBox?: boolean;
}
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
_disposer: IReactionDisposer | undefined = undefined;
- get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
+ get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+ get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${anime.random(-1, 1) * this.props.jitterRotation}deg)`; }
get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
- get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); }
- @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
+ get height() {
+ const hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
+ return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
+ }
+ @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
@computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); }
@computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); }
@computed get renderScriptDim() {
if (this.Document.renderScript) {
- let someView = Cast(this.props.Document.someView, Doc);
- let minimap = Cast(this.props.Document.minimap, Doc);
+ const someView = Cast(this.props.Document.someView, Doc);
+ const minimap = Cast(this.props.Document.minimap, Doc);
if (someView instanceof Doc && minimap instanceof Doc) {
- let x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2;
- let y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2;
- let w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
- let h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
+ const x = (NumCast(someView.panX) - NumCast(someView.width) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitX) - NumCast(minimap.fitW) / 2)) / NumCast(minimap.fitW) * NumCast(minimap.width) - NumCast(minimap.width) / 2;
+ const y = (NumCast(someView.panY) - NumCast(someView.height) / 2 / NumCast(someView.scale) - (NumCast(minimap.fitY) - NumCast(minimap.fitH) / 2)) / NumCast(minimap.fitH) * NumCast(minimap.height) - NumCast(minimap.height) / 2;
+ const w = NumCast(someView.width) / NumCast(someView.scale) / NumCast(minimap.fitW) * NumCast(minimap.width);
+ const h = NumCast(someView.height) / NumCast(someView.scale) / NumCast(minimap.fitH) * NumCast(minimap.height);
return { x: x, y: y, width: w, height: h };
}
}
@@ -52,11 +60,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
this._disposer && this._disposer();
}
componentDidMount() {
- this._disposer = reaction(() => [this.props.Document.animateToPos, this.props.Document.isAnimating],
- () => {
- const target = this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined;
- this._animPos = !target ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]);
- }, { fireImmediately: true });
+ this._disposer = reaction(() => this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined,
+ target => this._animPos = !target ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]),
+ { fireImmediately: true });
}
contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect ? this.width / this.nativeWidth : 1;
@@ -67,9 +73,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
.scale(1 / this.contentScaling())
borderRounding = () => {
- let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
- let ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined;
- let br = StrCast((ld || this.props.Document).borderRounding);
+ const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
+ const ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined;
+ const br = StrCast((ld || this.props.Document).borderRounding);
return !br && ruleRounding ? ruleRounding : br;
}
@@ -80,33 +86,43 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@observable _animPos: number[] | undefined = undefined;
- finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth();
- finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight();
+ finalPanelWidth = () => (this.dataProvider ? this.dataProvider.width : this.panelWidth());
+ finalPanelHeight = () => (this.dataProvider ? this.dataProvider.height : this.panelHeight());
render() {
- //trace();
+ TraceMobx();
return <div className="collectionFreeFormDocumentView-container"
style={{
boxShadow:
this.layoutDoc.opacity === 0 ? undefined : // if it's not visible, then no shadow
this.layoutDoc.z ? `#9c9396 ${StrCast(this.layoutDoc.boxShadow, "10px 10px 0.9vw")}` : // if it's a floating doc, give it a big shadow
this.clusterColor ? (`${this.clusterColor} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${(this.layoutDoc.isBackground ? 100 : 50) / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
- this.layoutDoc.isBackground ? `1px 1px 1px ${this.clusterColor}` : // if it's a background & has a cluster color, make the shadow spread really big
+ this.layoutDoc.isBackground ? undefined : // if it's a background & has a cluster color, make the shadow spread really big
StrCast(this.layoutDoc.boxShadow, ""),
borderRadius: this.borderRounding(),
transform: this.transform,
- transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
+ transition: this.Document.isAnimating ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
width: this.width,
height: this.height,
zIndex: this.Document.zIndex || 0,
}} >
- <DocumentView {...this.props}
+
+
+ {!this.props.fitToBox ? <DocumentView {...this.props}
+ dragDivName={"collectionFreeFormDocumentView-container"}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
backgroundColor={this.clusterColorFunc}
PanelWidth={this.finalPanelWidth}
PanelHeight={this.finalPanelHeight}
- />
+ /> : <ContentFittingDocumentView {...this.props}
+ DataDocument={this.props.DataDoc}
+ getTransform={this.getTransform}
+ active={returnFalse}
+ focus={(doc: Doc) => this.props.focus(doc, false)}
+ PanelWidth={this.finalPanelWidth}
+ PanelHeight={this.finalPanelHeight}
+ />}
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index fda6d64f4..40674b034 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -38,7 +38,9 @@ export class ColorBox extends DocExtendableComponent<FieldViewProps, ColorDocume
render() {
return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
- onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
+ onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
+ style={{ transformOrigin: "top left", transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+
<SketchPicker color={this._startupColor} onChange={InkingControl.Instance.switchColor} />
</div>;
}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.scss b/src/client/views/nodes/ContentFittingDocumentView.scss
new file mode 100644
index 000000000..2801af441
--- /dev/null
+++ b/src/client/views/nodes/ContentFittingDocumentView.scss
@@ -0,0 +1,24 @@
+@import "../globalCssVariables";
+
+.contentFittingDocumentView {
+ position: relative;
+ display: flex;
+ align-items: center;
+
+ .contentFittingDocumentView-previewDoc {
+ position: relative;
+ display: inline;
+ }
+
+ .contentFittingDocumentView-input {
+ position: absolute;
+ max-width: 150px;
+ width: 100%;
+ bottom: 0px;
+ }
+
+ .documentView-node:first-child {
+ position: relative;
+ background: $light-color;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
new file mode 100644
index 000000000..e97445f27
--- /dev/null
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -0,0 +1,126 @@
+import React = require("react");
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import "react-table/react-table.css";
+import { Doc } from "../../../new_fields/Doc";
+import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnOne } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
+import { Transform } from "../../util/Transform";
+import { undoBatch } from "../../util/UndoManager";
+import '../DocumentDecorations.scss';
+import { DocumentView } from "../nodes/DocumentView";
+import "./ContentFittingDocumentView.scss";
+import { CollectionView } from "../collections/CollectionView";
+import { TraceMobx } from "../../../new_fields/util";
+
+interface ContentFittingDocumentViewProps {
+ Document?: Doc;
+ DataDocument?: Doc;
+ LibraryPath: Doc[];
+ childDocs?: Doc[];
+ renderDepth: number;
+ fitToBox?: boolean;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ ruleProvider: Doc | undefined;
+ focus?: (doc: Doc) => void;
+ showOverlays?: (doc: Doc) => { title?: string, caption?: string };
+ CollectionView?: CollectionView;
+ CollectionDoc?: Doc;
+ onClick?: ScriptField;
+ getTransform: () => Transform;
+ addDocument?: (document: Doc) => boolean;
+ moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean;
+ removeDocument?: (document: Doc) => boolean;
+ active: (outsideReaction: boolean) => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+ addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
+ pinToPres: (document: Doc) => void;
+ dontRegisterView?: boolean;
+}
+
+@observer
+export class ContentFittingDocumentView extends React.Component<ContentFittingDocumentViewProps>{
+ public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive
+ private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); }
+ private get nativeWidth() { return NumCast(this.layoutDoc?.nativeWidth, this.props.PanelWidth()); }
+ private get nativeHeight() { return NumCast(this.layoutDoc?.nativeHeight, this.props.PanelHeight()); }
+ private contentScaling = () => {
+ const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1);
+ if (wscale * this.nativeHeight > this.props.PanelHeight()) {
+ return (this.props.PanelHeight() / (this.nativeHeight || this.props.PanelHeight() || 1)) || 1;
+ }
+ return wscale || 1;
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ const docDragData = de.complete.docDragData;
+ if (docDragData) {
+ this.props.childDocs && this.props.childDocs.map(otherdoc => {
+ const target = Doc.GetProto(otherdoc);
+ target.layout = ComputedField.MakeFunction("this.image_data[0]");
+ target.layoutCustom = Doc.MakeDelegate(docDragData.draggedDocuments[0]);
+ });
+ e.stopPropagation();
+ }
+ return true;
+ }
+ private PanelWidth = () => this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth();
+ private PanelHeight = () => this.nativeHeight && (!this.props.Document || !this.props.Document.fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight();
+ private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling());
+ private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document.fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
+ private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; }
+
+ @computed get borderRounding() { return StrCast(this.props.Document?.borderRounding); }
+
+ render() {
+ TraceMobx();
+ return (<div className="contentFittingDocumentView" style={{
+ width: Math.abs(this.centeringYOffset) > 0.001 ? "auto" : this.props.PanelWidth(),
+ height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight()
+ }}>
+ {!this.props.Document || !this.props.PanelWidth ? (null) : (
+ <div className="contentFittingDocumentView-previewDoc"
+ style={{
+ transform: `translate(${this.centeringOffset}px, 0px)`,
+ borderRadius: this.borderRounding,
+ height: Math.abs(this.centeringYOffset) > 0.001 ? `${100 * this.nativeHeight / this.nativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%` : this.props.PanelHeight(),
+ width: Math.abs(this.centeringOffset) > 0.001 ? `${100 * (this.props.PanelWidth() - this.centeringOffset * 2) / this.props.PanelWidth()}%` : this.props.PanelWidth()
+ }}>
+ <DocumentView {...this.props}
+ Document={this.props.Document}
+ DataDoc={this.props.DataDocument}
+ LibraryPath={this.props.LibraryPath}
+ fitToBox={this.props.fitToBox}
+ onClick={this.props.onClick}
+ ruleProvider={this.props.ruleProvider}
+ showOverlays={this.props.showOverlays}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ moveDocument={this.props.moveDocument}
+ whenActiveChanged={this.props.whenActiveChanged}
+ ContainingCollectionView={this.props.CollectionView}
+ ContainingCollectionDoc={this.props.CollectionDoc}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ parentActive={this.props.active}
+ ScreenToLocalTransform={this.getTransform}
+ renderDepth={this.props.renderDepth}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
+ focus={this.props.focus || emptyFunction}
+ backgroundColor={returnEmptyString}
+ bringToFront={emptyFunction}
+ dontRegisterView={this.props.dontRegisterView}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}
+ />
+ </div>)}
+ </div>);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index d73407903..0d4d50c59 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -1,17 +1,18 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { makeInterface } from "../../../new_fields/Schema";
import { NumCast, StrCast, Cast } from "../../../new_fields/Types";
import { Utils } from '../../../Utils';
import { DocumentManager } from "../../util/DocumentManager";
-import { DragLinksAsDocuments } from "../../util/DragManager";
+import { DragManager } from "../../util/DragManager";
import { DocComponent } from "../DocComponent";
import "./DocuLinkBox.scss";
import { FieldView, FieldViewProps } from "./FieldView";
import React = require("react");
import { DocumentType } from "../../documents/DocumentTypes";
import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
type DocLinkSchema = makeInterface<[typeof documentSchema]>;
const DocLinkDocument = makeInterface(documentSchema);
@@ -36,14 +37,14 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
(e.button === 0 && !e.ctrlKey) && e.stopPropagation();
}
onPointerMove = action((e: PointerEvent) => {
- let cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
+ const cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
if (cdiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) {
- let bounds = cdiv.getBoundingClientRect();
- let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
- let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
- let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy));
+ const bounds = cdiv.getBoundingClientRect();
+ const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
+ const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY));
+ const dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy));
if (separation > 100) {
- DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack.
+ DragManager.StartLinkTargetsDrag(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, [this.props.Document]); // Containging collection is the document, not a collection... hack.
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
} else if (dragdist > separation) {
@@ -67,18 +68,18 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
}
render() {
- let anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc);
- let hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO;
- let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
- let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
- let c = StrCast(this.props.Document.backgroundColor, "lightblue");
- let anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
- let timecode = this.props.Document[anchor + "Timecode"];
- let targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
+ const x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
+ const y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
+ const c = StrCast(this.props.Document.backgroundColor, "lightblue");
+ const anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
+ const anchorScale = (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .15;
+
+ const timecode = this.props.Document[anchor + "Timecode"];
+ const targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle}
ref={this._ref} style={{
background: c, left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
- transform: `scale(${hasAnchor ? 0.333 : 1 / this.props.ContentScaling()})`
+ transform: `scale(${anchorScale / this.props.ContentScaling()})`
}} />;
}
}
diff --git a/src/client/views/nodes/DocumentBox.scss b/src/client/views/nodes/DocumentBox.scss
new file mode 100644
index 000000000..b7d06b364
--- /dev/null
+++ b/src/client/views/nodes/DocumentBox.scss
@@ -0,0 +1,15 @@
+.documentBox-container {
+ width: 100%;
+ height: 100%;
+ pointer-events: all;
+ background: gray;
+ border: #00000021 solid 15px;
+ border-top: #0000005e inset 15px;
+ border-bottom: #0000005e outset 15px;
+ .documentBox-lock {
+ margin: auto;
+ color: white;
+ margin-top: -15px;
+ position: absolute;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentBox.tsx b/src/client/views/nodes/DocumentBox.tsx
new file mode 100644
index 000000000..863ea748b
--- /dev/null
+++ b/src/client/views/nodes/DocumentBox.tsx
@@ -0,0 +1,112 @@
+import { IReactionDisposer, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, Field } from "../../../new_fields/Doc";
+import { documentSchema } from "../../../new_fields/documentSchemas";
+import { List } from "../../../new_fields/List";
+import { makeInterface } from "../../../new_fields/Schema";
+import { ComputedField } from "../../../new_fields/ScriptField";
+import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { emptyFunction, emptyPath } from "../../../Utils";
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { DocComponent } from "../DocComponent";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import "./DocumentBox.scss";
+import { FieldView, FieldViewProps } from "./FieldView";
+import React = require("react");
+
+type DocBoxSchema = makeInterface<[typeof documentSchema]>;
+const DocBoxDocument = makeInterface(documentSchema);
+
+@observer
+export class DocumentBox extends DocComponent<FieldViewProps, DocBoxSchema>(DocBoxDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DocumentBox, fieldKey); }
+ _prevSelectionDisposer: IReactionDisposer | undefined;
+ _selections: Doc[] = [];
+ _curSelection = -1;
+ componentDidMount() {
+ this._prevSelectionDisposer = reaction(() => Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, (data) => {
+ if (data && !this._selections.includes(data)) {
+ this._selections.length = ++this._curSelection;
+ this._selections.push(data);
+ }
+ });
+ }
+ componentWillUnmount() {
+ this._prevSelectionDisposer && this._prevSelectionDisposer();
+ }
+ specificContextMenu = (e: React.MouseEvent): void => {
+ const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: (this.isSelectionLocked() ? "Show" : "Lock") + " Selection", event: () => this.toggleLockSelection, icon: "expand-arrows-alt" });
+ funcs.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
+
+ ContextMenu.Instance.addItem({ description: "DocumentBox Funcs...", subitems: funcs, icon: "asterisk" });
+ }
+ lockSelection = () => {
+ Doc.GetProto(this.props.Document)[this.props.fieldKey] = this.props.Document[this.props.fieldKey];
+ }
+ showSelection = () => {
+ Doc.GetProto(this.props.Document)[this.props.fieldKey] = ComputedField.MakeFunction("selectedDocs(this,true,[_last_])?.[0]");
+ }
+ isSelectionLocked = () => {
+ const kvpstring = Field.toKeyValueString(this.props.Document, this.props.fieldKey);
+ return !(kvpstring.startsWith("=") || kvpstring.startsWith(":="));
+ }
+ toggleLockSelection = () => {
+ !this.isSelectionLocked() ? this.lockSelection() : this.showSelection();
+ }
+ prevSelection = () => {
+ if (this._curSelection > 0) {
+ Doc.UserDoc().SelectedDocs = new List([this._selections[--this._curSelection]]);
+ }
+ }
+ nextSelection = () => {
+ if (this._curSelection < this._selections.length - 1 && this._selections.length) {
+ Doc.UserDoc().SelectedDocs = new List([this._selections[++this._curSelection]]);
+ }
+ }
+ onPointerDown = (e: React.PointerEvent) => {
+ }
+ onClick = (e: React.MouseEvent) => {
+ if (this._contRef.current!.getBoundingClientRect().top + 15 > e.clientY) this.toggleLockSelection();
+ else {
+ if (this._contRef.current!.getBoundingClientRect().left + 15 > e.clientX) this.prevSelection();
+ if (this._contRef.current!.getBoundingClientRect().right - 15 < e.clientX) this.nextSelection();
+ }
+ }
+ _contRef = React.createRef<HTMLDivElement>();
+ pwidth = () => this.props.PanelWidth() - 30;
+ pheight = () => this.props.PanelHeight() - 30;
+ getTransform = () => this.props.ScreenToLocalTransform().translate(-15, -15);
+ render() {
+ const containedDoc = this.props.Document[this.props.fieldKey] as Doc;
+ return <div className="documentBox-container" ref={this._contRef}
+ onContextMenu={this.specificContextMenu}
+ onPointerDown={this.onPointerDown} onClick={this.onClick}
+ style={{ background: StrCast(this.props.Document.backgroundColor) }}>
+ <div className="documentBox-lock">
+ <FontAwesomeIcon icon={this.isSelectionLocked() ? "lock" : "unlock"} size="sm" />
+ </div>
+ {!(containedDoc instanceof Doc) ? (null) : <ContentFittingDocumentView
+ Document={containedDoc}
+ DataDocument={undefined}
+ LibraryPath={emptyPath}
+ fitToBox={this.props.fitToBox}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ removeDocument={this.props.removeDocument}
+ ruleProvider={this.props.ruleProvider}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ getTransform={this.getTransform}
+ renderDepth={this.props.Document.forceActive ? 0 : this.props.renderDepth + 1} // bcz: really need to have an 'alwaysSelected' prop that's not conflated with renderDepth
+ PanelWidth={this.pwidth}
+ PanelHeight={this.pheight}
+ focus={this.props.focus}
+ active={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ />}
+ </div>;
+ }
+}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 60d5a16d7..aa553afb7 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -14,6 +14,7 @@ import { LinkFollowBox } from "../linking/LinkFollowBox";
import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
import { AudioBox } from "./AudioBox";
import { ButtonBox } from "./ButtonBox";
+import { DocumentBox } from "./DocumentBox";
import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FontIconBox } from "./FontIconBox";
@@ -30,13 +31,11 @@ import { DocuLinkBox } from "./DocuLinkBox";
import { PresElementBox } from "../presentationview/PresElementBox";
import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
+import { InkingStroke } from "../InkingStroke";
import React = require("react");
-import { NumCast } from "../../../new_fields/Types";
-import { List } from "../../../new_fields/List";
-import { fromPromise } from "mobx-utils";
-import { DashWebCam } from "../../views/webcam/DashWebCam";
import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
+import { TraceMobx } from "../../../new_fields/util";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -55,13 +54,14 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
@observer
export class DocumentContentsView extends React.Component<DocumentViewProps & {
- isSelected: () => boolean,
+ isSelected: (outsideReaction: boolean) => boolean,
select: (ctrl: boolean) => void,
onClick?: ScriptField,
layoutKey: string,
hideOnLeave?: boolean
}> {
@computed get layout(): string {
+ TraceMobx();
if (!this.layoutDoc) return "<p>awaiting layout</p>";
const layout = Cast(this.layoutDoc[this.props.layoutKey], "string");
if (layout === undefined) {
@@ -84,11 +84,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
return this.props.DataDoc;
}
get layoutDoc() {
- return this.props.DataDoc === undefined ? Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document) : Doc.Layout(this.props.Document);
+ return Doc.expandTemplateLayout(Doc.Layout(this.props.Document), this.props.Document);
}
CreateBindings(): JsxBindings {
- let list = {
+ const list = {
...OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit,
Document: this.layoutDoc,
DataDoc: this.dataDoc,
@@ -97,13 +97,15 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
render() {
+ TraceMobx();
return (this.props.renderDepth > 7 || !this.layout) ? (null) :
<ObserverJsxParser
blacklistedAttrs={[]}
components={{
FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DocuLinkBox, DashWebRTCVideo
+ PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
+ ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox
}}
bindings={this.CreateBindings()}
jsx={this.layout}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 65df86d27..2ce56c73d 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,13 +1,14 @@
@import "../globalCssVariables";
-.documentView-node, .documentView-node-topmost {
+.documentView-node,
+.documentView-node-topmost {
position: inherit;
top: 0;
- left:0;
+ left: 0;
border-radius: inherit;
- transition : outline .3s linear;
+ transition: outline .3s linear;
cursor: grab;
-
+
// background: $light-color; //overflow: hidden;
transform-origin: left top;
@@ -37,46 +38,62 @@
position: absolute;
transform-origin: top left;
width: 100%;
- height: 100%;
+ height: 100%;
+ z-index: 1;
}
+
.documentView-styleWrapper {
- position: absolute;
+ position: absolute;
display: inline-block;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
pointer-events: none;
.documentView-styleContentWrapper {
- width:100%;
+ width: 100%;
display: inline-block;
position: absolute;
}
- .documentView-titleWrapper {
- overflow:hidden;
- color: white;
- transform-origin: top left;
- top: 0;
+
+ .documentView-titleWrapper, .documentView-titleWrapper-hover {
+ overflow: hidden;
+ color: white;
+ transform-origin: top left;
+ top: 0;
+ width: 100%;
height: 25;
background: rgba(0, 0, 0, .4);
- padding: 4px;
- text-align: center;
- text-overflow: ellipsis;
+ padding: 4px;
+ text-align: center;
+ text-overflow: ellipsis;
white-space: pre;
}
+ .documentView-titleWrapper-hover {
+ display:none;
+ }
.documentView-searchHighlight {
- position: absolute;
- background: yellow;
+ position: absolute;
+ background: yellow;
bottom: -20px;
border-radius: 5px;
- transform-origin: bottom left;
+ transform-origin: bottom left;
}
.documentView-captionWrapper {
- position: absolute;
- bottom: 0;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
transform-origin: bottom left;
}
}
+}
+
+.documentView-node:hover, .documentView-node-topmost:hover {
+ > .documentView-styleWrapper {
+ > .documentView-titleWrapper-hover {
+ display:inline-block;
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e76a83e5e..60dc253f7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,23 +4,21 @@ import { action, computed, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import * as rp from "request-promise";
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
-import { Document } from '../../../new_fields/documentSchemas';
+import { Document, PositionDocument } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
import { listSpec } from "../../../new_fields/Schema";
import { ScriptField } from '../../../new_fields/ScriptField';
import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { ImageField } from '../../../new_fields/URLField';
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, returnTransparent, returnTrue, Utils } from "../../../Utils";
+import { emptyFunction, returnTransparent, returnTrue, Utils, returnOne } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
import { ClientUtils } from '../../util/ClientUtils';
-import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, dropActionType } from "../../util/DragManager";
-import { LinkManager } from '../../util/LinkManager';
import { Scripting } from '../../util/Scripting';
import { SelectionManager } from "../../util/SelectionManager";
import SharingManager from '../../util/SharingManager';
@@ -31,7 +29,6 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
-import { DictationOverlay } from '../DictationOverlay';
import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
import { OverlayView } from '../OverlayView';
@@ -41,6 +38,13 @@ import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import { FormattedTextBox } from './FormattedTextBox';
import React = require("react");
+import { InteractionUtils } from '../../util/InteractionUtils';
+import { InkingControl } from '../InkingControl';
+import { InkTool } from '../../../new_fields/InkField';
+import { TraceMobx } from '../../../new_fields/util';
+import { List } from '../../../new_fields/List';
+import { FormattedTextBoxComment } from './FormattedTextBoxComment';
+import { CollectionStackingView } from '../collections/CollectionStackingView';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -51,29 +55,32 @@ export interface DocumentViewProps {
ContainingCollectionDoc: Opt<Doc>;
Document: Doc;
DataDoc?: Doc;
+ LibraryPath: Doc[];
fitToBox?: boolean;
onClick?: ScriptField;
+ dragDivName?: string;
addDocument?: (doc: Doc) => boolean;
removeDocument?: (doc: Doc) => boolean;
- moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
renderDepth: number;
- showOverlays?: (doc: Doc) => { title?: string, caption?: string };
+ showOverlays?: (doc: Doc) => { title?: string, titleHover?: string, caption?: string };
ContentScaling: () => number;
ruleProvider: Doc | undefined;
PanelWidth: () => number;
PanelHeight: () => number;
focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
- parentActive: () => boolean;
+ parentActive: (outsideReaction: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
- addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean;
+ addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string, libraryPath?: Doc[]) => boolean;
pinToPres: (document: Doc) => void;
zoomToScale: (scale: number) => void;
backgroundColor: (doc: Doc) => string | undefined;
getScale: () => number;
animateBetweenIcon?: (maximize: boolean, target: number[]) => void;
ChromeHeight?: () => number;
+ dontRegisterView?: boolean;
layoutKey?: string;
}
@@ -87,9 +94,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _hitTemplateDrag = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
+ private _titleRef = React.createRef<EditableView>();
+ public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
- @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
+ @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@computed get topMost() { return this.props.renderDepth === 0; }
@computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; }
@computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; }
@@ -97,37 +106,74 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
componentDidMount() {
- this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));
- DocumentManager.Instance.DocumentViews.push(this);
+ this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
+
+ !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
}
@action
componentDidUpdate() {
this._dropDisposer && this._dropDisposer();
- this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));
+ this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
}
@action
componentWillUnmount() {
this._dropDisposer && this._dropDisposer();
Doc.UnBrushDoc(this.props.Document);
- DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+ !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
}
startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) {
if (this._mainCont.current) {
- let dragData = new DragManager.DocumentDragData([this.props.Document]);
+ const dragData = new DragManager.DocumentDragData([this.props.Document]);
const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
dragData.offset = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.dropAction = dropAction;
- dragData.moveDocument = this.Document.onDragStart ? undefined : this.props.moveDocument;
+ dragData.moveDocument = this.props.moveDocument;// this.Document.onDragStart ? undefined : this.props.moveDocument;
dragData.applyAsTemplate = applyAsTemplate;
- DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
- handlers: {
- dragComplete: action((emptyFunction))
- },
- hideSource: !dropAction && !this.Document.onDragStart
- });
+ dragData.dragDivName = this.props.dragDivName;
+ DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart });
+ }
+ }
+
+ public static FloatDoc(topDocView: DocumentView, x: number, y: number) {
+ const topDoc = topDocView.props.Document;
+ const de = new DragManager.DocumentDragData([topDoc]);
+ de.dragDivName = topDocView.props.dragDivName;
+ de.moveDocument = topDocView.props.moveDocument;
+ undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
+ setTimeout(() => {
+ const newDocView = DocumentManager.Instance.getDocumentView(topDoc);
+ if (newDocView) {
+ const contentDiv = newDocView.ContentDiv!;
+ const xf = contentDiv.getBoundingClientRect();
+ DragManager.StartDocumentDrag([contentDiv], de, x, y, { offsetX: x - xf.left, offsetY: y - xf.top, hideSource: true });
+ }
+ }, 0);
+ }
+
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.altKey && !(e.nativeEvent as any).StopPropagationForReal) {
+ (e.nativeEvent as any).StopPropagationForReal = true; // e.stopPropagation() doesn't seem to work...
+ e.stopPropagation();
+ e.preventDefault();
+ if (e.key === "†" || e.key === "t") {
+ if (!StrCast(this.layoutDoc.showTitle)) this.layoutDoc.showTitle = "title";
+ if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0);
+ else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text...
+ {
+ this._titleRef.current?.setIsFocused(false);
+ const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any);
+ any.keeplocation = true;
+ any?.focus();
+ }
+ }
+ } else if (e.key === "f") {
+ const ex = (e.nativeEvent.target! as any).getBoundingClientRect().left;
+ const ey = (e.nativeEvent.target! as any).getBoundingClientRect().top;
+ DocumentView.FloatDoc(this, ex, ey);
+ }
}
}
@@ -136,8 +182,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
e.stopPropagation();
let preventDefault = true;
- if (this._doubleTap && this.props.renderDepth && (!this.onClickHandler || !this.onClickHandler.script)) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
- let fullScreenAlias = Doc.MakeAlias(this.props.Document);
+ if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ const fullScreenAlias = Doc.MakeAlias(this.props.Document);
if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) {
fullScreenAlias.layoutKey = "layoutCustom";
}
@@ -148,6 +194,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.onClickHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
} else if (this.Document.type === DocumentType.BUTTON) {
ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY);
+ } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script
+ FormattedTextBoxComment.Hide();
+ this.Document.links?.[0] instanceof Doc && (Doc.UserDoc().SelectedDocs = new List([Doc.LinkOtherAnchor(this.Document.links[0], this.props.Document)]));
} else if (this.Document.isButton) {
SelectionManager.SelectDoc(this, e.ctrlKey); // don't think this should happen if a button action is actually triggered.
this.buttonClick(e.altKey, e.ctrlKey);
@@ -160,9 +209,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
buttonClick = async (altKey: boolean, ctrlKey: boolean) => {
- let maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs);
- let summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs);
- let linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document);
+ const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs);
+ const summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs);
+ const linkDocs = DocListCast(this.props.Document.links);
let expandedDocs: Doc[] = [];
expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
@@ -173,7 +222,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab"));
if (maxLocation === "inPlace") {
expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc));
- let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2);
+ const scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2);
DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs);
} else {
expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation)));
@@ -187,33 +236,176 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
+ handle1PointerDown = (e: React.TouchEvent) => {
+ if (!e.nativeEvent.cancelBubble) {
+ const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+ this._downX = touch.clientX;
+ this._downY = touch.clientY;
+ this._hitTemplateDrag = false;
+ for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+ if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+ this._hitTemplateDrag = true;
+ }
+ }
+ if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation();
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ if ((e.nativeEvent as any).formattedHandled) e.stopPropagation();
+ }
+ }
+
+ handle1PointerMove = (e: TouchEvent) => {
+ if ((e as any).formattedHandled) { e.stopPropagation; return; }
+ if (e.cancelBubble && this.active) {
+ document.removeEventListener("touchmove", this.onTouch);
+ }
+ else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+ const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+ if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
+ if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+ }
+ }
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
+
+ }
+ }
+
+ handle2PointersDown = (e: React.TouchEvent) => {
+ if (!e.nativeEvent.cancelBubble && !this.isSelected()) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ }
+ }
+
+ @action
+ handle2PointersMove = (e: TouchEvent) => {
+ const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const pt1 = myTouches[0];
+ const pt2 = myTouches[1];
+ const oldPoint1 = this.prevPoints.get(pt1.identifier);
+ const oldPoint2 = this.prevPoints.get(pt2.identifier);
+ const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
+ if (pinching !== 0 && oldPoint1 && oldPoint2) {
+ // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
+ // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
+ // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
+ // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
+ // let dW = -dX;
+ // let dH = -dY;
+ const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
+ const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
+ const dX = -1 * Math.sign(dW);
+ const dY = -1 * Math.sign(dH);
+
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+ const doc = PositionDocument(this.props.Document);
+ const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
+ let nwidth = layoutDoc.nativeWidth || 0;
+ let nheight = layoutDoc.nativeHeight || 0;
+ const width = (layoutDoc.width || 0);
+ const height = (layoutDoc.height || (nheight / nwidth * width));
+ const scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling();
+ const actualdW = Math.max(width + (dW * scale), 20);
+ const actualdH = Math.max(height + (dH * scale), 20);
+ doc.x = (doc.x || 0) + dX * (actualdW - width);
+ doc.y = (doc.y || 0) + dY * (actualdH - height);
+ const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
+ if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
+ layoutDoc.ignoreAspect = false;
+ layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+ layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+ }
+ if (fixedAspect && (!nwidth || !nheight)) {
+ layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+ layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+ }
+ if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
+ if (Math.abs(dW) > Math.abs(dH)) {
+ if (!fixedAspect) {
+ layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
+ }
+ layoutDoc.width = actualdW;
+ if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
+ else layoutDoc.height = actualdH;
+ }
+ else {
+ if (!fixedAspect) {
+ layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
+ }
+ layoutDoc.height = actualdH;
+ if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
+ else layoutDoc.width = actualdW;
+ }
+ } else {
+ dW && (layoutDoc.width = actualdW);
+ dH && (layoutDoc.height = actualdH);
+ dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
+ }
+ }
+ // let newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX))
+ // this.props.Document.width = newWidth;
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
onPointerDown = (e: React.PointerEvent): void => {
- if (e.nativeEvent.cancelBubble && e.button === 0) return;
- this._downX = e.clientX;
- this._downY = e.clientY;
- this._hitTemplateDrag = false;
- // this whole section needs to move somewhere else. We're trying to initiate a special "template" drag where
- // this document is the template and we apply it to whatever we drop it on.
- for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
- if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
- this._hitTemplateDrag = true;
+ // console.log(e.button)
+ // console.log(e.nativeEvent)
+ // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
+ if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) {
+ if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ e.stopPropagation();
}
+ return;
+ }
+ if ((!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart)) {
+ // if ((e.nativeEvent.cancelBubble && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)))
+ // // return if we're inking, and not selecting a button document
+ // || (InkingControl.Instance.selectedTool !== InkTool.None && !this.Document.onClick)
+ // // return if using pen or eraser
+ // || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) {
+ // return;
+ // }
+
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ this._hitTemplateDrag = false;
+ // this whole section needs to move somewhere else. We're trying to initiate a special "template" drag where
+ // this document is the template and we apply it to whatever we drop it on.
+ for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+ if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+ this._hitTemplateDrag = true;
+ }
+ }
+ if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
}
- if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && e.button === 0 && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
}
+
onPointerMove = (e: PointerEvent): void => {
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
if (e.cancelBubble && this.active) {
document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
}
- else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive() || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+ else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && e.buttons === 1) {
+ if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
@@ -223,6 +415,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.preventDefault();
}
}
+
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -266,7 +459,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
fieldTemplate.heading = 1;
fieldTemplate.autoHeight = true;
- let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) });
+ const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) });
Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true);
Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, "layoutCustom", undefined);
@@ -288,34 +481,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@undoBatch
+ makeSelBtnClicked = (): void => {
+ if (this.Document.isButton || this.Document.onClick || this.Document.ignoreClick) {
+ this.Document.isButton = false;
+ this.Document.ignoreClick = false;
+ this.Document.onClick = undefined;
+ } else {
+ this.props.Document.isButton = "Selector";
+ }
+ }
+
+ @undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.AnnotationDragData) {
+ if (de.complete.annoDragData) {
/// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner
e.stopPropagation();
- (de.data as any).linkedToDoc = true;
+ de.complete.annoDragData.linkedToDoc = true;
- DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`);
+ DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc },
+ `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`);
}
- if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
- Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, "layoutCustom");
+ if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) {
+ Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layoutCustom");
e.stopPropagation();
}
- if (de.data instanceof DragManager.LinkDragData) {
+ if (de.complete.linkDragData) {
e.stopPropagation();
// const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
// const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
- de.data.linkSourceDocument !== this.props.Document &&
- (de.data.linkDocument = DocUtils.MakeLink({ doc: de.data.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
+ de.complete.linkDragData.linkSourceDocument !== this.props.Document &&
+ (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed
}
}
@action
onDrop = (e: React.DragEvent) => {
- let text = e.dataTransfer.getData("text/plain");
+ const text = e.dataTransfer.getData("text/plain");
if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
- let oldLayout = this.Document.layout || "";
- let layout = text.replace("{layout}", oldLayout);
+ const oldLayout = this.Document.layout || "";
+ const layout = text.replace("{layout}", oldLayout);
this.Document.layout = layout;
e.stopPropagation();
e.preventDefault();
@@ -336,11 +541,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
makeIntoPortal = async () => {
- let anchors = await Promise.all(DocListCast(this.Document.links).map(async (d: Doc) => Cast(d.anchor2, Doc)));
+ const anchors = await Promise.all(DocListCast(this.Document.links).map(async (d: Doc) => Cast(d.anchor2, Doc)));
if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) {
- let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
+ const portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
DocServer.GetRefField(portalID).then(existingPortal => {
- let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID });
+ const portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID });
DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link");
this.Document.isButton = true;
});
@@ -349,10 +554,24 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
+ setNarrativeView = (custom: boolean): void => {
+ if (custom) {
+ this.props.Document.layout_narrative = CollectionView.LayoutString("narrative");
+ this.props.Document.nativeWidth = this.props.Document.nativeHeight = undefined;
+ !this.props.Document.narrative && (Doc.GetProto(this.props.Document).narrative = new List<Doc>([]));
+ this.props.Document.viewType = CollectionViewType.Stacking;
+ this.props.Document.layoutKey = "layout_narrative";
+ } else {
+ DocumentView.makeNativeViewClicked(this.props.Document)
+ }
+ }
+
+ @undoBatch
+ @action
setCustomView = (custom: boolean): void => {
- if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) {
- Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc);
- } else { // bcz: not robust -- for now documents with string layout are native documents, and those with Doc layouts are customized
+ if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document);
+ } else {
custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document);
}
}
@@ -370,19 +589,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
}
- listen = async () => {
- Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({
- continuous: { indefinite: true },
- interimHandler: (results: string) => {
- DictationOverlay.Instance.dictationSuccess = true;
- DictationOverlay.Instance.dictatedPhrase = results;
- DictationOverlay.Instance.isListening = { interim: true };
- }
- });
+ @undoBatch
+ @action
+ toggleLockTransform = (): void => {
+ this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true;
}
@action
onContextMenu = async (e: React.MouseEvent): Promise<void> => {
+ // the touch onContextMenu is button 0, the pointer onContextMenu is button 2
+ if (e.button === 0 && !e.ctrlKey) {
+ e.preventDefault();
+ return;
+ }
e.persist();
e.stopPropagation();
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
@@ -393,47 +612,36 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.preventDefault();
const cm = ContextMenu.Instance;
- let subitems: ContextMenuProps[] = [];
- subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this), icon: "desktop" });
- subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab"), icon: "folder" });
- subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight"), icon: "caret-square-right" });
+ const subitems: ContextMenuProps[] = [];
+ subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this, this.props.LibraryPath), icon: "desktop" });
+ subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab", this.props.LibraryPath), icon: "folder" });
+ subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" });
subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" });
subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" });
subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });
cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
- if (Cast(this.props.Document.data, ImageField)) {
- cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
- }
- if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- cm.addItem({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
- cm.addItem({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
- cm.addItem({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
- }
- let existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
- let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ const existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" });
onClicks.push({ description: "Toggle Detail", event: () => this.Document.onClick = ScriptField.MakeScript("toggleDetail(this)"), icon: "window-restore" });
onClicks.push({ description: this.Document.ignoreClick ? "Select" : "Do Nothing", event: () => this.Document.ignoreClick = !this.Document.ignoreClick, icon: this.Document.ignoreClick ? "unlock" : "lock" });
onClicks.push({ description: this.Document.isButton || this.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, icon: "concierge-bell" });
+ onClicks.push({ description: this.props.Document.isButton ? "Remove Select Link Behavior" : "Select Link", event: this.makeSelBtnClicked, icon: "concierge-bell" });
onClicks.push({ description: "Edit onClick Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", obj.x, obj.y) });
- onClicks.push({
- description: "Edit onClick Foreach Doc Script", icon: "edit", event: (obj: any) => {
- this.props.Document.collectionContext = this.props.ContainingCollectionDoc;
- ScriptBox.EditButtonScript("Foreach Collection Doc (d) => ", this.props.Document, "onClick", obj.x, obj.y, "docList(this.collectionContext.data).map(d => {", "});\n");
- }
- });
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
- let funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
- funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
- funcs.push({ description: "Drag Document", icon: "edit", event: () => this.Document.onDragStart = undefined });
- ContextMenu.Instance.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+ const funcs: ContextMenuProps[] = [];
+ if (this.Document.onDragStart) {
+ funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
+ funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
+ funcs.push({ description: "Drag Document", icon: "edit", event: () => this.Document.onDragStart = undefined });
+ ContextMenu.Instance.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+ }
- let existing = ContextMenu.Instance.findByDescription("Layout...");
- let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
+ const existing = ContextMenu.Instance.findByDescription("Layout...");
+ const layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
layoutItems.push({ description: this.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.Document.lockedPosition ? "unlock" : "lock" });
if (this.props.DataDoc) {
layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" });
@@ -442,6 +650,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" });
layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+ layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) {
@@ -450,18 +659,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
layoutItems.push({ description: "Use Native Layout", event: () => DocumentView.makeNativeViewClicked(this.props.Document), icon: "concierge-bell" });
}
!existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
+
+ const more = ContextMenu.Instance.findByDescription("More...");
+ const moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
+
if (!ClientUtils.RELEASE) {
// let copies: ContextMenuProps[] = [];
- cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
// cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
}
- let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
- let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
- analyzers.push({ description: "Transcribe Speech", event: this.listen, icon: "microphone" });
- !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" });
- cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
- cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
- cm.addItem({
+ if (Cast(this.props.Document.data, ImageField)) {
+ moreItems.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+ }
+ if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
+ moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
+ moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
+ }
+ moreItems.push({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
+ moreItems.push({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
+ moreItems.push({
description: "Download document", icon: "download", event: async () =>
console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
@@ -473,11 +690,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// a.click();
});
- cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
- cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+ moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
+ moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+ moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+ !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
runInAction(() => {
if (!ClientUtils.RELEASE) {
- let setWriteMode = (mode: DocServer.WriteMode) => {
+ const setWriteMode = (mode: DocServer.WriteMode) => {
DocServer.AclsMode = mode;
const mode1 = mode;
const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
@@ -491,13 +710,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
DocServer.setFieldWriteMode("scale", mode2);
DocServer.setFieldWriteMode("viewType", mode2);
};
- let aclsMenu: ContextMenuProps[] = [];
+ const aclsMenu: ContextMenuProps[] = [];
aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" });
aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" });
aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" });
- cm.addItem({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
}
});
runInAction(() => {
@@ -512,33 +730,45 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.stopPropagation();
}
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- if (!SelectionManager.IsSelected(this)) {
+ if (!SelectionManager.IsSelected(this, true)) {
SelectionManager.SelectDoc(this, false);
}
});
+ const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc().LibraryBtn as Doc).sourcePanel as Doc) ? "" : d.title), "");
+ cm.addItem({
+ description: `path: ${path}`, event: () => {
+ this.props.LibraryPath.map(lp => Doc.GetProto(lp).treeViewOpen = lp.treeViewOpen = true);
+ Doc.linkFollowHighlight(this.props.Document);
+ }, icon: "check"
+ });
}
// does Document set a layout prop
- setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)];
+ setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)] && this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)];
// get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise.
getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
- isSelected = () => SelectionManager.IsSelected(this);
+ isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
chromeHeight = () => {
- let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
- let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.Document.showTitle);
- return (showTitle ? 25 : 0) + 1;
+ const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
+ const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
+ const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.layoutDoc.showTitleHover);
+ return (showTitle && !showTitleHover ? 0 : 0) + 1;
}
+ @computed get finalLayoutKey() { return this.props.layoutKey || StrCast(this.props.Document.layoutKey, "layout"); }
childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
@computed get contents() {
+ TraceMobx();
return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
Document={this.props.Document}
+ DataDoc={this.props.DataDoc}
fitToBox={this.props.fitToBox}
+ LibraryPath={this.props.LibraryPath}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
moveDocument={this.props.moveDocument}
@@ -563,47 +793,32 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
isSelected={this.isSelected}
select={this.select}
onClick={this.onClickHandler}
- layoutKey={this.props.layoutKey || "layout"}
- DataDoc={this.props.DataDoc} />);
+ layoutKey={this.finalLayoutKey} />);
}
linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
- // used to decide whether a link document should be created or not.
+ // used to decide whether a link document should be created or not.
// if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
// would be good to generalize this some way.
isNonTemporalLink = (linkDoc: Doc) => {
- let anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
- let ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode;
+ const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
+ const ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode;
return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
}
- render() {
- if (!this.props.Document) return (null);
- // trace();
- const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
- const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
- const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
- const colorSet = this.setsLayoutProp("backgroundColor");
- const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
- const backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ?
- this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
- ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
-
- const nativeWidth = this.layoutDoc.fitWidth ? this.props.PanelWidth() - 2 : this.nativeWidth > 0 && !this.layoutDoc.ignoreAspect ? `${this.nativeWidth}px` : "100%";
- const nativeHeight = this.layoutDoc.fitWidth ? this.props.PanelHeight() - 2 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
+ @computed get innards() {
+ TraceMobx();
const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
- const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle");
+ const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.getLayoutPropStr("showTitle"));
+ const showTitleHover = showOverlays && "titleHover" in showOverlays ? showOverlays.titleHover : StrCast(this.getLayoutPropStr("showTitleHover"));
const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
- const showTextTitle = showTitle && StrCast(this.Document.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined;
- const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
- const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
- const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
+ const showTextTitle = showTitle && StrCast(this.layoutDoc.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined;
const searchHighlight = (!this.Document.searchFields ? (null) :
- <div className="documentView-searchHighlight" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
+ <div className="documentView-searchHighlight">
{this.Document.searchFields}
</div>);
const captionView = (!showCaption ? (null) :
- <div className="documentView-captionWrapper" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
+ <div className="documentView-captionWrapper">
<FormattedTextBox {...this.props}
onClick={this.onClickHandler} DataDoc={this.props.DataDoc} active={returnTrue}
isSelected={this.isSelected} focus={emptyFunction} select={this.select}
@@ -611,44 +826,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
/>
</div>);
const titleView = (!showTitle ? (null) :
- <div className="documentView-titleWrapper" style={{
- width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})`,
+ <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} style={{
position: showTextTitle ? "relative" : "absolute",
pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all",
}}>
- <EditableView
- contents={this.Document[showTitle]}
+ <EditableView ref={this._titleRef}
+ contents={(this.props.DataDoc || this.props.Document)[showTitle]}
display={"block"} height={72} fontSize={12}
- GetValue={() => StrCast(this.Document[showTitle])}
- SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true}
+ GetValue={() => StrCast((this.props.DataDoc || this.props.Document)[showTitle])}
+ SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)}
/>
</div>);
- let animheight = animDims ? animDims[1] : nativeHeight;
- let animwidth = animDims ? animDims[0] : nativeWidth;
-
- const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
- const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
- let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
- return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
- ref={this._mainCont}
- style={{
- transition: this.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
- pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
- color: StrCast(this.Document.color),
- outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
- border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
- background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
- width: animwidth,
- height: animheight,
- transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`,
- opacity: this.Document.opacity
- }}
- onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
- onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
- >
- {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) =>
- <div className="documentView-docuLinkWrapper" key={`${d[Id]}`} style={{ transform: `scale(${this.layoutDoc.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
- <DocumentView {...this.props} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} />
+ return <>
+ {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
+ <div className="documentView-docuLinkWrapper" key={`${d[Id]}`}>
+ <DocumentView {...this.props} ContentScaling={returnOne} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} />
</div>)}
{!showTitle && !showCaption ?
this.Document.searchFields ?
@@ -668,6 +860,49 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
{searchHighlight}
</div>
}
+ </>;
+ }
+ @computed get ignorePointerEvents() {
+ return (this.Document.isBackground && !this.isSelected()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+ }
+
+ render() {
+ if (!(this.props.Document instanceof Doc)) return (null);
+ const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
+ const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
+ const colorSet = this.setsLayoutProp("backgroundColor");
+ const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
+ const backgroundColor = (clusterCol && !colorSet) ?
+ this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
+ ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
+
+ const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
+ const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
+ const localScale = fullDegree;
+
+ const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
+ const animheight = animDims ? animDims[1] : "100%";
+ const animwidth = animDims ? animDims[0] : "100%";
+
+ const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
+ const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
+ let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
+ highlighting = highlighting && this.props.focus !== emptyFunction; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way
+ return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
+ onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
+ onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
+ style={{
+ transition: this.Document.isAnimating ? ".5s linear" : StrCast(this.Document.transition),
+ pointerEvents: this.ignorePointerEvents ? "none" : "all",
+ color: StrCast(this.Document.color),
+ outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
+ border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
+ background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
+ width: animwidth,
+ height: animheight,
+ opacity: this.Document.opacity
+ }} onTouchStart={this.onTouchStart}>
+ {this.innards}
</div>;
}
}
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
index 887efc0d5..20afa4565 100644
--- a/src/client/views/nodes/FaceRectangle.tsx
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -12,7 +12,7 @@ export default class FaceRectangle extends React.Component<{ rectangle: Rectangl
}
render() {
- let rectangle = this.props.rectangle;
+ const rectangle = this.props.rectangle;
return (
<div
style={{
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
index acf1aced3..3c7f1f206 100644
--- a/src/client/views/nodes/FaceRectangles.tsx
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -20,10 +20,10 @@ export interface RectangleTemplate {
export default class FaceRectangles extends React.Component<FaceRectanglesProps> {
render() {
- let faces = DocListCast(this.props.document.faces);
- let templates: RectangleTemplate[] = faces.map(faceDoc => {
- let rectangle = Cast(faceDoc.faceRectangle, Doc) as Doc;
- let style = {
+ const faces = DocListCast(this.props.document.faces);
+ const templates: RectangleTemplate[] = faces.map(faceDoc => {
+ const rectangle = Cast(faceDoc.faceRectangle, Doc) as Doc;
+ const style = {
top: NumCast(rectangle.top),
left: NumCast(rectangle.left),
width: NumCast(rectangle.width),
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5108954bb..c56fde186 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -30,17 +30,18 @@ export interface FieldViewProps {
ruleProvider: Doc | undefined;
Document: Doc;
DataDoc?: Doc;
+ LibraryPath: Doc[];
onClick?: ScriptField;
- isSelected: () => boolean;
+ isSelected: (outsideReaction?: boolean) => boolean;
select: (isCtrlPressed: boolean) => void;
renderDepth: number;
addDocument?: (document: Doc) => boolean;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
removeDocument?: (document: Doc) => boolean;
- moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
+ moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
- active: () => boolean;
+ active: (outsideReaction?: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
focus: (doc: Doc) => void;
PanelWidth: () => number;
@@ -53,7 +54,7 @@ export interface FieldViewProps {
@observer
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string) {
- return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`; //e.g., "<ImageBox {...props} fieldKey={"dada} />"
+ return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"dada} />"
}
@computed
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index 75d093fcb..f0fe7a54e 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -2,12 +2,14 @@
width: 100%;
height: 100%;
pointer-events: all;
+ touch-action: none;
border-radius: inherit;
background: black;
border-radius: 100%;
+ transform-origin: top left;
+
svg {
- margin:18%;
- width:65% !important;
- height:65%;
+ width: 95% !important;
+ height: 95%;
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index ae9273639..2433251b3 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -25,8 +25,8 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
this._backgroundReaction = reaction(() => this.props.Document.backgroundColor,
() => {
if (this._ref && this._ref.current) {
- let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor!);
- let colsum = (col.r + col.g + col.b);
+ const col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor);
+ const colsum = (col.r + col.g + col.b);
if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black");
else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white");
}
@@ -36,10 +36,13 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
this._backgroundReaction && this._backgroundReaction();
}
render() {
- let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document);
- let referenceLayout = Doc.Layout(referenceDoc);
+ const referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document);
+ const referenceLayout = Doc.Layout(referenceDoc);
return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref}
- style={{ background: StrCast(referenceLayout.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
+ style={{
+ background: StrCast(referenceLayout.backgroundColor),
+ boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined
+ }}>
<FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
</button>;
}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index a4acd3b82..c203ca0c3 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -11,6 +11,7 @@
}
.formattedTextBox-cont {
+ touch-action: none;
cursor: text;
background: inherit;
padding: 0;
@@ -25,17 +26,64 @@
color: initial;
height: 100%;
pointer-events: all;
- overflow-y: auto;
max-height: 100%;
+ display: flex;
+ flex-direction: row;
+
.formattedTextBox-dictation {
- height: 20px;
- width: 20px;
+ height: 12px;
+ width: 10px;
top: 0px;
left: 0px;
position: absolute;
}
}
+.collectionfreeformview-container {
+ position: relative;
+}
+
+.formattedTextBox-outer {
+ position: relative;
+ overflow: auto;
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+}
+
+.formattedTextBox-sidebar-handle {
+ position: absolute;
+ top: calc(50% - 17.5px);
+ width: 10px;
+ height: 35px;
+ background: lightgray;
+ border-radius: 20px;
+ cursor:grabbing;
+}
+
+.formattedTextBox-cont>.formattedTextBox-sidebar-handle {
+ right: 0;
+ left: unset;
+}
+
+.formattedTextBox-sidebar,
+.formattedTextBox-sidebar-inking {
+ border-left: dashed 1px black;
+ height: 100%;
+ display: inline-block;
+ position: absolute;
+ right: 0;
+
+ >.formattedTextBox-sidebar-handle {
+ right: unset;
+ left: -5;
+ }
+}
+
+.formattedTextBox-sidebar-inking {
+ pointer-events: all;
+}
+
.formattedTextBox-inner-rounded {
height: 70%;
width: 85%;
@@ -45,23 +93,23 @@
left: 10%;
}
-.formattedTextBox-inner-rounded ,
-.formattedTextBox-inner {
+.formattedTextBox-inner-rounded,
+.formattedTextBox-inner {
padding: 10px 10px;
height: 100%;
}
-.menuicon {
- display: inline-block;
- border-right: 1px solid rgba(0, 0, 0, 0.2);
- color: #888;
- line-height: 1;
- padding: 0 7px;
- margin: 1px;
- cursor: pointer;
- text-align: center;
- min-width: 1.4em;
-}
+// .menuicon {
+// display: inline-block;
+// border-right: 1px solid rgba(0, 0, 0, 0.2);
+// color: #888;
+// line-height: 1;
+// padding: 0 7px;
+// margin: 1px;
+// cursor: pointer;
+// text-align: center;
+// min-width: 1.4em;
+// }
.strong,
.heading {
@@ -73,19 +121,20 @@
}
.userMarkOpen {
- background: rgba(255, 255, 0, 0.267);
+ background: rgba(255, 255, 0, 0.267);
display: inline;
}
+
.userMark {
- background: rgba(255, 255, 0, 0.267);
+ background: rgba(255, 255, 0, 0.267);
font-size: 2px;
display: inline-grid;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- width:10px;
- min-height:10px;
- text-align:center;
+ width: 10px;
+ min-height: 10px;
+ text-align: center;
align-content: center;
}
@@ -93,8 +142,9 @@ footnote {
display: inline-block;
position: relative;
cursor: pointer;
+
div {
- padding : 0 !important;
+ padding: 0 !important;
}
}
@@ -107,7 +157,7 @@ footnote::after {
.ProseMirror {
counter-reset: prosemirror-footnote;
- }
+}
.footnote-tooltip {
cursor: auto;
@@ -126,6 +176,7 @@ footnote::after {
.prosemirror-attribution {
font-size: 8px;
}
+
.footnote-tooltip::before {
border: 5px solid silver;
border-top-width: 0px;
@@ -139,46 +190,78 @@ footnote::after {
width: 0;
}
-.formattedTextBox-summarizer {
- opacity :0.5;
+
+.formattedTextBox-inlineComment {
position: relative;
- width:40px;
- height:20px;
+ width: 40px;
+ height: 20px;
+ &::before {
+ content: "→";
+ }
+ &:hover {
+ background: orange;
+ }
}
-.formattedTextBox-summarizer::after{
- content: "←" ;
+
+.formattedTextBox-summarizer {
+ opacity: 0.5;
+ position: relative;
+ width: 40px;
+ height: 20px;
+ &::after {
+ content: "←";
+ }
}
.formattedTextBox-summarizer-collapsed {
- opacity :0.5;
+ opacity: 0.5;
position: relative;
- width:40px;
- height:20px;
-}
-.formattedTextBox-summarizer-collapsed::after {
- content: "...";
+ width: 40px;
+ height: 20px;
+ &::after {
+ content: "...";
+ }
}
.ProseMirror {
-ol { counter-reset: deci1 0; padding-left: 0px; }
-.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 ; ul, ol { padding-left:30px; } }
-.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 ; ul, ol { padding-left:30px; } }
-.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 ; ul, ol { padding-left:30px; } }
-.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18; }
-.lower-roman-ol {counter-reset: lroman; p { display: inline }; font-size: 14; }
-.lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10; }
-.decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;}
-.decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35}
-.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35}
-.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ") "; counter-increment: deci4; display:inline-block; min-width: 40}
-.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ") "; counter-increment: deci5; display:inline-block; min-width: 40}
-.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ") "; counter-increment: deci6; display:inline-block; min-width: 45}
-.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ") "; counter-increment: deci7; display:inline-block; min-width: 50}
-.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 }
-.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 }
-.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35}
-}
+ touch-action: none;
+ span {
+ font-family: inherit;
+ }
+
+ ol, ul {
+ counter-reset: deci1 0 multi1 0;
+ padding-left: 1em;
+ font-family: inherit;
+ }
+ ol {
+ margin-left: 1em;
+ font-family: inherit;
+ }
+
+ .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; }
+ .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;}
+ .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+ .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;}
+ .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; }
+ .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; }
+ .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; }
+
+ .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em }
+ .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;}
+ .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;}
+ .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;}
+
+ .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; }
+ .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; }
+ .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; }
+ .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; }
+ .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; }
+ .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; }
+ .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; }
+
+ .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; }
+ .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; }
+ .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; }
+ .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 037c92a62..60842bcb0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
-import _ from "lodash";
+import { isEqual } from "lodash";
import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
@@ -17,25 +17,23 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols';
import { RichTextField } from "../../../new_fields/RichTextField";
import { RichTextUtils } from '../../../new_fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from '../../../Utils';
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DictationManager } from '../../util/DictationManager';
-import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from "../../util/DragManager";
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import { inpRules } from "../../util/RichTextRules";
-import { FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema";
+import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema";
import { SelectionManager } from "../../util/SelectionManager";
import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocExtendableComponent } from "../DocComponent";
+import { DocAnnotatableComponent } from "../DocComponent";
import { DocumentButtonBar } from '../DocumentButtonBar';
-import { DocumentDecorations } from '../DocumentDecorations';
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
@@ -46,6 +44,11 @@ import { ContextMenu } from '../ContextMenu';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { documentSchema } from '../../../new_fields/documentSchemas';
import { AudioBox } from './AudioBox';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { InkTool } from '../../../new_fields/InkField';
+import { TraceMobx } from '../../../new_fields/util';
+import RichTextMenu from '../../util/RichTextMenu';
+import { DocumentDecorations } from '../DocumentDecorations';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -70,40 +73,45 @@ const RichTextDocument = makeInterface(richTextSchema, documentSchema);
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
- private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
+ public static ToolTipTextMenu: TooltipTextMenu | undefined = undefined;
+ public ProseRef?: HTMLDivElement;
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
- private _proseRef?: HTMLDivElement;
+ private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef();
private _editorView: Opt<EditorView>;
private _applyingChange: boolean = false;
- private _nodeClicked: any;
private _searchIndex = 0;
+ private _sidebarMovement = 0;
+ private _lastX = 0;
+ private _lastY = 0;
private _undoTyping?: UndoManager.Batch;
private _searchReactionDisposer?: Lambda;
private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
private _reactionDisposer: Opt<IReactionDisposer>;
- private _textReactionDisposer: Opt<IReactionDisposer>;
private _heightReactionDisposer: Opt<IReactionDisposer>;
private _rulesReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
private _pullReactionDisposer: Opt<IReactionDisposer>;
private _pushReactionDisposer: Opt<IReactionDisposer>;
+ private _buttonBarReactionDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
- @observable private _fontSize = 13;
- @observable private _fontFamily = "Arial";
+ @observable private _ruleFontSize = 0;
+ @observable private _ruleFontFamily = "Arial";
@observable private _fontAlign = "";
@observable private _entered = false;
+
+ public static FocusedBox: FormattedTextBox | undefined;
public static SelectOnLoad = "";
public static IsFragment(html: string) {
return html.indexOf("data-pm-slice") !== -1;
}
public static GetHref(html: string): string {
- let parser = new DOMParser();
- let parsedHtml = parser.parseFromString(html, 'text/html');
+ const parser = new DOMParser();
+ const parsedHtml = parser.parseFromString(html, 'text/html');
if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 &&
(parsedHtml.body.childNodes[0].childNodes[0] as any).href) {
return (parsedHtml.body.childNodes[0].childNodes[0] as any).href;
@@ -120,17 +128,17 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
public static getToolTip(ev: EditorView) {
- return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev);
+ return this.ToolTipTextMenu ? this.ToolTipTextMenu : this.ToolTipTextMenu = new TooltipTextMenu(ev);
}
@undoBatch
public setFontColor(color: string) {
- let view = this._editorView!;
+ const view = this._editorView!;
if (view.state.selection.from === view.state.selection.to) return false;
if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) {
this.layoutDoc.color = color;
}
- let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
+ const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color });
view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark));
return true;
}
@@ -138,6 +146,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
constructor(props: any) {
super(props);
FormattedTextBox.Instance = this;
+ this.updateHighlights();
}
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
@@ -146,9 +155,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
doLinkOnDeselect() {
Array.from(this.linkOnDeselect.entries()).map(entry => {
- let key = entry[0];
- let value = entry[1];
- let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
+ const key = entry[0];
+ const value = entry[1];
+ const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
DocServer.GetRefField(value).then(doc => {
DocServer.GetRefField(id).then(linkDoc => {
this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value);
@@ -163,40 +172,38 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
- let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
+ const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
if (metadata) {
- let range = tx.selection.$from.blockRange(tx.selection.$to);
+ const range = tx.selection.$from.blockRange(tx.selection.$to);
let text = range ? tx.doc.textBetween(range.start, range.end) : "";
let textEndSelection = tx.selection.to;
for (; textEndSelection < range!.end && text[textEndSelection - range!.start] !== " "; textEndSelection++) { }
text = text.substr(0, textEndSelection - range!.start);
text = text.split(" ")[text.split(" ").length - 1];
- let split = text.split("::");
+ const split = text.split("::");
if (split.length > 1 && split[1]) {
- let key = split[0];
- let value = split[split.length - 1];
+ const key = split[0];
+ const value = split[split.length - 1];
this.linkOnDeselect.set(key, value);
- let id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
- const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value });
+ const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key);
+ const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value });
const mval = this._editorView.state.schema.marks.metadataVal.create();
- let offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
+ const offset = (tx.selection.to === range!.end - 1 ? -1 : 0);
tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval);
this.dataDoc[key] = value;
}
}
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
- if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
- FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
- }
+ (tx.storedMarks && !this._editorView.state.storedMarks) && (this._editorView.state.storedMarks = tx.storedMarks);
- let tsel = this._editorView.state.selection.$from;
+ const tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000)));
this._applyingChange = true;
- this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+ this.extensionDoc && !this.extensionDoc.lastModified && (this.extensionDoc.backgroundColor = "lightGray");
this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
- this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n"));
this._applyingChange = false;
this.updateTitle();
this.tryUpdateHeight();
@@ -205,21 +212,21 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
updateTitle = () => {
if (StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.Document.customTitle) {
- let str = this._editorView.state.doc.textContent;
- let titlestr = str.substr(0, Math.min(40, str.length));
+ const str = this._editorView.state.doc.textContent;
+ const titlestr = str.substr(0, Math.min(40, str.length));
this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
}
}
public highlightSearchTerms = (terms: string[]) => {
- if (this._editorView && (this._editorView as any).docView) {
+ if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- let res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
+ const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term));
let tr = this._editorView.state.tr;
- let flattened: TextSelection[] = [];
+ const flattened: TextSelection[] = [];
res.map(r => r.map(h => flattened.push(h)));
- let lastSel = Math.min(flattened.length - 1, this._searchIndex);
+ const lastSel = Math.min(flattened.length - 1, this._searchIndex);
flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark));
this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView());
@@ -230,59 +237,59 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
if (this._editorView && (this._editorView as any).docView) {
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true });
- let end = this._editorView.state.doc.nodeSize - 2;
+ const end = this._editorView.state.doc.nodeSize - 2;
this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark));
}
}
- setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
- let view = this._editorView!;
- let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+ adoptAnnotation = (start: number, end: number, mark: Mark) => {
+ const view = this._editorView!;
+ const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail });
view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark));
}
protected createDropTarget = (ele: HTMLDivElement) => {
- this._proseRef = ele;
+ this.ProseRef = ele;
this.dropDisposer && this.dropDisposer();
- ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
+ ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
}
@undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
- const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
+ if (de.complete.docDragData) {
+ const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0];
// replace text contents whend dragging with Alt
- if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") {
+ if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) {
if (draggedDoc.data instanceof RichTextField) {
- Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data);
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
e.stopPropagation();
}
// apply as template when dragging with Meta
- } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") {
+ } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.metaKey) {
draggedDoc.isTemplateDoc = true;
let newLayout = Doc.Layout(draggedDoc);
if (typeof (draggedDoc.layout) === "string") {
newLayout = Doc.MakeDelegate(draggedDoc);
- newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
+ newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${this.props.fieldKey}'}`);
}
this.Document.layoutCustom = newLayout;
this.Document.layoutKey = "layoutCustom";
e.stopPropagation();
// embed document when dragging with a userDropAction or an embedDoc flag set
- } else if (de.data.userDropAction || de.data.embedDoc) {
- let target = de.data.droppedDocuments[0];
- const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
- if (link) {
- target.fitToBox = true;
- let node = schema.nodes.dashDoc.create({
- width: target[WidthSym](), height: target[HeightSym](),
- title: "dashDoc", docid: target[Id],
- float: "right"
- });
- let view = this._editorView!;
- view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
- this.tryUpdateHeight();
- e.stopPropagation();
- }
+ } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) {
+ const target = de.complete.docDragData.droppedDocuments[0];
+ // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title);
+ // if (link) {
+ target.fitToBox = true;
+ const node = schema.nodes.dashDoc.create({
+ width: target[WidthSym](), height: target[HeightSym](),
+ title: "dashDoc", docid: target[Id],
+ float: "right"
+ });
+ const view = this._editorView!;
+ view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node));
+ this.tryUpdateHeight();
+ e.stopPropagation();
+ // }
} // otherwise, fall through to outer collection to handle drop
}
}
@@ -293,8 +300,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
if (context === node) return { from: offset, to: offset + node.nodeSize };
if (node.isBlock) {
+ // tslint:disable-next-line: prefer-for-of
for (let i = 0; i < (context.content as any).content.length; i++) {
- let result = this.getNodeEndpoints((context.content as any).content[i], node);
+ const result = this.getNodeEndpoints((context.content as any).content[i], node);
if (result) {
return {
from: result.from + offset + (context.type.name === "doc" ? 0 : 1),
@@ -315,9 +323,10 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
let ret: TextSelection[] = [];
if (node.isTextblock) {
- let index = 0, foundAt, ep = this.getNodeEndpoints(pm.state.doc, node);
+ let index = 0, foundAt;
+ const ep = this.getNodeEndpoints(pm.state.doc, node);
while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) {
- let sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 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;
}
@@ -326,7 +335,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
return ret;
}
- static _highlights: string[] = [];
+ static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"];
updateHighlights = () => {
clearStyleSheetRules(FormattedTextBox._userStyleSheet);
@@ -346,23 +355,45 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" });
}
if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) {
- addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "0" });
+ addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "1" });
}
if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
- let min = Math.round(Date.now() / 1000 / 60);
+ const min = Math.round(Date.now() / 1000 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() }));
setTimeout(() => this.updateHighlights());
}
if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) {
addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" });
- let hr = Math.round(Date.now() / 1000 / 60 / 60);
+ const hr = Math.round(Date.now() / 1000 / 60 / 60);
numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() }));
}
}
+ sidebarDown = (e: React.PointerEvent) => {
+ this._lastX = e.clientX;
+ this._lastY = e.clientY;
+ this._sidebarMovement = 0;
+ document.addEventListener("pointermove", this.sidebarMove);
+ document.addEventListener("pointerup", this.sidebarUp);
+ e.stopPropagation();
+ e.preventDefault(); // prevents text from being selected during drag
+ }
+ sidebarMove = (e: PointerEvent) => {
+ const bounds = this.CurrentDiv.getBoundingClientRect();
+ this._sidebarMovement += Math.sqrt((e.clientX - this._lastX) * (e.clientX - this._lastX) + (e.clientY - this._lastY) * (e.clientY - this._lastY));
+ this.props.Document.sidebarWidthPercent = "" + 100 * (1 - (e.clientX - bounds.left) / bounds.width) + "%";
+ }
+ sidebarUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.sidebarMove);
+ document.removeEventListener("pointerup", this.sidebarUp);
+ }
+
+ toggleSidebar = () => this._sidebarMovement < 5 && (this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%");
+
specificContextMenu = (e: React.MouseEvent): void => {
- let funcs: ContextMenuProps[] = [];
+ const funcs: ContextMenuProps[] = [];
+ funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.toggleSidebar(); }, icon: "expand-arrows-alt" });
funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
funcs.push({
@@ -402,8 +433,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
recordBullet = async () => {
- let completedCue = "end session";
- let results = await DictationManager.Controls.listen({
+ const completedCue = "end session";
+ const results = await DictationManager.Controls.listen({
interimHandler: this.setCurrentBulletContent,
continuous: { indefinite: false },
terminators: [completedCue, "bullet", "next"]
@@ -419,20 +450,20 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
setCurrentBulletContent = (value: string) => {
if (this._editorView) {
let state = this._editorView.state;
- let from = state.selection.from;
- let to = state.selection.to;
+ const from = state.selection.from;
+ const to = state.selection.to;
this._editorView.dispatch(state.tr.insertText(value, from, to));
state = this._editorView.state;
- let updated = TextSelection.create(state.doc, from, from + value.length);
+ const updated = TextSelection.create(state.doc, from, from + value.length);
this._editorView.dispatch(state.tr.setSelection(updated));
}
}
nextBullet = (pos: number) => {
if (this._editorView) {
- let frag = Fragment.fromArray(this.newListItems(2));
+ const frag = Fragment.fromArray(this.newListItems(2));
if (this._editorView.state.doc.resolve(pos).depth >= 2) {
- let slice = new Slice(frag, 2, 2);
+ const slice = new Slice(frag, 2, 2);
let state = this._editorView.state;
this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
pos += 4;
@@ -470,8 +501,15 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
componentDidMount() {
- this.pullFromGoogleDoc(this.checkState);
- this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
+ this._buttonBarReactionDisposer = reaction(
+ () => DocumentButtonBar.Instance,
+ instance => {
+ if (instance) {
+ this.pullFromGoogleDoc(this.checkState);
+ this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => instance.isAnimatingFetch = true);
+ }
+ }
+ );
this._reactionDisposer = reaction(
() => {
@@ -480,7 +518,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
},
incomingValue => {
if (this._editorView && !this._applyingChange) {
- let updatedState = JSON.parse(incomingValue);
+ const updatedState = JSON.parse(incomingValue);
this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
this.tryUpdateHeight();
}
@@ -492,7 +530,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
() => {
if (!DocumentButtonBar.hasPulledHack) {
DocumentButtonBar.hasPulledHack = true;
- let unchanged = this.dataDoc.unchanged;
+ const unchanged = this.dataDoc.unchanged;
this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
}
}
@@ -513,35 +551,15 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
() => this.tryUpdateHeight()
);
- this._textReactionDisposer = reaction(
- () => this.extensionDoc,
- () => {
- if (this.extensionDoc && (this.dataDoc.text || this.dataDoc.lastModified)) {
- this.extensionDoc.text = this.dataDoc.text;
- this.extensionDoc.lastModified = DateCast(this.dataDoc.lastModified)[Copy]();
- this.dataDoc.text = undefined;
- this.dataDoc.lastModified = undefined;
- }
- }, { fireImmediately: true });
-
-
this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
- this._searchReactionDisposer = reaction(() => {
- return StrCast(this.layoutDoc.search_string);
- }, searchString => {
- if (searchString) {
- this.highlightSearchTerms([searchString]);
- }
- else {
- this.unhighlightSearchTerms();
- }
- }, { fireImmediately: true });
-
+ this._searchReactionDisposer = reaction(() => this.layoutDoc.searchMatch,
+ search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(),
+ { fireImmediately: true });
this._rulesReactionDisposer = reaction(() => {
- let ruleProvider = this.props.ruleProvider;
- let heading = NumCast(this.layoutDoc.heading);
+ const ruleProvider = this.props.ruleProvider;
+ const heading = NumCast(this.layoutDoc.heading);
if (ruleProvider instanceof Doc) {
return {
align: StrCast(ruleProvider["ruleAlign_" + heading], ""),
@@ -552,12 +570,12 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
return undefined;
},
action((rules: any) => {
- this._fontFamily = rules ? rules.font : "Arial";
- this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13);
+ this._ruleFontFamily = rules ? rules.font : "Arial";
+ this._ruleFontSize = rules ? rules.size : 0;
rules && setTimeout(() => {
const view = this._editorView!;
- if (this._proseRef) {
- let n = new NodeSelection(view.state.doc.resolve(0));
+ if (this.ProseRef) {
+ const n = new NodeSelection(view.state.doc.resolve(0));
if (this._editorView!.state.doc.textContent === "") {
view.dispatch(view.state.tr.setSelection(new TextSelection(view.state.doc.resolve(0), view.state.doc.resolve(2))).
replaceSelectionWith(this._editorView!.state.schema.nodes.paragraph.create({ align: rules.align }), true));
@@ -572,10 +590,10 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
this._scrollToRegionReactionDisposer = reaction(
() => StrCast(this.layoutDoc.scrollToLinkID),
async (scrollToLinkID) => {
- let findLinkFrag = (frag: Fragment, editor: EditorView) => {
+ const findLinkFrag = (frag: Fragment, editor: EditorView) => {
const nodes: Node[] = [];
frag.forEach((node, index) => {
- let examinedNode = findLinkNode(node, editor);
+ const examinedNode = findLinkNode(node, editor);
if (examinedNode && examinedNode.textContent) {
nodes.push(examinedNode);
start += index;
@@ -583,7 +601,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
});
return { frag: Fragment.fromArray(nodes), start: start };
};
- let findLinkNode = (node: Node, editor: EditorView) => {
+ const findLinkNode = (node: Node, editor: EditorView) => {
if (!node.isText) {
const content = findLinkFrag(node.content, editor);
return node.copy(content.frag);
@@ -595,8 +613,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
let start = -1;
if (this._editorView && scrollToLinkID) {
- let editor = this._editorView;
- let ret = findLinkFrag(editor.state.doc.content, editor);
+ const editor = this._editorView;
+ const ret = findLinkFrag(editor.state.doc.content, editor);
if (ret.frag.size > 2 && ret.start >= 0) {
let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
@@ -615,33 +633,33 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
{ fireImmediately: true }
);
- setTimeout(() => this.tryUpdateHeight(), 0);
+ setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0)));
}
pushToGoogleDoc = async () => {
this.pullFromGoogleDoc(async (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
- let modes = GoogleApiClientUtils.Docs.WriteMode;
+ const modes = GoogleApiClientUtils.Docs.WriteMode;
let mode = modes.Replace;
let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string");
if (!reference) {
mode = modes.Insert;
reference = { title: StrCast(this.dataDoc.title) };
}
- let redo = async () => {
+ const redo = async () => {
if (this._editorView && reference) {
- let content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
- let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+ const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state);
+ const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
response && (this.dataDoc[GoogleRef] = response.documentId);
- let pushSuccess = response !== undefined && !("errors" in response);
+ const pushSuccess = response !== undefined && !("errors" in response);
dataDoc.unchanged = pushSuccess;
DocumentButtonBar.Instance.startPushOutcome(pushSuccess);
}
};
- let undo = () => {
+ const undo = () => {
if (!exportState) {
return;
}
- let content: GoogleApiClientUtils.Docs.Content = {
+ const content: GoogleApiClientUtils.Docs.Content = {
text: exportState.text,
requests: []
};
@@ -655,8 +673,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
pullFromGoogleDoc = async (handler: PullHandler) => {
- let dataDoc = this.dataDoc;
- let documentId = StrCast(dataDoc[GoogleRef]);
+ const dataDoc = this.dataDoc;
+ const documentId = StrCast(dataDoc[GoogleRef]);
let exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>;
if (documentId) {
exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc);
@@ -671,8 +689,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
dataDoc.data = new RichTextField(JSON.stringify(exportState.state.toJSON()));
setTimeout(() => {
if (this._editorView) {
- let state = this._editorView.state;
- let end = state.doc.content.size - 1;
+ const state = this._editorView.state;
+ const end = state.doc.content.size - 1;
this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
}
}, 0);
@@ -687,9 +705,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
checkState = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => {
if (exportState && this._editorView) {
- let equalContent = _.isEqual(this._editorView.state.doc, exportState.state.doc);
- let equalTitles = dataDoc.title === exportState.title;
- let unchanged = equalContent && equalTitles;
+ const equalContent = isEqual(this._editorView.state.doc, exportState.state.doc);
+ const equalTitles = dataDoc.title === exportState.title;
+ const unchanged = equalContent && equalTitles;
dataDoc.unchanged = unchanged;
DocumentButtonBar.Instance.setPullState(unchanged);
}
@@ -717,7 +735,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => {
- let cbe = event as ClipboardEvent;
+ const cbe = event as ClipboardEvent;
const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin");
const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion");
if (pdfDocId && pdfRegionId) {
@@ -727,18 +745,18 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
setTimeout(async () => {
const extension = Doc.fieldExtensionDoc(pdfDoc, "data");
if (extension) {
- let targetAnnotations = await DocListCastAsync(extension.annotations);// bcz: NO... this assumes the pdf is using its 'data' field. need to have the PDF's view handle updating its own annotations
+ const targetAnnotations = await DocListCastAsync(extension.annotations);// bcz: NO... this assumes the pdf is using its 'data' field. need to have the PDF's view handle updating its own annotations
targetAnnotations && targetAnnotations.push(pdfRegion);
}
});
- let link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
+ const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
if (link) {
cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
- let linkId = link[Id];
- let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
+ const linkId = link[Id];
+ const frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId));
slice = new Slice(frag, slice.openStart, slice.openEnd);
- var tr = view.state.tr.replaceSelection(slice);
+ const tr = view.state.tr.replaceSelection(slice);
view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
}
}
@@ -768,54 +786,59 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
private setupEditor(config: any, doc: Doc, fieldKey: string) {
- let field = doc ? Cast(doc[fieldKey], RichTextField) : undefined;
+ const field = doc ? Cast(doc[fieldKey], RichTextField) : undefined;
let startup = StrCast(doc.documentText);
startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : "";
if (!field && doc) {
- let text = StrCast(doc[fieldKey]);
+ const text = StrCast(doc[fieldKey]);
if (text) {
startup = text;
} else if (Cast(doc[fieldKey], "number")) {
startup = NumCast(doc[fieldKey], 99).toString();
}
}
- if (this._proseRef) {
- let self = this;
+ if (this.ProseRef) {
+ const self = this;
this._editorView && this._editorView.destroy();
- this._editorView = new EditorView(this._proseRef, {
+ this._editorView = new EditorView(this.ProseRef, {
state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
handleScrollToSelection: (editorView) => {
- let ref = editorView.domAtPos(editorView.state.selection.from);
+ const ref = editorView.domAtPos(editorView.state.selection.from);
let refNode = ref.node as any;
while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
- let r1 = refNode && refNode.getBoundingClientRect();
- let r3 = self._ref.current!.getBoundingClientRect();
- r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
+ const r1 = refNode && refNode.getBoundingClientRect();
+ const r3 = self._ref.current!.getBoundingClientRect();
+ if (r1.top < r3.top || r1.top > r3.bottom) {
+ r1 && (self._scrollRef.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
+ }
return true;
},
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
+ dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); },
dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); },
image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
- star(node, view, getPos) { return new SummarizedView(node, view, getPos); },
+ summary(node, view, getPos) { return new SummaryView(node, view, getPos); },
ordered_list(node, view, getPos) { return new OrderedListView(); },
footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); }
},
clipboardTextSerializer: this.clipboardTextSerializer,
handlePaste: this.handlePaste,
});
- if (startup) {
+ this._editorView.state.schema.Document = this.props.Document;
+ if (startup && this._editorView) {
Doc.GetProto(doc).documentText = undefined;
this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
}
}
- let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad;
+ const selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad;
if (selectOnLoad) {
FormattedTextBox.SelectOnLoad = "";
this.props.select(false);
}
- this._editorView!.focus();
+ const rtf = doc ? Cast(doc[fieldKey], RichTextField) : undefined;
+ (selectOnLoad || (rtf && !rtf.Text)) && this._editorView!.focus();
// add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) })];
}
@@ -837,21 +860,26 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
this._rulesReactionDisposer && this._rulesReactionDisposer();
this._reactionDisposer && this._reactionDisposer();
this._proxyReactionDisposer && this._proxyReactionDisposer();
- this._textReactionDisposer && this._textReactionDisposer();
this._pushReactionDisposer && this._pushReactionDisposer();
this._pullReactionDisposer && this._pullReactionDisposer();
this._heightReactionDisposer && this._heightReactionDisposer();
this._searchReactionDisposer && this._searchReactionDisposer();
+ this._buttonBarReactionDisposer && this._buttonBarReactionDisposer();
this._editorView && this._editorView.destroy();
}
+
+ static _downEvent: any;
onPointerDown = (e: React.PointerEvent): void => {
- let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
- pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos));
+ this.doLinkOnDeselect();
+ FormattedTextBox._downEvent = true;
+ FormattedTextBoxComment.textBox = this;
if (this.props.onClick && e.button === 0) {
e.preventDefault();
}
- if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
- e.stopPropagation();
+ if (e.button === 0 && this.active(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // don't stop propagation if clicking in the sidebar
+ e.stopPropagation();
+ }
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
@@ -859,23 +887,42 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
onPointerUp = (e: React.PointerEvent): void => {
- if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; }
+ if (!FormattedTextBox._downEvent) return;
+ FormattedTextBox._downEvent = false;
+ if (!(e.nativeEvent as any).formattedHandled) {
+ FormattedTextBoxComment.textBox = this;
+ FormattedTextBoxComment.update(this._editorView!);
+ }
(e.nativeEvent as any).formattedHandled = true;
- if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+ if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
}
- static InputBoxOverlay: FormattedTextBox | undefined;
@action
onFocused = (e: React.FocusEvent): void => {
- FormattedTextBox.InputBoxOverlay = this;
+ FormattedTextBox.FocusedBox = this;
this.tryUpdateHeight();
+
+ // see if we need to preserve the insertion point
+ const prosediv = this.ProseRef?.children?.[0] as any;
+ const keeplocation = prosediv?.keeplocation;
+ prosediv && (prosediv.keeplocation = undefined);
+ const pos = this._editorView?.state.selection.$from.pos || 1;
+ keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+
+ // jump rich text menu to this textbox
+ const { current } = this._ref;
+ if (current) {
+ const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
+ const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50;
+ RichTextMenu.Instance.jumpTo(x, y);
+ }
}
onPointerWheel = (e: React.WheelEvent): void => {
// if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
- if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+ if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
e.stopPropagation();
}
}
@@ -884,66 +931,91 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
static _userStyleSheet: any = addStyleSheet();
onClick = (e: React.MouseEvent): void => {
- if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
- (e.nativeEvent as any).formattedHandled = true;
- if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
- let href = (e.target as any).href;
- let location: string;
- if ((e.target as any).attributes.location) {
- location = (e.target as any).attributes.location.value;
+ if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
+ const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+ const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
+ if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
+ e.preventDefault();
}
- let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
- let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
- if (node) {
- let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
- if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
- href = link && link.attrs.href;
- location = link && link.attrs.location;
+ if (!node && this.ProseRef) {
+ const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
+ if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
}
}
- if (href) {
- if (href.indexOf(Utils.prepend("/doc/")) === 0) {
- let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- if (linkClicked) {
- DocServer.GetRefField(linkClicked).then(async linkDoc => {
- (linkDoc instanceof Doc) &&
- DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false);
- });
- }
- } else {
- let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
- this.props.addDocument && this.props.addDocument(webDoc);
- }
- e.stopPropagation();
- e.preventDefault();
- }
}
-
- this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey);
+ if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
+ (e.nativeEvent as any).formattedHandled = true;
+ // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
+ // let href = (e.target as any).href;
+ // let location: string;
+ // if ((e.target as any).attributes.location) {
+ // location = (e.target as any).attributes.location.value;
+ // }
+ // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+ // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos);
+ // if (node) {
+ // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link);
+ // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above).
+ // href = link && link.attrs.href;
+ // location = link && link.attrs.location;
+ // }
+ // }
+ // if (href) {
+ // if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+ // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ // if (linkClicked) {
+ // DocServer.GetRefField(linkClicked).then(async linkDoc => {
+ // (linkDoc instanceof Doc) &&
+ // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false);
+ // });
+ // }
+ // } else {
+ // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) });
+ // this.props.addDocument && this.props.addDocument(webDoc);
+ // }
+ // e.stopPropagation();
+ // e.preventDefault();
+ // }
+ // }
+
+ this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false);
if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500);
}
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
- hitBulletTargets(x: number, y: number, offsetX: number, select: boolean = false) {
+ hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) {
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
- if (this.props.isSelected() && offsetX < 40) {
- let pos = this._editorView!.posAtCoords({ left: x, top: y });
- if (pos && pos.pos > 0) {
- let node = this._editorView!.state.doc.nodeAt(pos.pos);
- let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined;
- if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) {
- let hit = this._editorView!.domAtPos(pos.pos).node as any; // let beforeEle = document.querySelector("." + hit.className) as Element;
- let before = hit ? window.getComputedStyle(hit, ':before') : undefined;
- let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined;
- if (beforeWidth && offsetX < beforeWidth) {
- let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
- if (ol && ol.type === schema.nodes.ordered_list && select) {
- this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
- addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "gray" });
- } else {
- this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
- }
+ const pos = this._editorView!.posAtCoords({ left: x, top: y });
+ if (pos && this.props.isSelected(true)) {
+ // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined;
+ //const node = this._editorView!.state.doc.nodeAt(pos.pos);
+ const $pos = this._editorView!.state.doc.resolve(pos.pos);
+ let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined;
+ if ($pos.node().type === schema.nodes.ordered_list) {
+ for (let off = 1; off < 100; off++) {
+ const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
+ const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
+ if (node?.type === schema.nodes.list_item) {
+ list_node = node;
+ break;
+ }
+ }
+ }
+ if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) {
+ if (select) {
+ const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1);
+ if (!highlightOnly) {
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos)));
}
+ addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
+ } else if (Math.abs(pos.pos - pos.inside) < 2) {
+ if (!highlightOnly) {
+ this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility }));
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside)));
+ }
+ addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" });
}
}
}
@@ -951,11 +1023,11 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
onMouseUp = (e: React.MouseEvent): void => {
e.stopPropagation();
- let view = this._editorView as any;
+ const view = this._editorView as any;
// this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there
// are nested prosemirrors. We only want the lowest level prosemirror to be invoked.
if (view.mouseDown) {
- let originalUpHandler = view.mouseDown.up;
+ const originalUpHandler = view.mouseDown.up;
view.root.removeEventListener("mouseup", originalUpHandler);
view.mouseDown.up = (e: MouseEvent) => {
!(e as any).formattedHandled && originalUpHandler(e);
@@ -967,16 +1039,18 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
tooltipTextMenuPlugin() {
- let self = FormattedTextBox;
+ const self = FormattedTextBox;
return new Plugin({
view(newView) {
- return self._toolTipTextMenu = FormattedTextBox.getToolTip(newView);
+ // return self.ToolTipTextMenu = FormattedTextBox.getToolTip(newView);
+ RichTextMenu.Instance.changeView(newView);
+ return RichTextMenu.Instance;
}
});
}
tooltipLinkingMenuPlugin() {
- let myprops = this.props;
+ const myprops = this.props;
return new Plugin({
view(_editorView) {
return new TooltipLinkingMenu(_editorView, myprops);
@@ -990,42 +1064,83 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
this._undoTyping = undefined;
}
this.doLinkOnDeselect();
+
+ // move the richtextmenu offscreen
+ if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300);
}
+
+ _lastTimedMark: Mark | undefined = undefined;
onKeyPress = (e: React.KeyboardEvent) => {
+ if (e.altKey) {
+ e.preventDefault();
+ return;
+ }
+ const state = this._editorView!.state;
+ if (!state.selection.empty && e.key === "%") {
+ state.schema.EnteringStyle = true;
+ e.preventDefault();
+ e.stopPropagation();
+ return;
+ }
+
+ if (state.selection.empty || !state.schema.EnteringStyle) {
+ state.schema.EnteringStyle = false;
+ }
if (e.key === "Escape") {
+ this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
+ (document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
}
e.stopPropagation();
if (e.key === "Tab" || e.key === "Enter") {
e.preventDefault();
}
- let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+ const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) });
+ this._lastTimedMark = mark;
this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark));
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");
}
- if (this._recording) { this.stopDictation(true); setTimeout(() => this.recordDictation(), 250); }
+ if (this._recording) {
+ this.stopDictation(true);
+ setTimeout(() => this.recordDictation(), 250);
+ }
}
@action
- tryUpdateHeight() {
- let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0;
- if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 &&
+ tryUpdateHeight(limitHeight?: number) {
+ let scrollHeight = this._ref.current?.scrollHeight;
+ if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
- let nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
- let dh = NumCast(this.layoutDoc.height, 0);
- this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
- this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
+ if (limitHeight && scrollHeight > limitHeight) {
+ scrollHeight = limitHeight;
+ this.layoutDoc.limitHeight = undefined;
+ this.layoutDoc.autoHeight = false;
+ }
+ const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
+ const dh = NumCast(this.layoutDoc.height, 0);
+ const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0));
+ if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle
+ this.layoutDoc.height = newHeight;
+ this.dataDoc.nativeHeight = nh ? scrollHeight : undefined;
+ }
}
}
+ @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
+ @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
+ @computed get annotationsKey() { return "annotations"; }
render() {
- let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
- let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground
- ? "none" : "all";
+ TraceMobx();
+ const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
+ const interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
if (this.props.isSelected()) {
- FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
+ // TODO: ftong --> update from dash in richtextmenu
+ RichTextMenu.Instance.updateFromDash(this._editorView!, undefined, this.props);
+ // FormattedTextBox.ToolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
+ } else if (FormattedTextBoxComment.textBox === this) {
+ FormattedTextBoxComment.Hide();
}
return (
<div className={`formattedTextBox-cont`} ref={this._ref}
@@ -1034,14 +1149,15 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
- pointerEvents: interactive,
- fontSize: this._fontSize,
- fontFamily: this._fontFamily,
+ pointerEvents: interactive ? "none" : "all",
+ fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13),
+ fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"),
}}
onContextMenu={this.specificContextMenu}
onKeyDown={this.onKeyPress}
onFocus={this.onFocused}
onClick={this.onClick}
+ onPointerMove={e => this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, true)}
onBlur={this.onBlur}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
@@ -1050,8 +1166,36 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
onPointerEnter={action(() => this._entered = true)}
onPointerLeave={action(() => this._entered = false)}
>
- <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
-
+ <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} ref={this._scrollRef}>
+ <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
+ </div>
+ {this.props.Document.hideSidebar ? (null) : this.sidebarWidthPercent === "0%" ?
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} /> :
+ <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
+ style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}>
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={() => this.sidebarWidth}
+ annotationsKey={this.annotationsKey}
+ isAnnotationOverlay={false}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ select={emptyFunction}
+ active={this.annotationsActive}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={(doc: Doc) => { doc.hideSidebar = true; return this.addDocument(doc); }}
+ CollectionView={undefined}
+ ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)}
+ ruleProvider={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ chromeCollapsed={true}>
+ </CollectionFreeFormView>
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} onClick={e => this.toggleSidebar()} />
+ </div>}
<div className="formattedTextBox-dictation"
onClick={e => {
this._recording ? this.stopDictation(true) : this.recordDictation();
@@ -1059,7 +1203,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
e.stopPropagation();
}} >
<FontAwesomeIcon className="formattedTExtBox-audioFont"
- style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.2 }} icon={"microphone"} size="sm" />
+ style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.5, display: this.props.isSelected() ? "" : "none" }} icon={"microphone"} size="sm" />
</div>
</div>
);
diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss
index 792cee182..2dd63ec21 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/FormattedTextBoxComment.scss
@@ -5,7 +5,6 @@
background: white;
border: 1px solid silver;
border-radius: 2px;
- padding: 2px 10px;
margin-bottom: 7px;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index bde278be3..f7a530790 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -1,13 +1,20 @@
-import { Plugin, EditorState } from "prosemirror-state";
-import './FormattedTextBoxComment.scss';
-import { ResolvedPos, Mark } from "prosemirror-model";
+import { Mark, ResolvedPos } from "prosemirror-model";
+import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
import { Doc } from "../../../new_fields/Doc";
-import { schema } from "../../util/RichTextSchema";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { Utils } from "../../../Utils";
-import { StrCast } from "../../../new_fields/Types";
+import { DocumentManager } from "../../util/DocumentManager";
+import { schema } from "../../util/RichTextSchema";
+import { Transform } from "../../util/Transform";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
import { FormattedTextBox } from "./FormattedTextBox";
+import './FormattedTextBoxComment.scss';
+import React = require("react");
+import { Docs } from "../../documents/Documents";
+import wiki from "wikijs";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -46,109 +53,174 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
+ static tooltipInput: HTMLInputElement;
static start: number;
static end: number;
static mark: Mark;
- static opened: boolean;
static textBox: FormattedTextBox | undefined;
+ static linkDoc: Doc | undefined;
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
const root = document.getElementById("root");
- let input = document.createElement("input");
- input.type = "checkbox";
+ FormattedTextBoxComment.tooltipInput = document.createElement("input");
+ FormattedTextBoxComment.tooltipInput.type = "checkbox";
FormattedTextBoxComment.tooltip = document.createElement("div");
FormattedTextBoxComment.tooltipText = document.createElement("div");
+ FormattedTextBoxComment.tooltipText.style.width = "100%";
+ FormattedTextBoxComment.tooltipText.style.height = "100%";
+ FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
- FormattedTextBoxComment.tooltip.appendChild(input);
+ FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
+ FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
+ FormattedTextBoxComment.tooltip.style.width = "100%";
+ FormattedTextBoxComment.tooltip.style.height = "100%";
+ FormattedTextBoxComment.tooltip.style.overflow = "hidden";
+ FormattedTextBoxComment.tooltip.style.display = "none";
+ FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
- let keep = e.target && (e.target as any).type === "checkbox" ? true : false;
- FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
- FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation(
- FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
- FormattedTextBoxComment.opened, keep);
+ const keep = e.target && (e.target as any).type === "checkbox" ? true : false;
+ const textBox = FormattedTextBoxComment.textBox;
+ if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+ (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight"));
+ } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
+ textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight");
+ }
+ keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation(
+ FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark);
+ e.stopPropagation();
+ e.preventDefault();
};
root && root.appendChild(FormattedTextBoxComment.tooltip);
}
- this.update(view, undefined);
}
public static Hide() {
FormattedTextBoxComment.textBox = undefined;
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
}
- public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) {
+ public static SetState(textBox: any, start: number, end: number, mark: Mark) {
FormattedTextBoxComment.textBox = textBox;
FormattedTextBoxComment.start = start;
FormattedTextBoxComment.end = end;
FormattedTextBoxComment.mark = mark;
- FormattedTextBoxComment.opened = opened;
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
}
- update(view: EditorView, lastState?: EditorState) {
- let state = view.state;
+ static update(view: EditorView, lastState?: EditorState) {
+ const state = view.state;
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) &&
- lastState.selection.eq(state.selection)) return;
+ lastState.selection.eq(state.selection)) {
+ return;
+ }
+ FormattedTextBoxComment.linkDoc = undefined;
- if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return;
+ const textBox = FormattedTextBoxComment.textBox;
+ if (!textBox || !textBox.props) {
+ return;
+ }
let set = "none";
- if (FormattedTextBoxComment.textBox && state.selection.$from) {
- let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
- let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
- const spos = state.selection.$from.pos - nbef;
- const epos = state.selection.$from.pos + naft;
- let child = state.selection.$from.nodeBefore;
- let mark = child && findOtherUserMark(child.marks);
- let noselection = view.state.selection.$from === view.state.selection.$to;
+ let nbef = 0;
+ FormattedTextBoxComment.tooltipInput.style.display = "none";
+ FormattedTextBoxComment.tooltip.style.width = "";
+ FormattedTextBoxComment.tooltip.style.height = "";
+ (FormattedTextBoxComment.tooltipText as any).href = "";
+ FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
+ FormattedTextBoxComment.tooltipText.style.overflow = "";
+ // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
+ if (state.selection.$from) {
+ nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
+ const naft = findEndOfMark(state.selection.$from, view, findOtherUserMark);
+ const noselection = view.state.selection.$from === view.state.selection.$to;
+ let child: any = null;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+ const mark = child && findOtherUserMark(child.marks);
if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
- FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark);
+ FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark);
}
- if (mark && child && nbef && naft) {
- FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified;
- // These are in screen coordinates
- // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
- let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
- // The box in which the tooltip is positioned, to use as base
- let box = (document.getElementById("main-div") as any).getBoundingClientRect();
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- let left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ if (mark && child && ((nbef && naft) || !noselection)) {
+ FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
set = "";
+ FormattedTextBoxComment.tooltipInput.style.display = "";
}
}
+ // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
if (set === "none" && state.selection.$from) {
- FormattedTextBoxComment.textBox = undefined;
- let nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
- let naft = findEndOfMark(state.selection.$from, view, findLinkMark);
- let child = state.selection.$from.nodeBefore;
- let mark = child && findLinkMark(child.marks);
- if (mark && child && nbef && naft) {
- FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href);
+ nbef = findStartOfMark(state.selection.$from, view, findLinkMark);
+ const naft = findEndOfMark(state.selection.$from, view, findLinkMark);
+ let child: any = null;
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node));
+ const mark = child && findLinkMark(child.marks);
+ if (mark && child && nbef && naft && mark.attrs.showPreview) {
+ FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
+ (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+ if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
+ wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
+ } else {
+ FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
+ FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
+ }
if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
- let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
- docTarget && DocServer.GetRefField(docTarget).then(linkDoc =>
- (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title)));
+ FormattedTextBoxComment.tooltipText.textContent = "target not found...";
+ (FormattedTextBoxComment.tooltipText as any).href = "";
+ const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
+ if (linkDoc instanceof Doc) {
+ (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
+ FormattedTextBoxComment.linkDoc = linkDoc;
+ const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
+ try {
+ ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+ } catch (e) { }
+ if (target) {
+ ReactDOM.render(<ContentFittingDocumentView
+ Document={target}
+ LibraryPath={emptyPath}
+ fitToBox={true}
+ moveDocument={returnFalse}
+ getTransform={Transform.Identity}
+ active={returnFalse}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ ruleProvider={undefined}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ dontRegisterView={true}
+ renderDepth={1}
+ PanelWidth={() => Math.min(350, NumCast(target.width, 350))}
+ PanelHeight={() => Math.min(250, NumCast(target.height, 250))}
+ focus={emptyFunction}
+ whenActiveChanged={returnFalse}
+ />, FormattedTextBoxComment.tooltipText);
+ FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
+ FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
+ }
+ // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document ....
+ // let text = ext && StrCast(ext.text);
+ // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
+ }
+ });
}
- // These are in screen coordinates
- // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
- let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
- // The box in which the tooltip is positioned, to use as base
- let box = (document.getElementById("main-div") as any).getBoundingClientRect();
- // Find a center-ish x position from the selection endpoints (when
- // crossing lines, end may be more to the left)
- let left = Math.max((start.left + end.left) / 2, start.left + 3);
- FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
- FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
set = "";
}
}
+ if (set !== "none") {
+ // These are in screen coordinates
+ // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to);
+ const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef);
+ // The box in which the tooltip is positioned, to use as base
+ const box = (document.getElementById("mainView-container") as any).getBoundingClientRect();
+ // Find a center-ish x position from the selection endpoints (when
+ // crossing lines, end may be more to the left)
+ const left = Math.max((start.left + end.left) / 2, start.left + 3);
+ FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px";
+ FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px";
+ }
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
}
- destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; }
+ destroy() { }
}
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 60f547b1e..9462ff024 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -51,7 +51,7 @@ export class IconBox extends React.Component<FieldViewProps> {
}
public static DocumentIcon(layout: string) {
- let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ const button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
layout.indexOf("ImageBox") !== -1 ? faImage :
layout.indexOf("Formatted") !== -1 ? faStickyNote :
layout.indexOf("Video") !== -1 ? faFilm :
@@ -65,14 +65,14 @@ export class IconBox extends React.Component<FieldViewProps> {
}
specificContextMenu = (): void => {
- let cm = ContextMenu.Instance;
+ const cm = ContextMenu.Instance;
cm.addItem({ description: this.props.Document.hideLabel ? "Show label with icon" : "Remove label from icon", event: this.setLabelField, icon: "tag" });
if (!this.props.Document.hideLabel) {
cm.addItem({ description: "Use Target Title", event: () => IconBox.AutomaticTitle(this.props.Document), icon: "text-height" });
}
}
render() {
- let label = this.props.Document.hideLabel ? "" : this.props.Document.title;
+ const label = this.props.Document.hideLabel ? "" : this.props.Document.title;
return (
<div className="iconBox-container" onContextMenu={this.specificContextMenu}>
{this.minimizedIcon}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 57c024bbf..cf5d999a7 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,4 +1,22 @@
-.imageBox-cont, .imageBox-cont-interactive {
+.imageBox, .imageBox-dragging{
+ pointer-events: all;
+ border-radius: inherit;
+ width:100%;
+ height:100%;
+ position: absolute;
+ transform-origin: top left;
+ .imageBox-fader {
+ pointer-events: all;
+ }
+}
+
+.imageBox-dragging {
+ .imageBox-fader {
+ pointer-events: none;
+ }
+}
+
+.imageBox-cont {
padding: 0vw;
position: absolute;
text-align: center;
@@ -8,17 +26,11 @@
max-height: 100%;
pointer-events: none;
background:transparent;
-}
-
-.imageBox-container {
- border-radius: inherit;
- width:100%;
- height:100%;
- position: absolute;
-}
-
-.imageBox-cont-interactive {
- pointer-events: all;
+ img {
+ height: auto;
+ width: 100%;
+ pointer-events: all;
+ }
}
.imageBox-dot {
@@ -31,16 +43,6 @@
background: gray;
}
-.imageBox-cont img {
- height: auto;
- width: 100%;
-}
-
-.imageBox-cont-interactive img {
- height: auto;
- width: 100%;
-}
-
#google-photos {
transition: all 0.5s ease 0s;
width: 30px;
@@ -98,18 +100,18 @@
}
}
-#cf {
+.imageBox-fader {
position: relative;
width: 100%;
margin: 0 auto;
display: flex;
- align-items: center;
height: 100%;
overflow: hidden;
.imageBox-fadeBlocker {
width: 100%;
height: 100%;
+ position: absolute;
background: black;
display: flex;
flex-direction: row;
@@ -124,7 +126,7 @@
}
}
-#cf img {
+.imageBox-fader img {
position: absolute;
left: 0;
}
@@ -136,10 +138,12 @@
transition: opacity 1s ease-in-out;
}
-.imageBox-fadeBlocker:hover {
- -webkit-transition: opacity 1s ease-in-out;
- -moz-transition: opacity 1s ease-in-out;
- -o-transition: opacity 1s ease-in-out;
- transition: opacity 1s ease-in-out;
- opacity: 0;
+.imageBox:hover {
+ .imageBox-fadeBlocker {
+ -webkit-transition: opacity 1s ease-in-out;
+ -moz-transition: opacity 1s ease-in-out;
+ -o-transition: opacity 1s ease-in-out;
+ transition: opacity 1s ease-in-out;
+ opacity: 0;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 212c99f9d..634555012 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,10 +8,9 @@ import { Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
import { List } from '../../../new_fields/List';
import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
import { ComputedField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types';
+import { Cast, NumCast } from '../../../new_fields/Types';
import { AudioField, ImageField } from '../../../new_fields/URLField';
-import { RouteStore } from '../../../server/RouteStore';
-import { Utils, returnOne, emptyFunction, OmitKeys } from '../../../Utils';
+import { Utils, returnOne, emptyFunction } from '../../../Utils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
@@ -19,7 +18,6 @@ import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
import { DocAnnotatableComponent } from '../DocComponent';
-import { InkingControl } from '../InkingControl';
import FaceRectangles from './FaceRectangles';
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
@@ -27,8 +25,10 @@ import React = require("react");
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { documentSchema } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
-var requestImageSize = require('../../util/request-image-size');
-var path = require('path');
+import { TraceMobx } from '../../../new_fields/util';
+import { SelectionManager } from '../../util/SelectionManager';
+const requestImageSize = require('../../util/request-image-size');
+const path = require('path');
const { Howl } = require('howler');
@@ -66,18 +66,18 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer && this._dropDisposer();
- ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
+ ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
}
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.DocumentDragData) {
- if (de.mods === "AltKey" && de.data.draggedDocuments.length && de.data.draggedDocuments[0].data instanceof ImageField) {
- Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.data.draggedDocuments[0].data.url);
+ if (de.complete.docDragData) {
+ if (de.altKey && de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0].data instanceof ImageField) {
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.complete.docDragData.draggedDocuments[0].data.url);
e.stopPropagation();
}
- de.mods === "MetaKey" && de.data.droppedDocuments.forEach(action((drop: Doc) => {
+ de.metaKey && de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => {
this.extensionDoc && Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop);
e.stopPropagation();
}));
@@ -87,7 +87,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
recordAudioAnnotation = () => {
let gumStream: any;
let recorder: any;
- let self = this;
+ const self = this;
const extensionDoc = this.extensionDoc;
extensionDoc && navigator.mediaDevices.getUserMedia({
audio: true
@@ -97,16 +97,16 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
recorder.ondataavailable = async function (e: any) {
const formData = new FormData();
formData.append("file", e.data);
- const res = await fetch(Utils.prepend(RouteStore.upload), {
+ const res = await fetch(Utils.prepend("/upload"), {
method: 'POST',
body: formData
});
const files = await res.json();
const url = Utils.prepend(files[0].path);
// upload to server with known URL
- let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", width: 200, height: 32 });
+ const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", width: 200, height: 32 });
audioDoc.treeViewExpandedView = "layout";
- let audioAnnos = Cast(extensionDoc.audioAnnotations, listSpec(Doc));
+ const audioAnnos = Cast(extensionDoc.audioAnnotations, listSpec(Doc));
if (audioAnnos === undefined) {
extensionDoc.audioAnnotations = new List([audioDoc]);
} else {
@@ -125,10 +125,10 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
@undoBatch
rotate = action(() => {
- let nw = this.Document.nativeWidth;
- let nh = this.Document.nativeHeight;
- let w = this.Document.width;
- let h = this.Document.height;
+ const nw = this.Document.nativeWidth;
+ const nh = this.Document.nativeHeight;
+ const w = this.Document.width;
+ const h = this.Document.height;
this.Document.rotation = ((this.Document.rotation || 0) + 90) % 360;
this.Document.nativeWidth = nh;
this.Document.nativeHeight = nw;
@@ -139,12 +139,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
specificContextMenu = (e: React.MouseEvent): void => {
const field = Cast(this.Document[this.props.fieldKey], ImageField);
if (field) {
- let funcs: ContextMenuProps[] = [];
+ const funcs: ContextMenuProps[] = [];
funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" });
funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
- let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
- let modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
+ const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
+ const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
!existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" });
@@ -154,8 +154,8 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
}
extractFaces = () => {
- let converter = (results: any) => {
- let faceDocs = new List<Doc>();
+ const converter = (results: any) => {
+ const faceDocs = new List<Doc>();
results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!), new List<Doc>());
return faceDocs;
};
@@ -163,12 +163,12 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
}
generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
- let converter = (results: any) => {
- let tagDoc = new Doc;
- let tagsList = new List();
+ const converter = (results: any) => {
+ const tagDoc = new Doc;
+ const tagsList = new List();
results.tags.map((tag: Tag) => {
tagsList.push(tag.name);
- let sanitized = tag.name.replace(" ", "_");
+ const sanitized = tag.name.replace(" ", "_");
tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`);
});
this.extensionDoc && (this.extensionDoc.generatedTags = tagsList);
@@ -180,7 +180,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
}
@computed private get url() {
- let data = Cast(this.dataDoc[this.props.fieldKey], ImageField);
+ const data = Cast(this.dataDoc[this.props.fieldKey], ImageField);
return data ? data.url.href : undefined;
}
@@ -193,7 +193,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
} else if (!(lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg"))) {
return url.href;//Why is this here
}
- let ext = path.extname(url.href);
+ const ext = path.extname(url.href);
const suffix = this.props.renderDepth < 1 ? "_o" : this._curSuffix;
return url.href.replace(ext, suffix + ext);
}
@@ -207,36 +207,40 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
if (this._curSuffix === "_l") this._largeRetryCount++;
}
@action onError = () => {
- let timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
+ const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
if (timeout < 10) {
setTimeout(this.retryPath, Math.min(10000, timeout * 5));
}
}
_curSuffix = "_m";
- resize = (srcpath: string) => {
- requestImageSize(srcpath)
+ _resized = "";
+ resize = (imgPath: string) => {
+ requestImageSize(imgPath)
.then((size: any) => {
- let rotation = NumCast(this.dataDoc.rotation) % 180;
- let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
- let aspect = realsize.height / realsize.width;
+ const rotation = NumCast(this.dataDoc.rotation) % 180;
+ const realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size;
+ const aspect = realsize.height / realsize.width;
if (this.Document.width && (Math.abs(1 - NumCast(this.Document.height) / NumCast(this.Document.width) / (realsize.height / realsize.width)) > 0.1)) {
setTimeout(action(() => {
- this.Document.height = this.Document[WidthSym]() * aspect;
- this.Document.nativeHeight = realsize.height;
- this.Document.nativeWidth = realsize.width;
+ if (this.pathInfos.srcpath === imgPath && (!this.layoutDoc.isTemplateDoc || this.dataDoc !== this.layoutDoc)) {
+ this._resized = imgPath;
+ this.Document.height = this.Document[WidthSym]() * aspect;
+ this.Document.nativeHeight = realsize.height;
+ this.Document.nativeWidth = realsize.width;
+ }
}), 0);
- }
+ } else this._resized = imgPath;
})
.catch((err: any) => console.log(err));
}
@action
onPointerEnter = () => {
- let self = this;
- let audioAnnos = this.extensionDoc && DocListCast(this.extensionDoc.audioAnnotations);
+ const self = this;
+ const audioAnnos = this.extensionDoc && DocListCast(this.extensionDoc.audioAnnotations);
if (audioAnnos && audioAnnos.length && this._audioState === 0) {
- let anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
+ const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
anno.data instanceof AudioField && new Howl({
src: [anno.data.url.href],
format: ["mp3"],
@@ -267,71 +271,86 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />);
}
- @computed get content() {
- const extensionDoc = this.extensionDoc;
- if (!extensionDoc) return (null);
- // let transform = this.props.ScreenToLocalTransform().inverse();
- let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
- // var [sptX, sptY] = transform.transformPoint(0, 0);
- // let [bptX, bptY] = transform.transformPoint(pw, this.props.PanelHeight());
- // let w = bptX - sptX;
-
- let nativeWidth = (this.Document.nativeWidth || pw);
- let nativeHeight = (this.Document.nativeHeight || 0);
- let paths = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
+ @computed get nativeSize() {
+ const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
+ const nativeWidth = (this.Document.nativeWidth || pw);
+ const nativeHeight = (this.Document.nativeHeight || 1);
+ return { nativeWidth, nativeHeight };
+ }
+
+ @computed get pathInfos() {
+ const extensionDoc = this.extensionDoc!;
+ const { nativeWidth, nativeHeight } = this.nativeSize;
+ let paths = [[Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png"), nativeWidth / nativeHeight]];
// this._curSuffix = "";
// if (w > 20) {
- let alts = DocListCast(extensionDoc.Alternates);
- let altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url));
- let field = this.dataDoc[this.props.fieldKey];
+ const alts = DocListCast(extensionDoc.Alternates);
+ const altpaths = alts.filter(doc => doc.data instanceof ImageField).map(doc => [this.choosePath((doc.data as ImageField).url), doc[WidthSym]() / doc[HeightSym]()]);
+ const field = this.dataDoc[this.props.fieldKey];
// if (w < 100 && this._smallRetryCount < 10) this._curSuffix = "_s";
// else if (w < 600 && this._mediumRetryCount < 10) this._curSuffix = "_m";
// else if (this._largeRetryCount < 10) this._curSuffix = "_l";
- if (field instanceof ImageField) paths = [this.choosePath(field.url)];
+ if (field instanceof ImageField) paths = [[this.choosePath(field.url), nativeWidth / nativeHeight]];
paths.push(...altpaths);
- // }
- let interactive = InkingControl.Instance.selectedTool || this.Document.isBackground ? "" : "-interactive";
- let rotation = NumCast(this.Document.rotation, 0);
- let aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
- let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
- let srcpath = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))];
- let fadepath = paths[Math.min(paths.length - 1, 1)];
-
- !this.Document.ignoreAspect && this.resize(srcpath);
-
- return (
- <div className={`imageBox-cont${interactive}`} key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
- <div id="cf">
- <img
- key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
- src={srcpath}
- style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
- width={nativeWidth}
- ref={this._imgRef}
- onError={this.onError} />
- {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker"> <img className="imageBox-fadeaway"
+ const srcpath = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][0] as string;
+ const srcaspect = paths[Math.min(paths.length - 1, (this.Document.curPage || 0))][1] as number;
+ const fadepath = paths[Math.min(paths.length - 1, 1)][0] as string;
+ return { srcpath, srcaspect, fadepath };
+ }
+
+ @computed get content() {
+ TraceMobx();
+ const extensionDoc = this.extensionDoc;
+ if (!extensionDoc) return (null);
+
+ const { srcpath, srcaspect, fadepath } = this.pathInfos;
+ const { nativeWidth, nativeHeight } = this.nativeSize;
+ const rotation = NumCast(this.Document.rotation, 0);
+ const aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
+ const shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
+
+ !this.Document.ignoreAspect && this._resized !== srcpath && this.resize(srcpath);
+
+ return <div className="imageBox-cont" key={this.props.Document[Id]} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <div className="imageBox-fader" >
+ <img key={this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
+ src={srcpath}
+ style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
+ width={nativeWidth}
+ ref={this._imgRef}
+ onError={this.onError} />
+ {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker" style={{ width: nativeWidth, height: nativeWidth / srcaspect }}>
+ <img className="imageBox-fadeaway"
key={"fadeaway" + this._smallRetryCount + (this._mediumRetryCount << 4) + (this._largeRetryCount << 8)} // force cache to update on retrys
src={fadepath}
- style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})` }}
+ style={{ transform: `translate(0px, ${shift}px) rotate(${rotation}deg) scale(${aspect})`, }}
width={nativeWidth}
ref={this._imgRef}
onError={this.onError} /></div>}
- </div>
- <div className="imageBox-audioBackground"
- onPointerDown={this.audioDown}
- onPointerEnter={this.onPointerEnter}
- style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
- >
- <FontAwesomeIcon className="imageBox-audioFont"
- style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" />
- </div>
- {this.considerGooglePhotosLink()}
- <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
- </div>);
+ </div>
+ <div className="imageBox-audioBackground"
+ onPointerDown={this.audioDown}
+ onPointerEnter={this.onPointerEnter}
+ style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
+ >
+ <FontAwesomeIcon className="imageBox-audioFont"
+ style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" />
+ </div>
+ {this.considerGooglePhotosLink()}
+ <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
+ </div>;
}
+ contentFunc = () => [this.content];
render() {
- return (<div className={"imageBox-container"} onContextMenu={this.specificContextMenu}>
+ TraceMobx();
+ const dragging = !SelectionManager.GetIsDragging() ? "" : "-dragging";
+ return (<div className={`imageBox${dragging}`} onContextMenu={this.specificContextMenu}
+ style={{
+ transform: `scale(${this.props.ContentScaling()})`,
+ width: `${100 / this.props.ContentScaling()}%`,
+ height: `${100 / this.props.ContentScaling()}%`
+ }} >
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
@@ -340,7 +359,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
focus={this.props.focus}
isSelected={this.props.isSelected}
select={emptyFunction}
- active={this.active}
+ active={this.annotationsActive}
ContentScaling={returnOne}
whenActiveChanged={this.whenActiveChanged}
removeDocument={this.removeDocument}
@@ -352,7 +371,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
chromeCollapsed={true}>
- {() => [this.content]}
+ {this.contentFunc}
</CollectionFreeFormView>
</div >);
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 35e9e4862..234a6a9d3 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -53,30 +53,30 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
}
public static CompileKVPScript(value: string): KVPScript | undefined {
- let eq = value.startsWith("=");
+ const eq = value.startsWith("=");
value = eq ? value.substr(1) : value;
const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false;
value = dubEq ? value.substr(2) : value;
- let options: ScriptOptions = { addReturn: true, params: { this: "Doc" } };
+ const options: ScriptOptions = { addReturn: true, params: { this: "Doc", _last_: "any" }, editable: false };
if (dubEq) options.typecheck = false;
- let script = CompileScript(value, options);
+ const script = CompileScript(value, options);
if (!script.compiled) {
return undefined;
}
return { script, type: dubEq, onDelegate: eq };
}
- public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript): boolean {
+ public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean {
const { script, type, onDelegate } = kvpScript;
//const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates
- const target = onDelegate ? doc : Doc.GetProto(doc);
+ const target = forceOnDelegate || onDelegate ? doc : Doc.GetProto(doc);
let field: Field;
if (type === "computed") {
field = new ComputedField(script);
} else if (type === "script") {
field = new ScriptField(script);
} else {
- let res = script.run({ this: target }, console.log);
+ const res = script.run({ this: target }, console.log);
if (!res.success) return false;
field = res.result;
}
@@ -88,14 +88,14 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
@undoBatch
- public static SetField(doc: Doc, key: string, value: string) {
+ public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean) {
const script = this.CompileKVPScript(value);
if (!script) return false;
- return this.ApplyKVPScript(doc, key, script);
+ return this.ApplyKVPScript(doc, key, script, forceOnDelegate);
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected()) {
+ if (e.buttons === 1 && this.props.isSelected(true)) {
e.stopPropagation();
}
}
@@ -106,14 +106,14 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
rowHeight = () => 30;
createTable = () => {
- let doc = this.fieldDocToLayout;
+ const doc = this.fieldDocToLayout;
if (!doc) {
return <tr><td>Loading...</td></tr>;
}
- let realDoc = doc;
+ const realDoc = doc;
- let ids: { [key: string]: string } = {};
- let protos = Doc.GetAllPrototypes(doc);
+ const ids: { [key: string]: string } = {};
+ const protos = Doc.GetAllPrototypes(doc);
for (const proto of protos) {
Object.keys(proto).forEach(key => {
if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
@@ -122,10 +122,10 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
});
}
- let rows: JSX.Element[] = [];
+ const rows: JSX.Element[] = [];
let i = 0;
const self = this;
- for (let key of Object.keys(ids).slice().sort()) {
+ for (const key of Object.keys(ids).slice().sort()) {
rows.push(<KeyValuePair doc={realDoc} addDocTab={this.props.addDocTab} PanelWidth={this.props.PanelWidth} PanelHeight={this.rowHeight}
ref={(function () {
let oldEl: KeyValuePair | undefined;
@@ -163,7 +163,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@action
onDividerMove = (e: PointerEvent): void => {
- let nativeWidth = this._mainCont.current!.getBoundingClientRect();
+ const nativeWidth = this._mainCont.current!.getBoundingClientRect();
this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
}
@action
@@ -179,10 +179,10 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
getTemplate = async () => {
- let parent = Docs.Create.StackingDocument([], { width: 800, height: 800, title: "Template" });
+ const parent = Docs.Create.StackingDocument([], { width: 800, height: 800, title: "Template" });
parent.singleColumn = false;
parent.columnWidth = 100;
- for (let row of this.rows.filter(row => row.isChecked)) {
+ for (const row of this.rows.filter(row => row.isChecked)) {
await this.createTemplateField(parent, row);
row.uncheck();
}
@@ -190,17 +190,17 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
createTemplateField = async (parentStackingDoc: Doc, row: KeyValuePair) => {
- let metaKey = row.props.keyName;
- let sourceDoc = await Cast(this.props.Document.data, Doc);
+ const metaKey = row.props.keyName;
+ const sourceDoc = await Cast(this.props.Document.data, Doc);
if (!sourceDoc) {
return;
}
- let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
+ const fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
if (!fieldTemplate) {
return;
}
- let previousViewType = fieldTemplate.viewType;
+ const previousViewType = fieldTemplate.viewType;
Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc));
previousViewType && (fieldTemplate.viewType = previousViewType);
@@ -208,14 +208,14 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
inferType = async (data: FieldResult, metaKey: string) => {
- let options = { width: 300, height: 300, title: metaKey };
+ const options = { width: 300, height: 300, title: metaKey };
if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
return Docs.Create.TextDocument(options);
} else if (data instanceof List) {
if (data.length === 0) {
return Docs.Create.StackingDocument([], options);
}
- let first = await Cast(data[0], Doc);
+ const first = await Cast(data[0], Doc);
if (!first || !first.data) {
return Docs.Create.StackingDocument([], options);
}
@@ -235,7 +235,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
render() {
- let dividerDragger = this.splitPercentage === 0 ? (null) :
+ const dividerDragger = this.splitPercentage === 0 ? (null) :
<div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}>
<div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} />
</div>;
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 225565964..91f8bb3b0 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -5,7 +5,6 @@ import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Util
import { Docs } from '../../documents/Documents';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
-import { CollectionDockingView } from '../collections/CollectionDockingView';
import { ContextMenu } from '../ContextMenu';
import { EditableView } from "../EditableView";
import { FieldView, FieldViewProps } from './FieldView';
@@ -53,9 +52,10 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
}
render() {
- let props: FieldViewProps = {
+ const props: FieldViewProps = {
Document: this.props.doc,
DataDoc: this.props.doc,
+ LibraryPath: [],
ContainingCollectionView: undefined,
ContainingCollectionDoc: undefined,
ruleProvider: undefined,
@@ -73,7 +73,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
pinToPres: returnZero,
ContentScaling: returnOne
};
- let contents = <FieldView {...props} />;
+ const contents = <FieldView {...props} />;
// let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")";
let protoCount = 0;
let doc: Doc | undefined = props.Document;
@@ -85,9 +85,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
doc = doc.proto;
}
const parenCount = Math.max(0, protoCount - 1);
- let keyStyle = protoCount === 0 ? "black" : "blue";
+ const keyStyle = protoCount === 0 ? "black" : "blue";
- let hover = { transition: "0.3s ease opacity", opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
+ const hover = { transition: "0.3s ease opacity", opacity: this.isPointerOver || this.isChecked ? 1 : 0 };
return (
<tr className={this.props.rowStyle} onPointerEnter={action(() => this.isPointerOver = true)} onPointerLeave={action(() => this.isPointerOver = false)}>
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 963205206..c7d6f988c 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,35 +1,172 @@
-.pdfBox-cont,
-.pdfBox-cont-interactive {
+.pdfBox,
+.pdfBox-interactive {
display: inline-block;
- flex-direction: row;
+ position: absolute;
height: 100%;
- width:100%;
+ width: 100%;
overflow: hidden;
- position:absolute;
cursor:auto;
transform-origin: top left;
-}
-
-.pdfBox-title-outer {
z-index: 0;
- position: absolute;
- width: 100%;
- height: 100%;
- background: lightslategray;
- .pdfBox-cont, .pdfBox-cont-interactive{
+ .pdfBox-ui {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ pointer-events: none;
+
+ .pdfBox-overlayButton {
+ border-bottom-left-radius: 50%;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 20px;
+ background: none;
+ padding: 0;
+ position: absolute;
+ pointer-events: all;
+
+ .pdfBox-overlayButton-arrow {
+ width: 0;
+ height: 0;
+ border-top: 10px solid transparent;
+ border-bottom: 10px solid transparent;
+ border-right: 15px solid #121721;
+ transition: all 0.5s;
+ }
+
+ .pdfBox-overlayButton-iconCont {
+ background: #121721;
+ height: 20px;
+ width: 25px;
+ display: flex;
+ position: relative;
+ align-items: center;
+ justify-content: center;
+ border-radius: 3px;
+ pointer-events: all;
+ }
+ }
+
+ .pdfBox-nextIcon,
+ .pdfBox-prevIcon {
+ background: #121721;
+ height: 20px;
+ width: 25px;
+ display: flex;
+ position: relative;
+ align-items: center;
+ justify-content: center;
+ border-radius: 3px;
+ pointer-events: all;
+ padding: 0px;
+ }
+
+ .pdfBox-overlayButton:hover {
+ background: none;
+ }
+
+
+ .pdfBox-settingsCont {
+ position: absolute;
+ right: 0;
+ top: 3;
+ pointer-events: all;
+
+ .pdfBox-settingsButton {
+ border-bottom-left-radius: 50%;
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ height: 20px;
+ background: none;
+ padding: 0;
+
+ .pdfBox-settingsButton-arrow {
+ width: 0;
+ height: 0;
+ border-top: 10px solid transparent;
+ border-bottom: 10px solid transparent;
+ border-right: 15px solid #121721;
+ transition: all 0.5s;
+ }
+
+ .pdfBox-settingsButton-iconCont {
+ background: #121721;
+ height: 20px;
+ width: 25px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-left: -2px;
+ border-radius: 3px;
+ }
+ }
+
+ .pdfBox-settingsButton:hover {
+ background: none;
+ }
+
+ .pdfBox-settingsFlyout {
+ position: absolute;
+ background: #323232;
+ box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+ right: 20px;
+ border-radius: 7px;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ font-size: 14px;
+ transition: all 0.5s;
+
+ .pdfBox-settingsFlyout-title {
+ color: white;
+ }
+
+ .pdfBox-settingsFlyout-kvpInput {
+ margin-top: 20px;
+ display: grid;
+ grid-template-columns: 47.5% 5% 47.5%;
+ }
+ }
+ }
+
+ .pdfBox-overlayCont {
+ position: absolute;
+ width: calc(100% - 40px);
+ height: 20px;
+ background: #121721;
+ bottom: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden;
+ transition: left .5s;
+ pointer-events: all;
+
+ .pdfBox-searchBar {
+ width: 70%;
+ font-size: 14px;
+ }
+ }
+ }
+ .pdfBox-title-outer {
width: 150%;
height: 100%;
position: relative;
display: grid;
+ z-index: 0;
+ background: lightslategray;
+ transform-origin: top left;
.pdfBox-title {
color:lightgray;
margin-top: auto;
margin-bottom: auto;
- transform-origin: 42% -18%;
+ transform-origin: 42% 15%;
width: 100%;
transform: rotate(55deg);
- font-size: 144;
+ font-size: 200;
padding: 5%;
overflow: hidden;
display: inline-block;
@@ -40,8 +177,7 @@
}
}
-
-.pdfBox-cont {
+.pdfBox {
pointer-events: none;
.collectionFreeFormView-none {
pointer-events: none;
@@ -55,7 +191,7 @@
}
}
-.pdfBox-cont-interactive {
+.pdfBox-interactive {
pointer-events: all;
.pdfViewer-text {
.textLayer {
@@ -64,134 +200,4 @@
}
}
}
-}
-
-
-.pdfBox-settingsCont {
- position: absolute;
- right: 0;
- top: 3;
- pointer-events: all;
-
- .pdfBox-settingsButton {
- border-bottom-left-radius: 50%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- height: 30px;
- background: none;
- padding: 0;
-
- .pdfBox-settingsButton-arrow {
- width: 0;
- height: 0;
- border-top: 15px solid transparent;
- border-bottom: 15px solid transparent;
- border-right: 15px solid #121721;
- transition: all 0.5s;
- }
-
- .pdfBox-settingsButton-iconCont {
- background: #121721;
- height: 30px;
- width: 70px;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-left: -2px;
- border-radius: 3px;
- }
- }
-
- .pdfBox-settingsButton:hover {
- background: none;
- }
-
- .pdfBox-settingsFlyout {
- position: absolute;
- background: #323232;
- box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
- right: 20px;
- border-radius: 7px;
- padding: 20px;
- display: flex;
- flex-direction: column;
- font-size: 14px;
- transition: all 0.5s;
-
- .pdfBox-settingsFlyout-title {
- color: white;
- }
-
- .pdfBox-settingsFlyout-kvpInput {
- margin-top: 20px;
- display: grid;
- grid-template-columns: 47.5% 5% 47.5%;
- }
- }
-}
-
-.pdfBox-overlayCont {
- position: absolute;
- width: 100%;
- height: 40px;
- background: #121721;
- bottom: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 20px;
- overflow: hidden;
- transition: left .5s;
- pointer-events: all;
-
- .pdfBox-searchBar {
- width: 70%;
- font-size: 14px;
- }
-}
-
-.pdfBox-overlayButton {
- border-bottom-left-radius: 50%;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- height: 30px;
- background: none;
- padding: 0;
- position: absolute;
- pointer-events: all;
-
- .pdfBox-overlayButton-arrow {
- width: 0;
- height: 0;
- border-top: 15px solid transparent;
- border-bottom: 15px solid transparent;
- border-right: 15px solid #121721;
- transition: all 0.5s;
- }
-
- .pdfBox-overlayButton-iconCont,
- .pdfBox-nextIcon,
- .pdfBox-prevIcon {
- background: #121721;
- height: 30px;
- width: 70px;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-left: -2px;
- border-radius: 3px;
- }
-}
-
-.pdfBox-overlayButton:hover {
- background: none;
-}
-
-.pdfBox-nextIcon {
- left: 20; top: 5; height: 30px; position: absolute;
-}
-.pdfBox-prevIcon {
- left: 50; top: 5; height: 30px; position: absolute;
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 3baa6eb09..8370df6ba 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,13 +1,13 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, runInAction, reaction, IReactionDisposer, trace } from 'mobx';
+import { action, observable, runInAction, reaction, IReactionDisposer, trace, untracked, computed } from 'mobx';
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
-import { Opt, WidthSym, Doc } from "../../../new_fields/Doc";
+import { Opt, WidthSym, Doc, HeightSym } from "../../../new_fields/Doc";
import { makeInterface } from "../../../new_fields/Schema";
import { ScriptField } from '../../../new_fields/ScriptField';
-import { Cast } from "../../../new_fields/Types";
-import { PdfField } from "../../../new_fields/URLField";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { PdfField, URLField } from "../../../new_fields/URLField";
import { Utils } from '../../../Utils';
import { KeyCodes } from '../../northstar/utils/KeyCodes';
import { undoBatch } from '../../util/UndoManager';
@@ -21,6 +21,7 @@ import { pageSchema } from "./ImageBox";
import "./PDFBox.scss";
import React = require("react");
import { documentSchema } from '../../../new_fields/documentSchemas';
+import { url } from 'inspector';
type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>;
const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@@ -32,7 +33,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
private _valueValue: string = "";
private _scriptValue: string = "";
private _searchString: string = "";
- private _initialScale: number | undefined; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live.
+ private _initialScale: number = 0; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live.
private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title
private _pdfViewer: PDFViewer | undefined;
private _searchRef = React.createRef<HTMLInputElement>();
@@ -46,25 +47,52 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
@observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
@observable private _pageControls = false;
+ constructor(props: any) {
+ super(props);
+ this._initialScale = this.props.ScreenToLocalTransform().Scale;
+ const nw = this.Document.nativeWidth = NumCast(this.extensionDocSync.nativeWidth, NumCast(this.Document.nativeWidth, 927));
+ const nh = this.Document.nativeHeight = NumCast(this.extensionDocSync.nativeHeight, NumCast(this.Document.nativeHeight, 1200));
+ !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
+
+ const backup = "oldPath";
+ const { Document } = this.props;
+ const { url: { href } } = Cast(Document[this.props.fieldKey], PdfField)!;
+ const pathCorrectionTest = /upload\_[a-z0-9]{32}.(.*)/g;
+ const matches = pathCorrectionTest.exec(href);
+ console.log("\nHere's the { url } being fed into the outer regex:");
+ console.log(href);
+ console.log("And here's the 'properPath' build from the captured filename:\n");
+ if (matches !== null && href.startsWith(window.location.origin)) {
+ const properPath = Utils.prepend(`/files/pdfs/${matches[0]}`);
+ console.log(properPath);
+ if (!properPath.includes(href)) {
+ console.log(`The two (url and proper path) were not equal`);
+ const proto = Doc.GetProto(Document);
+ proto[this.props.fieldKey] = new PdfField(properPath);
+ proto[backup] = href;
+ } else {
+ console.log(`The two (url and proper path) were equal`);
+ }
+ } else {
+ console.log("Outer matches was null!");
+ }
+ }
+
componentWillUnmount() {
this._selectReactionDisposer && this._selectReactionDisposer();
}
componentDidMount() {
- const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
- if (pdfUrl instanceof PdfField) {
- Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf));
- }
this._selectReactionDisposer = reaction(() => this.props.isSelected(),
() => {
document.removeEventListener("keydown", this.onKeyDown);
- this.props.isSelected() && document.addEventListener("keydown", this.onKeyDown);
+ this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown);
}, { fireImmediately: true });
}
loaded = (nw: number, nh: number, np: number) => {
- this.dataDoc.numPages = np;
- this.Document.nativeWidth = nw * 96 / 72;
- this.Document.nativeHeight = nh * 96 / 72;
+ this.extensionDocSync.numPages = np;
+ this.extensionDocSync.nativeWidth = this.Document.nativeWidth = nw * 96 / 72;
+ this.extensionDocSync.nativeHeight = this.Document.nativeHeight = nh * 96 / 72;
!this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw));
}
@@ -90,7 +118,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
@undoBatch
@action
private applyFilter = () => {
- let scriptText = this._scriptValue ? this._scriptValue :
+ const scriptText = this._scriptValue ? this._scriptValue :
this._keyValue && this._valueValue ? `this.${this._keyValue} === ${this._valueValue}` : "true";
this.props.Document.filterScript = ScriptField.MakeFunction(scriptText);
}
@@ -106,53 +134,51 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => this._valueValue = e.currentTarget.value;
private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => this._scriptValue = e.currentTarget.value;
- whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
+ whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
setPdfViewer = (pdfViewer: PDFViewer) => { this._pdfViewer = pdfViewer; };
searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
settingsPanel() {
- let pageBtns = <>
+ const pageBtns = <>
<button className="pdfBox-overlayButton-iconCont" key="back" title="Page Back"
- onPointerDown={e => e.stopPropagation()} onClick={this.backPage}
- style={{ left: 50, top: 5, height: "30px", position: "absolute", pointerEvents: "all" }}>
+ onPointerDown={e => e.stopPropagation()} onClick={e => this.backPage()} style={{ left: 45, top: 5 }}>
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-left"} size="sm" />
</button>
<button className="pdfBox-overlayButton-iconCont" key="fwd" title="Page Forward"
- onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}
- style={{ left: 80, top: 5, height: "30px", position: "absolute", pointerEvents: "all" }}>
+ onPointerDown={e => e.stopPropagation()} onClick={e => this.forwardPage()} style={{ left: 45, top: 5 }}>
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-right"} size="sm" />
</button>
</>;
return !this.active() ? (null) :
(<div className="pdfBox-ui" onKeyDown={e => e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true}
- onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none", position: "absolute", width: "100%", height: "100%", zIndex: 1, pointerEvents: "none" }}>
+ onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}>
<div className="pdfBox-overlayCont" key="cont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title="Open Search Bar" />
<input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} />
<button title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}>
<FontAwesomeIcon icon="search" size="sm" color="white" /></button>
<button className="pdfBox-prevIcon " title="Previous Annotation" onClick={this.prevAnnotation} >
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="sm" />
+ <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="lg" />
</button>
<button className="pdfBox-nextIcon" title="Next Annotation" onClick={this.nextAnnotation} >
- <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="sm" />
+ <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="lg" />
</button>
</div>
- <button className="pdfBox-overlayButton" key="search" onClick={action(() => this._searching = !this._searching)} title="Open Search Bar" style={{ bottom: 8, right: 0 }}>
+ <button className="pdfBox-overlayButton" key="search" onClick={action(() => this._searching = !this._searching)} title="Open Search Bar" style={{ bottom: 0, right: 0 }}>
<div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
<div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white", padding: 5 }} icon={this._searching ? "times" : "search"} size="3x" /></div>
+ <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="lg" /></div>
</button>
<input value={`${(this.Document.curPage || 1)}`}
onChange={e => this.gotoPage(Number(e.currentTarget.value))}
- style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }}
+ style={{ left: 5, top: 5, height: "20px", width: "20px", position: "absolute", pointerEvents: "all" }}
onClick={action(() => this._pageControls = !this._pageControls)} />
{this._pageControls ? pageBtns : (null)}
<div className="pdfBox-settingsCont" key="settings" onPointerDown={(e) => e.stopPropagation()}>
<button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings" >
<div className="pdfBox-settingsButton-arrow" style={{ transform: `scaleX(${this._flyout ? -1 : 1})` }} />
<div className="pdfBox-settingsButton-iconCont">
- <FontAwesomeIcon style={{ color: "white", padding: 5 }} icon="cog" size="3x" />
+ <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="lg" />
</div>
</button>
<div className="pdfBox-settingsFlyout" style={{ right: `${this._flyout ? 20 : -600}px` }} >
@@ -183,46 +209,58 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
specificContextMenu = (e: React.MouseEvent): void => {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
- let funcs: ContextMenuProps[] = [];
+ const funcs: ContextMenuProps[] = [];
pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" });
funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" });
}
- render() {
- trace();
+ @computed get contentScaling() { return this.props.ContentScaling(); }
+ @computed get renderTitleBox() {
+ const classname = "pdfBox" + (this.active() ? "-interactive" : "");
+ return <div className={classname} style={{
+ width: !this.props.Document.fitWidth ? this.Document.nativeWidth || 0 : `${100 / this.contentScaling}%`,
+ height: !this.props.Document.fitWidth ? this.Document.nativeHeight || 0 : `${100 / this.contentScaling}%`,
+ transform: `scale(${this.contentScaling})`
+ }} >
+ <div className="pdfBox-title-outer">
+ <strong className="pdfBox-title" >{this.props.Document.title}</strong>
+ </div>
+ </div>;
+ }
+
+ isChildActive = (outsideReaction?: boolean) => this._isChildActive;
+ @computed get renderPdfView() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
- let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
- let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf;
- if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale;
+ return <div className={"pdfBox"} onContextMenu={this.specificContextMenu}>
+ <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
+ setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
+ renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
+ addDocTab={this.props.addDocTab} focus={this.props.focus}
+ pinToPres={this.props.pinToPres} addDocument={this.addDocument}
+ Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
+ isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
+ isChildActive={this.isChildActive}
+ fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
+ {this.settingsPanel()}
+ </div>;
+ }
+
+ _pdfjsRequested = false;
+ render() {
+ const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true;
- return (!this.extensionDoc || noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ?
- <div className="pdfBox-title-outer" >
- <div className={classname} >
- <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
- </div>
- </div> :
- <div className={classname} style={{
- width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
- height: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
- transform: `scale(${this.props.Document.fitWidth ? this.props.ContentScaling() : 1})`
- }} onContextMenu={this.specificContextMenu} onPointerDown={e => {
- let hit = document.elementFromPoint(e.clientX, e.clientY);
- if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation
- e.button === 0 && e.stopPropagation();
- }
- }}>
- <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
- setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
- renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
- Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
- addDocTab={this.props.addDocTab} focus={this.props.focus}
- pinToPres={this.props.pinToPres} addDocument={this.addDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
- isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
- fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
- {this.settingsPanel()}
- </div>);
+ if (pdfUrl && this.extensionDoc && (this._everActive || (this.extensionDoc.nativeWidth && this.props.ScreenToLocalTransform().Scale < 2.5))) {
+ if (pdfUrl instanceof PdfField && this._pdf) {
+ return this.renderPdfView;
+ }
+ if (!this._pdfjsRequested) {
+ this._pdfjsRequested = true;
+ Pdfjs.getDocument(pdfUrl.url.href).promise.then(pdf => runInAction(() => this._pdf = pdf));
+ }
+ }
+ return this.renderTitleBox;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index cbb83b511..1e6894f37 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -42,7 +42,7 @@ export class PresBox extends React.Component<FieldViewProps> {
if (value) {
value.forEach((item, i) => {
if (item instanceof Doc && item.type !== DocumentType.PRESELEMENT) {
- let pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
+ const pinDoc = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent" });
Doc.GetProto(pinDoc).presentationTargetDoc = item;
Doc.GetProto(pinDoc).title = ComputedField.MakeFunction('(this.presentationTargetDoc instanceof Doc) && this.presentationTargetDoc.title.toString()');
value.splice(i, 1, pinDoc);
@@ -61,9 +61,9 @@ export class PresBox extends React.Component<FieldViewProps> {
next = async () => {
const current = NumCast(this.props.Document.selectedDoc);
//asking to get document at current index
- let docAtCurrentNext = await this.getDocAtIndex(current + 1);
+ const docAtCurrentNext = await this.getDocAtIndex(current + 1);
if (docAtCurrentNext !== undefined) {
- let presDocs = DocListCast(this.props.Document[this.props.fieldKey]);
+ const presDocs = DocListCast(this.props.Document[this.props.fieldKey]);
let nextSelected = current + 1;
for (; nextSelected < presDocs.length - 1; nextSelected++) {
@@ -78,15 +78,15 @@ export class PresBox extends React.Component<FieldViewProps> {
back = async () => {
const current = NumCast(this.props.Document.selectedDoc);
//requesting for the doc at current index
- let docAtCurrent = await this.getDocAtIndex(current);
+ const docAtCurrent = await this.getDocAtIndex(current);
if (docAtCurrent !== undefined) {
//asking for its presentation id.
let prevSelected = current;
let zoomOut: boolean = false;
- let presDocs = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
- let currentsArray: Doc[] = [];
+ const presDocs = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
+ const currentsArray: Doc[] = [];
for (; presDocs && prevSelected > 0 && presDocs[prevSelected].groupButton; prevSelected--) {
currentsArray.push(presDocs[prevSelected]);
}
@@ -104,8 +104,8 @@ export class PresBox extends React.Component<FieldViewProps> {
//If so making sure to zoom out, which goes back to state before zooming action
if (current > 0) {
if (zoomOut || docAtCurrent.showButton) {
- let prevScale = NumCast(this.childDocs[prevSelected].viewScale, null);
- let curScale = DocumentManager.Instance.getScaleOfDocView(this.childDocs[current]);
+ const prevScale = NumCast(this.childDocs[prevSelected].viewScale, null);
+ const curScale = DocumentManager.Instance.getScaleOfDocView(this.childDocs[current]);
if (prevScale !== undefined && prevScale !== curScale) {
DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
}
@@ -162,13 +162,13 @@ export class PresBox extends React.Component<FieldViewProps> {
* te option open, navigates to that element.
*/
navigateToElement = async (curDoc: Doc, fromDocIndex: number) => {
- let fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc;
+ const fromDoc = this.childDocs[fromDocIndex].presentationTargetDoc as Doc;
let docToJump = curDoc;
let willZoom = false;
- let presDocs = DocListCast(this.props.Document[this.props.fieldKey]);
+ const presDocs = DocListCast(this.props.Document[this.props.fieldKey]);
let nextSelected = presDocs.indexOf(curDoc);
- let currentDocGroups: Doc[] = [];
+ const currentDocGroups: Doc[] = [];
for (; nextSelected < presDocs.length - 1; nextSelected++) {
if (!presDocs[nextSelected + 1].groupButton) {
break;
@@ -190,11 +190,11 @@ export class PresBox extends React.Component<FieldViewProps> {
//docToJump stayed same meaning, it was not in the group or was the last element in the group
if (docToJump === curDoc) {
//checking if curDoc has navigation open
- let target = await curDoc.presentationTargetDoc as Doc;
+ const target = await curDoc.presentationTargetDoc as Doc;
if (curDoc.navButton) {
DocumentManager.Instance.jumpToDocument(target, false);
} else if (curDoc.showButton) {
- let curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
+ const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
//awaiting jump so that new scale can be found, since jumping is async
await DocumentManager.Instance.jumpToDocument(target, true);
curDoc.viewScale = DocumentManager.Instance.getScaleOfDocView(target);
@@ -207,11 +207,11 @@ export class PresBox extends React.Component<FieldViewProps> {
}
return;
}
- let curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
+ const curScale = DocumentManager.Instance.getScaleOfDocView(fromDoc);
//awaiting jump so that new scale can be found, since jumping is async
await DocumentManager.Instance.jumpToDocument(await docToJump.presentationTargetDoc as Doc, willZoom);
- let newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc);
+ const newScale = DocumentManager.Instance.getScaleOfDocView(await curDoc.presentationTargetDoc as Doc);
curDoc.viewScale = newScale;
//saving the scale that user was on
if (curScale !== 1) {
@@ -238,7 +238,7 @@ export class PresBox extends React.Component<FieldViewProps> {
public removeDocument = (doc: Doc) => {
const value = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)));
if (value) {
- let indexOfDoc = value.indexOf(doc);
+ const indexOfDoc = value.indexOf(doc);
if (indexOfDoc !== - 1) {
value.splice(indexOfDoc, 1)[0];
return true;
@@ -337,13 +337,13 @@ export class PresBox extends React.Component<FieldViewProps> {
@action
initializeScaleViews = (docList: Doc[], viewtype: number) => {
this.props.Document.chromeStatus = "disabled";
- let hgt = (viewtype === CollectionViewType.Tree) ? 50 : 72;
+ const hgt = (viewtype === CollectionViewType.Tree) ? 50 : 72;
docList.forEach((doc: Doc) => {
doc.presBox = this.props.Document;
doc.presBoxKey = this.props.fieldKey;
doc.collapsedHeight = hgt;
doc.height = ComputedField.MakeFunction("this.collapsedHeight + Number(this.embedOpen ? 100:0)");
- let curScale = NumCast(doc.viewScale, null);
+ const curScale = NumCast(doc.viewScale, null);
if (curScale === undefined) {
doc.viewScale = 1;
}
@@ -352,7 +352,7 @@ export class PresBox extends React.Component<FieldViewProps> {
selectElement = (doc: Doc) => {
- let index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc);
+ const index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc);
index !== -1 && this.gotoDocument(index, NumCast(this.props.Document.selectedDoc));
}
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 5829c1bd9..fabbf5196 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,5 +1,9 @@
-.videoBox-container {
+.videoBox {
pointer-events: all;
+ transform-origin: top left;
+ .videoBox-viewer {
+ opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger
+ }
.inkingCanvas-paths-markers {
opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 53baea4ae..376d27380 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -9,10 +9,9 @@ import { Doc } from "../../../new_fields/Doc";
import { InkTool } from "../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
-import { RouteStore } from "../../../server/RouteStore";
-import { emptyFunction, returnOne, Utils } from "../../../Utils";
+import { Utils, emptyFunction, returnOne } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { ContextMenu } from "../ContextMenu";
@@ -23,7 +22,7 @@ import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
import "./VideoBox.scss";
import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas";
-var path = require('path');
+const path = require('path');
export const timeSchema = createSchema({
currentTimecode: "number", // the current time of a video or other linear, time-based document. Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first
@@ -55,9 +54,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
videoLoad = () => {
- let aspect = this.player!.videoWidth / this.player!.videoHeight;
- var nativeWidth = (this.Document.nativeWidth || 0);
- var nativeHeight = (this.Document.nativeHeight || 0);
+ const aspect = this.player!.videoWidth / this.player!.videoHeight;
+ const nativeWidth = (this.Document.nativeWidth || 0);
+ const nativeHeight = (this.Document.nativeHeight || 0);
if (!nativeWidth || !nativeHeight) {
if (!this.Document.nativeWidth) this.Document.nativeWidth = this.player!.videoWidth;
this.Document.nativeHeight = (this.Document.nativeWidth || 0) / aspect;
@@ -102,12 +101,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
@action public Snapshot() {
- let width = this.Document.width || 0;
- let height = this.Document.height || 0;
- var canvas = document.createElement('canvas');
+ const width = this.Document.width || 0;
+ const height = this.Document.height || 0;
+ const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
- var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+ const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
if (ctx) {
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "blue";
@@ -116,20 +115,20 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
if (!this._videoRef) { // can't find a way to take snapshots of videos
- let b = Docs.Create.ButtonDocument({
+ const b = Docs.Create.ButtonDocument({
x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString()
});
b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`);
} else {
//convert to desired file format
- var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+ const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
// if you want to preview the captured image,
- let filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_")));
+ const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_")));
VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
if (returnedFilename) {
- let url = this.choosePath(Utils.prepend(returnedFilename));
- let imageSummary = Docs.Create.ImageDocument(url, {
+ const url = this.choosePath(Utils.prepend(returnedFilename));
+ const imageSummary = Docs.Create.ImageDocument(url, {
x: (this.Document.x || 0) + width, y: (this.Document.y || 0),
width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-"
});
@@ -151,9 +150,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
if (this.props.setVideoBox) this.props.setVideoBox(this);
if (this.youtubeVideoId) {
- let youtubeaspect = 400 / 315;
- var nativeWidth = (this.Document.nativeWidth || 0);
- var nativeHeight = (this.Document.nativeHeight || 0);
+ const youtubeaspect = 400 / 315;
+ const nativeWidth = (this.Document.nativeWidth || 0);
+ const nativeHeight = (this.Document.nativeHeight || 0);
if (!nativeWidth || !nativeHeight) {
if (!this.Document.nativeWidth) this.Document.nativeWidth = 600;
this.Document.nativeHeight = (this.Document.nativeWidth || 0) / youtubeaspect;
@@ -182,7 +181,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
public static async convertDataUri(imageUri: string, returnedFilename: string) {
try {
- let posting = Utils.prepend(RouteStore.dataUriToImage);
+ const posting = Utils.prepend("/uploadURI");
const returnedUri = await rp.post(posting, {
body: {
uri: imageUri,
@@ -197,10 +196,10 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
}
specificContextMenu = (e: React.MouseEvent): void => {
- let field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
if (field) {
- let url = field.url.href;
- let subitems: ContextMenuProps[] = [];
+ const url = field.url.href;
+ const subitems: ContextMenuProps[] = [];
subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" });
subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" });
subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" });
@@ -209,9 +208,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
@computed get content() {
- let field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
- let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
- let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
+ const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
+ const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
<video className={`${style}`} key="video" ref={this.setVideoRef} onCanPlay={this.videoLoad} controls={VideoBox._showControls}
onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} onClick={e => e.preventDefault()}>
@@ -221,7 +220,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
@computed get youtubeVideoId() {
- let field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
+ const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
return field && field.url.href.indexOf("youtube") !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split("/")) : "";
}
@@ -232,9 +231,9 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
else this._youtubeContentCreated = false;
- let iframe = e.target;
+ const iframe = e.target;
let started = true;
- let onYoutubePlayerStateChange = (event: any) => runInAction(() => {
+ const onYoutubePlayerStateChange = (event: any) => runInAction(() => {
if (started && event.data === YT.PlayerState.PLAYING) {
started = false;
this._youtubePlayer && this._youtubePlayer.unMute();
@@ -244,12 +243,12 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false);
if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false);
});
- let onYoutubePlayerReady = (event: any) => {
+ const onYoutubePlayerReady = (event: any) => {
this._reactionDisposer && this._reactionDisposer();
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0));
this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
- let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
+ const interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting;
iframe.style.pointerEvents = interactive ? "all" : "none";
}, { fireImmediately: true });
};
@@ -262,20 +261,20 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
}
private get uIButtons() {
- let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
- let curTime = (this.Document.currentTimecode || 0);
- return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling})` }}>
+ const scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+ const curTime = (this.Document.currentTimecode || 0);
+ return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} >
<span>{"" + Math.round(curTime)}</span>
<span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
</div>,
- <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} style={{ transform: `scale(${scaling})` }}>
+ <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} >
<FontAwesomeIcon icon="camera" size="lg" />
</div>,
VideoBox._showControls ? (null) : [
- <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling})` }}>
+ <div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} >
<FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
</div>,
- <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling})` }}>
+ <div className="videoBox-full" key="full" onPointerDown={this.onFullDown} >
F
</div>
]]);
@@ -319,8 +318,8 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
@computed get youtubeContent() {
this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
- let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
- let start = untracked(() => Math.round(this.Document.currentTimecode || 0));
+ const style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : "");
+ const start = untracked(() => Math.round(this.Document.currentTimecode || 0));
return <iframe key={this._youtubeIframeId} id={`${this.youtubeVideoId + this._youtubeIframeId}-player`}
onLoad={this.youtubeIframeLoaded} className={`${style}`} width={(this.Document.nativeWidth || 640)} height={(this.Document.nativeHeight || 390)}
src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />;
@@ -328,35 +327,39 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
@action.bound
addDocumentWithTimestamp(doc: Doc): boolean {
- var curTime = (this.Document.currentTimecode || -1);
+ const curTime = (this.Document.currentTimecode || -1);
curTime !== -1 && (doc.displayTimecode = curTime);
return this.addDocument(doc);
}
+ contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
render() {
- return (<div className={"videoBox-container"} onContextMenu={this.specificContextMenu}>
- <CollectionFreeFormView {...this.props}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- annotationsKey={this.annotationsKey}
- focus={this.props.focus}
- isSelected={this.props.isSelected}
- isAnnotationOverlay={true}
- select={emptyFunction}
- active={this.active}
- ContentScaling={returnOne}
- whenActiveChanged={this.whenActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocumentWithTimestamp}
- CollectionView={undefined}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- ruleProvider={undefined}
- renderDepth={this.props.renderDepth + 1}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- chromeCollapsed={true}>
- {() => [this.youtubeVideoId ? this.youtubeContent : this.content]}
- </CollectionFreeFormView>
+ return (<div className="videoBox" onContextMenu={this.specificContextMenu}
+ style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+ <div className="videoBox-viewer" >
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={this.props.PanelHeight}
+ PanelWidth={this.props.PanelWidth}
+ annotationsKey={this.annotationsKey}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ active={this.annotationsActive}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocumentWithTimestamp}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ ruleProvider={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ chromeCollapsed={true}>
+ {this.contentFunc}
+ </CollectionFreeFormView>
+ </div>
{this.uIButtons}
</div >);
}
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 5af743859..b35ea0bb0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -36,11 +36,11 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
componentWillMount() {
- let field = Cast(this.props.Document[this.props.fieldKey], WebField);
+ const field = Cast(this.props.Document[this.props.fieldKey], WebField);
if (field && field.url.href.indexOf("youtube") !== -1) {
- let youtubeaspect = 400 / 315;
- var nativeWidth = NumCast(this.layoutDoc.nativeWidth);
- var nativeHeight = NumCast(this.layoutDoc.nativeHeight);
+ const youtubeaspect = 400 / 315;
+ const nativeWidth = NumCast(this.layoutDoc.nativeWidth);
+ const nativeHeight = NumCast(this.layoutDoc.nativeHeight);
if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) {
if (!nativeWidth) this.layoutDoc.nativeWidth = 600;
this.layoutDoc.nativeHeight = NumCast(this.layoutDoc.nativeWidth) / youtubeaspect;
@@ -65,7 +65,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
@action
setURL() {
- let urlField: FieldResult<WebField> = Cast(this.props.Document.data, WebField);
+ const urlField: FieldResult<WebField> = Cast(this.props.Document.data, WebField);
if (urlField) this.url = urlField.url.toString();
else this.url = "";
}
@@ -80,10 +80,10 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
switchToText = () => {
let url: string = "";
- let field = Cast(this.props.Document[this.props.fieldKey], WebField);
+ const field = Cast(this.props.Document[this.props.fieldKey], WebField);
if (field) url = field.url.href;
- let newBox = Docs.Create.TextDocument({
+ const newBox = Docs.Create.TextDocument({
x: NumCast(this.props.Document.x),
y: NumCast(this.props.Document.y),
title: url,
@@ -167,7 +167,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
@computed
get content() {
- let field = this.dataDoc[this.props.fieldKey];
+ const field = this.dataDoc[this.props.fieldKey];
let view;
if (field instanceof HtmlField) {
view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
@@ -176,15 +176,15 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
} else {
view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
}
- let content =
+ const content =
<div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
{this.urlEditor()}
{view}
</div>;
- let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+ const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
- let classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+ const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
<>
<div className={classname} >
@@ -194,7 +194,7 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
</>);
}
render() {
- return (<div className={"imageBox-container"} >
+ return (<div className={"webBox-container"} >
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}