aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/ContextMenu.scss4
-rw-r--r--src/client/views/ContextMenu.tsx5
-rw-r--r--src/client/views/ContextMenuItem.tsx12
-rw-r--r--src/client/views/DocumentDecorations.scss8
-rw-r--r--src/client/views/DocumentDecorations.tsx238
-rw-r--r--src/client/views/EditableView.tsx33
-rw-r--r--src/client/views/GlobalKeyHandler.ts6
-rw-r--r--src/client/views/InkingControl.tsx38
-rw-r--r--src/client/views/Main.tsx1
-rw-r--r--src/client/views/MainOverlayTextBox.tsx17
-rw-r--r--src/client/views/MainView.tsx97
-rw-r--r--src/client/views/MetadataEntryMenu.scss18
-rw-r--r--src/client/views/MetadataEntryMenu.tsx93
-rw-r--r--src/client/views/OverlayView.scss5
-rw-r--r--src/client/views/OverlayView.tsx73
-rw-r--r--src/client/views/PreviewCursor.tsx91
-rw-r--r--src/client/views/ScriptBox.tsx42
-rw-r--r--src/client/views/SearchBox.tsx183
-rw-r--r--src/client/views/SearchItem.tsx67
-rw-r--r--src/client/views/TemplateMenu.tsx65
-rw-r--r--src/client/views/Templates.tsx38
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx3
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx40
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx7
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx11
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx48
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx27
-rw-r--r--src/client/views/collections/CollectionSubView.tsx22
-rw-r--r--src/client/views/collections/CollectionTreeView.scss13
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx172
-rw-r--r--src/client/views/collections/CollectionView.tsx24
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss74
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx270
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx9
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx239
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx63
-rw-r--r--src/client/views/linking/LinkEditor.scss (renamed from src/client/views/nodes/LinkEditor.scss)0
-rw-r--r--src/client/views/linking/LinkEditor.tsx (renamed from src/client/views/nodes/LinkEditor.tsx)0
-rw-r--r--src/client/views/linking/LinkFollowBox.scss93
-rw-r--r--src/client/views/linking/LinkFollowBox.tsx611
-rw-r--r--src/client/views/linking/LinkMenu.scss (renamed from src/client/views/nodes/LinkMenu.scss)0
-rw-r--r--src/client/views/linking/LinkMenu.tsx (renamed from src/client/views/nodes/LinkMenu.tsx)6
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx (renamed from src/client/views/nodes/LinkMenuGroup.tsx)12
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx (renamed from src/client/views/nodes/LinkMenuItem.tsx)88
-rw-r--r--src/client/views/nodes/ButtonBox.scss19
-rw-r--r--src/client/views/nodes/ButtonBox.tsx74
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx60
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx7
-rw-r--r--src/client/views/nodes/DocumentView.tsx461
-rw-r--r--src/client/views/nodes/FieldView.tsx8
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss112
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx602
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.scss34
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx152
-rw-r--r--src/client/views/nodes/ImageBox.scss1
-rw-r--r--src/client/views/nodes/ImageBox.tsx19
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx31
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx6
-rw-r--r--src/client/views/nodes/PDFBox.tsx17
-rw-r--r--src/client/views/nodes/PresBox.tsx529
-rw-r--r--src/client/views/nodes/VideoBox.tsx17
-rw-r--r--src/client/views/nodes/WebBox.scss11
-rw-r--r--src/client/views/nodes/WebBox.tsx64
-rw-r--r--src/client/views/pdf/Annotation.tsx33
-rw-r--r--src/client/views/pdf/PDFViewer.tsx37
-rw-r--r--src/client/views/pdf/Page.tsx18
-rw-r--r--src/client/views/presentationview/PresentationElement.tsx616
-rw-r--r--src/client/views/presentationview/PresentationList.tsx52
-rw-r--r--src/client/views/presentationview/PresentationView.scss20
-rw-r--r--src/client/views/presentationview/PresentationView.tsx994
-rw-r--r--src/client/views/search/FilterBox.tsx2
-rw-r--r--src/client/views/search/IconBar.tsx1
-rw-r--r--src/client/views/search/IconButton.tsx2
-rw-r--r--src/client/views/search/SearchBox.tsx42
-rw-r--r--src/client/views/search/SearchItem.tsx74
79 files changed, 4115 insertions, 2983 deletions
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 6c619fbe1..d3286aa22 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -125,7 +125,9 @@
}
.icon-background {
- pointer-events: none;
+ pointer-events: all;
+ height:100%;
+ margin-top: 15px;
background-color: transparent;
width: 35px;
text-align: center;
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 760736501..68b97f2b6 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -246,10 +246,11 @@ export class ContextMenu extends React.Component {
this.selectedIndex--;
}
e.preventDefault();
- } else if (e.key === "Enter") {
+ } else if (e.key === "Enter" || e.key === "Tab") {
const item = this.flatItems[this.selectedIndex];
- item.event();
+ item && item.event();
this.closeMenu();
+ e.preventDefault();
}
}
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 90f7be33f..330b94afa 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -10,7 +10,7 @@ library.add(faAngleRight);
export interface OriginalMenuProps {
description: string;
- event: () => void;
+ event: (stuff?: any) => void;
undoable?: boolean;
icon: IconProp; //maybe should be optional (icon?)
closeMenu?: () => void;
@@ -44,7 +44,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
if (this.props.undoable !== false) {
batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`);
}
- await this.props.event();
+ await this.props.event({ x: e.clientX, y: e.clientY });
batch && batch.end();
}
}
@@ -89,17 +89,17 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
);
} else if ("subitems" in this.props) {
let submenu = !this.overItem ? (null) :
- <div className="contextMenu-subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+ <div className="contextMenu-subMenu-cont" style={{ marginLeft: "25%", left: "0px" }}>
{this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />)}
</div>;
return (
- <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onMouseEnter={this.onPointerEnter} onMouseLeave={this.onPointerLeave}>
+ <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onMouseLeave={this.onPointerLeave} onMouseEnter={this.onPointerEnter}>
{this.props.icon ? (
- <span className="icon-background">
+ <span className="icon-background" onMouseEnter={this.onPointerLeave}>
<FontAwesomeIcon icon={this.props.icon} size="sm" />
</span>
) : null}
- <div className="contextMenu-description">
+ <div className="contextMenu-description" onMouseEnter={this.onPointerEnter} >
{this.props.description}
<FontAwesomeIcon icon={faAngleRight} size="lg" style={{ position: "absolute", right: "10px" }} />
</div>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 3627edaae..4ab5d733f 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -257,11 +257,15 @@ $linkGap : 3px;
padding: 2px 12px;
list-style: none;
- .templateToggle {
+ .templateToggle, .chromeToggle {
text-align: left;
}
input {
margin-right: 10px;
}
-} \ No newline at end of file
+}
+
+@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
+@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
+@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index aae7f0d3f..ac103b2ea 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,14 +1,14 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faLink, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { library, IconProp } from '@fortawesome/fontawesome-svg-core';
+import { faLink, faTag, faTimes, faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faStopCircle, faCloudUploadAlt, faSyncAlt, faShare } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, reaction, runInAction } from "mobx";
+import { action, computed, observable, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCastAsync } from "../../new_fields/Doc";
import { List } from "../../new_fields/List";
import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
import { URLField } from '../../new_fields/URLField';
import { emptyFunction, Utils } from "../../Utils";
-import { Docs } from "../documents/Documents";
+import { Docs, DocUtils } from "../documents/Documents";
import { DocumentManager } from "../util/DocumentManager";
import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
@@ -18,18 +18,20 @@ import { CollectionView } from "./collections/CollectionView";
import './DocumentDecorations.scss';
import { DocumentView, PositionDocument } from "./nodes/DocumentView";
import { FieldView } from "./nodes/FieldView";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
import { IconBox } from "./nodes/IconBox";
-import { LinkMenu } from "./nodes/LinkMenu";
+import { LinkMenu } from "./linking/LinkMenu";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
import { RichTextField } from '../../new_fields/RichTextField';
import { LinkManager } from '../util/LinkManager';
-import { ObjectField } from '../../new_fields/ObjectField';
import { MetadataEntryMenu } from './MetadataEntryMenu';
import { ImageBox } from './nodes/ImageBox';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
+import { ObjectField } from '../../new_fields/ObjectField';
+import { DocServer } from '../DocServer';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -37,6 +39,16 @@ export const Flyout = higflyout.default;
library.add(faLink);
library.add(faTag);
library.add(faTimes);
+library.add(faArrowAltCircleDown);
+library.add(faArrowAltCircleUp);
+library.add(faStopCircle);
+library.add(faCheckCircle);
+library.add(faCloudUploadAlt);
+library.add(faSyncAlt);
+library.add(faShare);
+
+const cloud: IconProp = "cloud-upload-alt";
+const fetch: IconProp = "sync-alt";
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -68,6 +80,52 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@observable public Interacting = false;
@observable private _isMoving = false;
+ @observable public pushIcon: IconProp = "arrow-alt-circle-up";
+ @observable public pullIcon: IconProp = "arrow-alt-circle-down";
+ @observable public pullColor: string = "white";
+ @observable public isAnimatingFetch = false;
+ @observable public openHover = false;
+ public pullColorAnimating = false;
+
+ private pullAnimating = false;
+ private pushAnimating = false;
+
+ public startPullOutcome = action((success: boolean) => {
+ if (!this.pullAnimating) {
+ this.pullAnimating = true;
+ this.pullIcon = success ? "check-circle" : "stop-circle";
+ setTimeout(() => runInAction(() => {
+ this.pullIcon = "arrow-alt-circle-down";
+ this.pullAnimating = false;
+ }), 1000);
+ }
+ });
+
+ public startPushOutcome = action((success: boolean) => {
+ if (!this.pushAnimating) {
+ this.pushAnimating = true;
+ this.pushIcon = success ? "check-circle" : "stop-circle";
+ setTimeout(() => runInAction(() => {
+ this.pushIcon = "arrow-alt-circle-up";
+ this.pushAnimating = false;
+ }), 1000);
+ }
+ });
+
+ public setPullState = action((unchanged: boolean) => {
+ this.isAnimatingFetch = false;
+ if (!this.pullColorAnimating) {
+ this.pullColorAnimating = true;
+ this.pullColor = unchanged ? "lawngreen" : "red";
+ setTimeout(this.clearPullColor, 1000);
+ }
+ });
+
+ private clearPullColor = action(() => {
+ this.pullColor = "white";
+ this.pullColorAnimating = false;
+ });
+
constructor(props: Readonly<{}>) {
super(props);
DocumentDecorations.Instance = this;
@@ -85,17 +143,29 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (text[0] === '#') {
this._fieldKey = text.slice(1, text.length);
this._title = this.selectionTitle;
+ } else if (text.startsWith("::")) {
+ let targetID = text.slice(2, text.length);
+ let promoteDoc = SelectionManager.SelectedDocuments()[0];
+ DocUtils.Publish(promoteDoc.props.Document, targetID, promoteDoc.props.addDocument, promoteDoc.props.removeDocument);
} else if (text.startsWith(">")) {
let fieldTemplateView = SelectionManager.SelectedDocuments()[0];
SelectionManager.DeselectAll();
let fieldTemplate = fieldTemplateView.props.Document;
- let docTemplate = fieldTemplateView.props.ContainingCollectionView!.props.Document;
- let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length);
- let proto = Doc.GetProto(docTemplate);
- Doc.MakeTemplate(fieldTemplate, metaKey, proto);
- if (text.startsWith(">>")) {
- proto.detailedLayout = proto.layout;
- proto.miniLayout = ImageBox.LayoutString(metaKey);
+ let containerView = fieldTemplateView.props.ContainingCollectionView;
+ if (containerView) {
+ let docTemplate = containerView.props.Document;
+ let metaKey = text.startsWith(">>") ? text.slice(2, text.length) : text.slice(1, text.length);
+ let proto = Doc.GetProto(docTemplate);
+ if (metaKey !== containerView.props.fieldKey && containerView.props.DataDoc) {
+ const fd = fieldTemplate.data;
+ fd instanceof ObjectField && (Doc.GetProto(containerView.props.DataDoc)[metaKey] = ObjectField.MakeCopy(fd));
+ }
+ fieldTemplate.title = metaKey;
+ Doc.MakeMetadataFieldTemplate(fieldTemplate, proto);
+ if (text.startsWith(">>")) {
+ proto.detailedLayout = proto.layout;
+ proto.miniLayout = ImageBox.LayoutString(metaKey);
+ }
}
}
else {
@@ -144,14 +214,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this.onBackgroundUp(e);
}
+ @observable _forceUpdate = 0;
+ _lastBox = { x: 0, y: 0, r: 0, b: 0 };
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
- return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
+ let x = this._forceUpdate;
+ this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.renderDepth === 0 ||
Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
return bounds;
}
let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
+ if (transform.TranslateX === 0 && transform.TranslateY === 0) {
+ setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...
+ return this._lastBox;
+ }
+
var [sptX, sptY] = transform.transformPoint(0, 0);
let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
return {
@@ -159,6 +237,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
};
}, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+ return this._lastBox;
}
onBackgroundDown = (e: React.PointerEvent): void => {
@@ -218,7 +297,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
onCloseUp = async (e: PointerEvent) => {
e.stopPropagation();
if (e.button === 0) {
- const recent = await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc);
+ const recent = Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc;
SelectionManager.SelectedDocuments().map(dv => {
recent && Doc.AddDocToList(recent, "data", dv.props.Document, undefined, true, true);
dv.props.removeDocument && dv.props.removeDocument(dv.props.Document);
@@ -286,14 +365,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
let docViews = SelectionManager.ViewsSortedVertically();
let topDocView = docViews[0];
- let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
- if (ind !== -1) {
- topDocView.templates.splice(ind, 1);
- topDocView.props.Document.subBulletDocs = undefined;
- } else {
- topDocView.addTemplate(Templates.Bullet);
- topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
- }
+ topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
}
}
this._removeIcon = false;
@@ -332,8 +404,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
moveIconDoc(iconDoc: Doc) {
let selView = SelectionManager.SelectedDocuments()[0];
- let zoom = NumCast(selView.props.Document.zoomBasis, 1);
- let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).scale(1 / zoom).
+ let where = (selView.props.ScreenToLocalTransform()).scale(selView.props.ContentScaling()).
transformPoint(this._minimizedX - 12, this._minimizedY - 12);
iconDoc.x = where[0] + NumCast(selView.props.Document.x);
iconDoc.y = where[1] + NumCast(selView.props.Document.y);
@@ -352,16 +423,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.addEventListener("pointermove", this.onRadiusMove);
document.addEventListener("pointerup", this.onRadiusUp);
}
- if (!this._isMoving) {
- SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
- map(d => d.borderRounding = "0%");
- }
}
onRadiusMove = (e: PointerEvent): void => {
this._isMoving = true;
let dist = Math.sqrt((e.clientX - this._radiusDown[0]) * (e.clientX - this._radiusDown[0]) + (e.clientY - this._radiusDown[1]) * (e.clientY - this._radiusDown[1]));
- SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
+ dist = dist < 3 ? 0 : dist;
+ let usingRule = false;
+ SelectionManager.SelectedDocuments().map(dv => {
+ let cv = dv.props.ContainingCollectionView;
+ let ruleProvider = cv && cv.props.ruleProvider;
+ let heading = NumCast(dv.props.Document.heading);
+ ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`);
+ usingRule = usingRule || (ruleProvider && heading ? true : false);
+ })
+ !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)).
map(d => d.borderRounding = `${Math.min(100, dist)}%`);
e.stopPropagation();
e.preventDefault();
@@ -550,6 +626,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
doc.y = (doc.y || 0) + dY * (actualdH - height);
let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here...
let fixedAspect = e.ctrlKey || (!BoolCast(doc.ignoreAspect) && nwidth && nheight);
+ if (fixedAspect && e.ctrlKey && BoolCast(doc.ignoreAspect)) {
+ doc.ignoreAspect = false;
+ proto.nativeWidth = nwidth = doc.width || 0;
+ proto.nativeHeight = nheight = doc.height || 0;
+ }
if (fixedAspect && (!nwidth || !nheight)) {
proto.nativeWidth = nwidth = doc.width || 0;
proto.nativeHeight = nheight = doc.height || 0;
@@ -630,6 +711,74 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
);
}
+ private get targetDoc() {
+ return SelectionManager.SelectedDocuments()[0].props.Document;
+ }
+
+ considerGoogleDocsPush = () => {
+ let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
+ if (!canPush) return (null);
+ let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined;
+ let icon: IconProp = published ? (this.pushIcon as any) : cloud;
+ return (
+ <div className={"linkButtonWrapper"}>
+ <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
+ DocumentDecorations.hasPushedHack = false;
+ this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} />
+ </div>
+ </div>
+ );
+ }
+
+ considerGoogleDocsPull = () => {
+ let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
+ let dataDoc = Doc.GetProto(this.targetDoc);
+ if (!canPull || !dataDoc[GoogleRef]) return (null);
+ let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
+ icon = this.openHover ? "share" : icon;
+ let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
+ let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
+ return (
+ <div className={"linkButtonWrapper"}>
+ <div
+ title={title}
+ className="linkButton-linker"
+ style={{
+ backgroundColor: this.pullColor,
+ transition: "0.2s ease all"
+ }}
+ onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
+ onPointerLeave={() => runInAction(() => this.openHover = false)}
+ onClick={e => {
+ if (e.altKey) {
+ e.preventDefault();
+ window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
+ } else {
+ this.clearPullColor();
+ DocumentDecorations.hasPulledHack = false;
+ this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
+ dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
+ }
+ }}>
+ <FontAwesomeIcon
+ style={{
+ WebkitAnimation: animation,
+ MozAnimation: animation
+ }}
+ className="documentdecorations-icon"
+ icon={icon}
+ size="sm"
+ />
+ </div>
+ </div>
+ );
+ }
+
+ public static hasPushedHack = false;
+ public static hasPulledHack = false;
+
considerTooltip = () => {
let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField;
@@ -685,6 +834,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
let selFirst = SelectionManager.SelectedDocuments()[0];
+
let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length;
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
@@ -697,28 +847,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let templates: Map<Template, boolean> = new Map();
Array.from(Object.values(Templates.TemplateList)).map(template => {
- let sorted = SelectionManager.ViewsSortedVertically(); // slice().sort((doc1, doc2) => {
- // if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.x)) return 1;
- // if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
- // return 0;
- // });
- let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => {
- let temps = doc.props.Document.templates;
- if (temps instanceof List) {
- temps.map(temp => {
- if (temp !== Templates.Bullet.Layout || i === 0) {
- res.push(temp);
- }
- });
- }
- return res;
- }, [] as string[]);
let checked = false;
- docTemps.forEach(temp => {
- if (template.Layout === temp) {
- checked = true;
- }
- });
+ SelectionManager.SelectedDocuments().map(doc => checked = checked || (doc.layoutDoc["show" + template.Name] !== undefined));
templates.set(template, checked);
});
@@ -782,6 +912,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
{this.metadataMenu}
{this.considerEmbed()}
+ {this.considerGoogleDocsPush()}
+ {this.considerGoogleDocsPull()}
{/* {this.considerTooltip()} */}
</div>
</div >
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index c3612fee9..e9db4b048 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react';
import { observable, action, trace } from 'mobx';
import "./EditableView.scss";
import * as Autosuggest from 'react-autosuggest';
+import { undoBatch } from '../util/UndoManager';
export interface EditableProps {
/**
@@ -27,7 +28,8 @@ export interface EditableProps {
contents: any;
fontStyle?: string;
fontSize?: number;
- height?: number;
+ height?: number | "auto";
+ maxHeight?: number;
display?: string;
autosuggestProps?: {
resetValue: () => void;
@@ -70,14 +72,12 @@ export class EditableView extends React.Component<EditableProps> {
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Tab") {
e.stopPropagation();
+ this.finalizeEdit(e.currentTarget.value, e.shiftKey);
this.props.OnTab && this.props.OnTab();
} else if (e.key === "Enter") {
e.stopPropagation();
if (!e.ctrlKey) {
- if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
- this._editing = false;
- this.props.isEditingCallback && this.props.isEditingCallback(false);
- }
+ this.finalizeEdit(e.currentTarget.value, e.shiftKey);
} else if (this.props.OnFillDown) {
this.props.OnFillDown(e.currentTarget.value);
this._editing = false;
@@ -100,6 +100,14 @@ export class EditableView extends React.Component<EditableProps> {
e.stopPropagation();
}
+ @action
+ private finalizeEdit(value: string, shiftDown: boolean) {
+ if (this.props.SetValue(value, shiftDown)) {
+ this._editing = false;
+ this.props.isEditingCallback && this.props.isEditingCallback(false);
+ }
+ }
+
stopPropagation(e: React.SyntheticEvent) {
e.stopPropagation();
}
@@ -118,7 +126,7 @@ export class EditableView extends React.Component<EditableProps> {
className: "editableView-input",
onKeyDown: this.onKeyDown,
autoFocus: true,
- onBlur: action(() => this._editing = false),
+ onBlur: e => this.finalizeEdit(e.currentTarget.value, false),
onPointerDown: this.stopPropagation,
onClick: this.stopPropagation,
onPointerUp: this.stopPropagation,
@@ -126,14 +134,19 @@ export class EditableView extends React.Component<EditableProps> {
onChange: this.props.autosuggestProps.onChange
}}
/>
- : <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
- onBlur={action(() => { this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false); })} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
- style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
+ : <input className="editableView-input"
+ defaultValue={this.props.GetValue()}
+ onKeyDown={this.onKeyDown}
+ autoFocus={true}
+ onBlur={e => this.finalizeEdit(e.currentTarget.value, false)}
+ onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
+ style={{ display: this.props.display, fontSize: this.props.fontSize }}
+ />;
} else {
if (this.props.autosuggestProps) this.props.autosuggestProps.resetValue();
return (
<div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`}
- style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
+ style={{ display: this.props.display, minHeight: "20px", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }}
onClick={this.onClick}>
<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span>
</div>
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 833bacedb..f9ee22f61 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -30,7 +30,7 @@ export default class KeyManager {
}
public handle = async (e: KeyboardEvent) => {
- let keyname = e.key.toLowerCase();
+ let keyname = e.key && e.key.toLowerCase();
this.handleGreedy(keyname);
if (modifiers.includes(keyname)) {
@@ -105,7 +105,7 @@ export default class KeyManager {
switch (keyname) {
case " ":
- DictationManager.Controls.listen({ tryExecute: true });
+ DictationManager.Controls.listen({ useOverlay: true, tryExecute: true });
stopPropagation = true;
preventDefault = true;
}
@@ -196,8 +196,6 @@ export default class KeyManager {
async printClipboard() {
let text: string = await navigator.clipboard.readText();
- console.log(text)
- console.log(document.activeElement)
}
private ctrl_shift = action((keyname: string) => {
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 3f40642b5..57dad5e6b 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -9,9 +9,11 @@ import { SelectionManager } from "../util/SelectionManager";
import { InkTool } from "../../new_fields/InkField";
import { Doc } from "../../new_fields/Doc";
import { undoBatch, UndoManager } from "../util/UndoManager";
-import { StrCast } from "../../new_fields/Types";
-import { FormattedTextBox } from "./nodes/FormattedTextBox";
+import { StrCast, NumCast, Cast } from "../../new_fields/Types";
import { MainOverlayTextBox } from "./MainOverlayTextBox";
+import { listSpec } from "../../new_fields/Schema";
+import { List } from "../../new_fields/List";
+import { Utils } from "../../Utils";
library.add(faPen, faHighlighter, faEraser, faBan);
@@ -49,7 +51,36 @@ export class InkingControl extends React.Component {
let oldColors = selected.map(view => {
let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document);
let oldColor = StrCast(targetDoc.backgroundColor);
- targetDoc.backgroundColor = this._selectedColor;
+ let matchedColor = this._selectedColor;
+ const cv = view.props.ContainingCollectionView;
+ let ruleProvider: Doc | undefined;
+ if (cv) {
+ if (!cv.props.Document.colorPalette) {
+ let defaultPalette = ["rg14,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
+ "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
+ let colorPalette = Cast(cv.props.Document.colorPalette, listSpec("string"));
+ if (!colorPalette) cv.props.Document.colorPalette = new List<string>(defaultPalette);
+ }
+ let cp = Cast(cv.props.Document.colorPalette, listSpec("string")) as string[];
+ let closest = 0;
+ let dist = 10000000;
+ let ccol = Utils.fromRGBAstr(StrCast(targetDoc.backgroundColor));
+ for (let i = 0; i < cp.length; i++) {
+ let cpcol = Utils.fromRGBAstr(cp[i]);
+ let d = Math.sqrt((ccol.r - cpcol.r) * (ccol.r - cpcol.r) + (ccol.b - cpcol.b) * (ccol.b - cpcol.b) + (ccol.g - cpcol.g) * (ccol.g - cpcol.g));
+ if (d < dist) {
+ dist = d;
+ closest = i;
+ }
+ }
+ cp[closest] = "rgba(" + color.rgb.r + "," + color.rgb.g + "," + color.rgb.b + "," + color.rgb.a + ")";
+ cv.props.Document.colorPalette = new List(cp);
+ matchedColor = cp[closest];
+ ruleProvider = (view.props.Document.heading && cv && cv.props.ruleProvider) ? cv.props.ruleProvider : undefined;
+ ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb)));
+ }
+ !ruleProvider && (targetDoc.backgroundColor = matchedColor);
+
return {
target: targetDoc,
previous: oldColor
@@ -62,7 +93,6 @@ export class InkingControl extends React.Component {
});
}
});
-
@action
switchWidth = (width: string): void => {
this._selectedWidth = width;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 0e687737d..11ec6f0c9 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -37,7 +37,6 @@ let swapDocs = async () => {
(await Cast(CurrentUserUtils.UserDocument.workspaces, Doc))!.chromeStatus = "disabled";
(await Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc))!.chromeStatus = "disabled";
(await Cast(CurrentUserUtils.UserDocument.sidebar, Doc))!.chromeStatus = "disabled";
- CurrentUserUtils.UserDocument.chromeStatus = "disabled";
await swapDocs();
document.getElementById('root')!.addEventListener('wheel', event => {
if (event.ctrlKey) {
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index b14a1e0ea..71fb2707d 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -2,7 +2,7 @@ import { action, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
-import { Doc } from '../../new_fields/Doc';
+import { Doc, DocListCast } from '../../new_fields/Doc';
import { BoolCast } from '../../new_fields/Types';
import { emptyFunction, returnTrue, returnZero, Utils, returnOne } from '../../Utils';
import { DragManager } from '../util/DragManager';
@@ -25,7 +25,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
private _textTargetDiv: HTMLDivElement | undefined;
private _textProxyDiv: React.RefObject<HTMLDivElement>;
private _textBottom: boolean | undefined;
- private _textAutoHeight: boolean | undefined;
private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); };
private _outerdiv: HTMLElement | null = null;
private _textBox: FormattedTextBox | undefined;
@@ -42,7 +41,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
return this._textBox && this._textBox.setFontColor(color);
}
-
constructor(props: MainOverlayTextBoxProps) {
super(props);
this._textProxyDiv = React.createRef();
@@ -59,7 +57,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale);
};
- this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content");
+ this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight) || box.props.height === "min-content");
}
else {
this.TextDoc = undefined;
@@ -74,7 +72,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
if (this._textTargetDiv) {
this._textTargetDiv.style.color = this._textColor;
}
- this._textAutoHeight = autoHeight;
this.TextFieldKey = textFieldKey!;
let txf = tx ? tx : () => Transform.Identity();
this._textXf = txf;
@@ -131,10 +128,11 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
this.TextDoc; this.TextDataDoc;
if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
let wid = FormattedTextBox.InputBoxOverlay.props.Document.width; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
+ let hgtx = FormattedTextBox.InputBoxOverlay.props.Document.height; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations)
let textRect = this._textTargetDiv.getBoundingClientRect();
let s = this._textXf().Scale;
let location = this._textBottom ? textRect.bottom : textRect.top;
- let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
+ let hgt = (this._textBox && this._textBox.props.Document.autoHeight) || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
return <div ref={this._setouterdiv} className="mainOverlayTextBox-unscaled_div" style={{ transform: `translate(${textRect.left}px, ${location}px)` }} >
<div className="mainOverlayTextBox-textInput" style={{ transform: `scale(${1 / s})`, width: "auto", height: "0px" }} >
<div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
@@ -144,9 +142,12 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
Document={FormattedTextBox.InputBoxOverlay.props.Document}
DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
onClick={undefined}
- isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
+ ruleProvider={this._textBox ? this._textBox.props.ruleProvider : undefined}
+ ChromeHeight={this.ChromeHeight}
+ isSelected={returnTrue} select={emptyFunction} renderDepth={0}
ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne}
- ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction}
+ pinToPres={returnZero} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
</div>
</div>
</div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index fdea8f963..6331763f1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
@@ -7,16 +7,15 @@ import "normalize.css";
import * as React from 'react';
import { SketchPicker } from 'react-color';
import Measure from 'react-measure';
-import { Doc, DocListCast, HeightSym, Opt } from '../../new_fields/Doc';
+import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc';
+import { List } from '../../new_fields/List';
import { Id } from '../../new_fields/FieldSymbols';
import { InkTool } from '../../new_fields/InkField';
-import { List } from '../../new_fields/List';
import { listSpec } from '../../new_fields/Schema';
-import { SchemaHeaderField } from '../../new_fields/SchemaHeaderField';
-import { BoolCast, Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { BoolCast, Cast, FieldValue, StrCast, NumCast } from '../../new_fields/Types';
import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
import { DocServer } from '../DocServer';
import { Docs } from '../documents/Documents';
import { ClientUtils } from '../util/ClientUtils';
@@ -24,7 +23,7 @@ import { DictationManager } from '../util/DictationManager';
import { SetupDrag } from '../util/DragManager';
import { HistoryUtil } from '../util/History';
import { Transform } from '../util/Transform';
-import { UndoManager } from '../util/UndoManager';
+import { UndoManager, undoBatch } from '../util/UndoManager';
import { CollectionBaseView } from './collections/CollectionBaseView';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionTreeView } from './collections/CollectionTreeView';
@@ -37,17 +36,18 @@ import { MainOverlayTextBox } from './MainOverlayTextBox';
import { DocumentView } from './nodes/DocumentView';
import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
-import { PresentationView } from './presentationview/PresentationView';
import { PreviewCursor } from './PreviewCursor';
import { FilterBox } from './search/FilterBox';
import { TimelineMenu } from './animationtimeline/TimelineMenu';
-
+import PresModeMenu from './presentationview/PresentationModeMenu';
+import { PresBox } from './nodes/PresBox';
+import { LinkFollowBox } from './linking/LinkFollowBox';
+import { DocumentManager } from '../util/DocumentManager';
@observer
export class MainView extends React.Component {
public static Instance: MainView;
@observable addMenuToggle = React.createRef<HTMLInputElement>();
- @observable private _workspacesShown: boolean = false;
@observable public pwidth: number = 0;
@observable public pheight: number = 0;
@@ -84,9 +84,6 @@ export class MainView extends React.Component {
public isPointerDown = false;
private set mainContainer(doc: Opt<Doc>) {
if (doc) {
- if (!("presentationView" in doc)) {
- doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })]);
- }
CurrentUserUtils.UserDocument.activeWorkspace = doc;
}
}
@@ -151,6 +148,7 @@ export class MainView extends React.Component {
componentWillUnMount() {
window.removeEventListener("keydown", KeyManager.Instance.handle);
+ //close presentation
window.removeEventListener("pointerdown", this.globalPointerDown);
window.removeEventListener("pointerup", this.globalPointerUp);
}
@@ -158,6 +156,7 @@ export class MainView extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
MainView.Instance = this;
+
// causes errors to be generated when modifying an observable outside of an action
configure({ enforceActions: "observed" });
if (window.location.pathname !== RouteStore.home) {
@@ -176,7 +175,7 @@ export class MainView extends React.Component {
library.add(faCat);
library.add(faFilePdf);
library.add(faObjectGroup);
- library.add(faTable);
+ library.add(faTv);
library.add(faGlobeAsia);
library.add(faUndoAlt);
library.add(faRedoAlt);
@@ -318,7 +317,6 @@ export class MainView extends React.Component {
@computed get dockingContent() {
let flyoutWidth = this.flyoutWidth;
let mainCont = this.mainContainer;
- let castRes = mainCont ? FieldValue(Cast(mainCont.presentationView, listSpec(Doc))) : undefined;
return <Measure offset onResize={this.onResize}>
{({ measureRef }) =>
<div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutWidth}px`, transform: `translate(${flyoutWidth}px, 0px)` }} onDrop={this.onDrop}>
@@ -327,7 +325,9 @@ export class MainView extends React.Component {
DataDoc={undefined}
addDocument={undefined}
addDocTab={emptyFunction}
+ pinToPres={emptyFunction}
onClick={undefined}
+ ruleProvider={undefined}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
@@ -335,7 +335,6 @@ export class MainView extends React.Component {
PanelHeight={this.getPHeight}
renderDepth={0}
backgroundColor={returnEmptyString}
- selectOnLoad={false}
focus={emptyFunction}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
@@ -344,7 +343,6 @@ export class MainView extends React.Component {
zoomToScale={emptyFunction}
getScale={returnOne}
/>}
- {castRes ? <PresentationView Documents={castRes} key="presentation" /> : null}
</div>
}
</Measure>;
@@ -391,14 +389,15 @@ export class MainView extends React.Component {
DataDoc={undefined}
addDocument={undefined}
addDocTab={this.addDocTabFunc}
+ pinToPres={emptyFunction}
removeDocument={undefined}
+ ruleProvider={undefined}
onClick={undefined}
ScreenToLocalTransform={Transform.Identity}
ContentScaling={returnOne}
PanelWidth={this.flyoutWidthFunc}
PanelHeight={this.getPHeight}
renderDepth={0}
- selectOnLoad={false}
focus={emptyFunction}
backgroundColor={returnEmptyString}
parentActive={returnTrue}
@@ -413,7 +412,7 @@ export class MainView extends React.Component {
get mainContent() {
let sidebar = CurrentUserUtils.UserDocument.sidebar;
if (!(sidebar instanceof Doc)) return (null);
- return <div>
+ return <div className="mainContent" style={{ width: "100%", height: "100%", position: "absolute" }}>
<div className="mainView-libraryHandle"
style={{ cursor: "ew-resize", left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
onPointerDown={this.onPointerDown}>
@@ -442,50 +441,40 @@ export class MainView extends React.Component {
}
}
+ toggleLinkFollowBox = (shouldClose: boolean) => {
+ if (LinkFollowBox.Instance) {
+ let dvs = DocumentManager.Instance.getDocumentViews(LinkFollowBox.Instance.props.Document);
+ // if it already exisits, close it
+ LinkFollowBox.Instance.props.Document.isMinimized = (dvs.length > 0 && shouldClose);
+ }
+ }
+
@observable private _colorPickerDisplay = false;
/* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
nodesMenu() {
-
let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
- // let addDockingNode = action(() => Docs.Create.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" }));
- let addSchemaNode = action(() => Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], [], { width: 200, height: 200, title: "a schema collection" }));
- //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
- // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" }));
let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
+ let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" }));
let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" }));
let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" }));
let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" }));
let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
- let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw";
- let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" }));
+ // let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw";
+ // let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" }));
let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+ [React.createRef<HTMLDivElement>(), "tv", "Add Presentation Trail", addPresNode],
[React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode],
[React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument],
- // [React.createRef<HTMLDivElement>(), "clone", "Add Docking Frame", addDockingNode],
- [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode],
- [React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
- [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode]
+ [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode],
+ [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode
+ //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher],
];
if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]);
- const setWriteMode = (mode: DocServer.WriteMode) => {
- console.log(DocServer.WriteMode[mode]);
- const mode1 = mode;
- const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
- DocServer.setFieldWriteMode("x", mode1);
- DocServer.setFieldWriteMode("y", mode1);
- DocServer.setFieldWriteMode("width", mode1);
- DocServer.setFieldWriteMode("height", mode1);
-
- DocServer.setFieldWriteMode("panX", mode2);
- DocServer.setFieldWriteMode("panY", mode2);
- DocServer.setFieldWriteMode("scale", mode2);
- DocServer.setFieldWriteMode("viewType", mode2);
- };
return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 20, bottom: 20 }} >
<input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} />
@@ -494,8 +483,6 @@ export class MainView extends React.Component {
<div id="add-options-content">
<ul id="add-options-list">
<li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
- <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
- <li key="timeline"><button className="add-button round-button" title="Add Timeline"><FontAwesomeIcon icon="times" size="sm"/></button></li>
<li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
<li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
{btns.map(btn =>
@@ -504,13 +491,7 @@ export class MainView extends React.Component {
<FontAwesomeIcon icon={btn[1]} size="sm" />
</button>
</div></li>)}
- <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
- {ClientUtils.RELEASE ? [] : [
- <li key="test"><button className="add-button round-button" title="Default" onClick={() => setWriteMode(DocServer.WriteMode.Default)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
- <li key="test1"><button className="add-button round-button" title="Playground" onClick={() => setWriteMode(DocServer.WriteMode.Playground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
- <li key="test2"><button className="add-button round-button" title="Live Playground" onClick={() => setWriteMode(DocServer.WriteMode.LivePlayground)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>,
- <li key="test3"><button className="add-button round-button" title="Live Readonly" onClick={() => setWriteMode(DocServer.WriteMode.LiveReadonly)}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li>
- ]}
+ <li key="linkFollow"><button className="add-button round-button" title="Open Link Follower" onClick={() => this.toggleLinkFollowBox(true)}><FontAwesomeIcon icon="link" size="sm" /></button></li>
<li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
<div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}>
<SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} />
@@ -549,7 +530,6 @@ export class MainView extends React.Component {
@observable isSearchVisible = false;
@action.bound
toggleSearch = () => {
- // console.log("search toggling")
this.isSearchVisible = !this.isSearchVisible;
}
@@ -578,12 +558,21 @@ export class MainView extends React.Component {
);
}
+ @computed get miniPresentation() {
+ let next = () => PresBox.CurrentPresentation.next();
+ let back = () => PresBox.CurrentPresentation.back();
+ let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres();
+ let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document); });
+ return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : <PresModeMenu next={next} back={back} presStatus={PresBox.CurrentPresentation.presStatus} startOrResetPres={startOrResetPres} closePresMode={closePresMode} > </PresModeMenu>;
+ }
+
render() {
return (
<div id="main-div">
{this.dictationOverlay}
<DocumentDecorations />
{this.mainContent}
+ {this.miniPresentation}
<PreviewCursor />
<ContextMenu />
{this.nodesMenu()}
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
index bcfc9a82d..7da55fd1c 100644
--- a/src/client/views/MetadataEntryMenu.scss
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -1,6 +1,22 @@
.metadataEntry-outerDiv {
display: flex;
- width: 300px;
+ width: 310px;
+ flex-direction: column;
+
+ input[type=checkbox] {
+ margin-left: 5px;
+ }
+}
+
+.metadataEntry-keys {
+ max-height: 80;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+.metadataEntry-inputArea {
+ display:flex;
+ flex-direction: row;
}
.react-autosuggest__container {
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
index 36c240dd8..f1b101b8e 100644
--- a/src/client/views/MetadataEntryMenu.tsx
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -1,11 +1,12 @@
import * as React from 'react';
import "./MetadataEntryMenu.scss";
import { observer } from 'mobx-react';
-import { observable, action, runInAction, trace } from 'mobx';
+import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx';
import { KeyValueBox } from './nodes/KeyValueBox';
-import { Doc, Field } from '../../new_fields/Doc';
+import { Doc, Field, DocListCast, DocListCastAsync } from '../../new_fields/Doc';
import * as Autosuggest from 'react-autosuggest';
import { undoBatch } from '../util/UndoManager';
+import { emptyFunction } from '../../Utils';
export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
export interface MetadataEntryProps {
@@ -19,6 +20,9 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
@observable private _currentKey: string = "";
@observable private _currentValue: string = "";
@observable private suggestions: string[] = [];
+ private _addChildren: boolean = false;
+ @observable _allSuggestions: string[] = [];
+ _suggestionDispser: IReactionDisposer | undefined;
private userModified = false;
private autosuggestRef = React.createRef<Autosuggest>();
@@ -82,16 +86,27 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
e.stopPropagation();
const script = KeyValueBox.CompileKVPScript(this._currentValue);
if (!script) return;
+
let doc = this.props.docs;
if (typeof doc === "function") {
doc = doc();
}
doc = await doc;
+
let success: boolean;
if (doc instanceof Doc) {
success = KeyValueBox.ApplyKVPScript(doc, this._currentKey, script);
} else {
- success = doc.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script));
+ let childSuccess = true;
+ if (this._addChildren) {
+ for (let document of doc) {
+ let collectionChildren = await DocListCastAsync(document.data);
+ if (collectionChildren) {
+ childSuccess = collectionChildren.every(c => KeyValueBox.ApplyKVPScript(c, this._currentKey, script));
+ }
+ }
+ }
+ success = doc.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script)) && childSuccess;
}
if (!success) {
if (this.props.onError) {
@@ -140,35 +155,67 @@ export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
getSuggestionValue = (suggestion: string) => suggestion;
renderSuggestion = (suggestion: string) => {
- return <p>{suggestion}</p>;
+ return (null);
}
+ componentDidMount() {
- onSuggestionFetch = async ({ value }: { value: string }) => {
- const sugg = await this.getKeySuggestions(value);
- runInAction(() => {
- this.suggestions = sugg;
- });
+ this._suggestionDispser = reaction(() => this._currentKey,
+ () => this.getKeySuggestions(this._currentKey).then(action((s: string[]) => this._allSuggestions = s)),
+ { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ this._suggestionDispser && this._suggestionDispser();
}
- @action
- onSuggestionClear = () => {
- this.suggestions = [];
+ onClick = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this._addChildren = !this._addChildren;
+ }
+
+ private get considerChildOptions() {
+ let docSource = this.props.docs;
+ if (typeof docSource === "function") {
+ docSource = docSource();
+ }
+ docSource = docSource as Doc[] | Doc;
+ if (docSource instanceof Doc) {
+ if (docSource.viewType === undefined) {
+ return (null);
+ }
+ } else if (Array.isArray(docSource)) {
+ if (!docSource.every(doc => doc.viewType !== undefined)) {
+ return null;
+ }
+ }
+ return (
+ <div style={{ display: "flex" }}>
+ Children:
+ <input type="checkbox" onChange={this.onClick} ></input>
+ </div>
+ );
}
render() {
return (
<div className="metadataEntry-outerDiv">
- Key:
- <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
- getSuggestionValue={this.getSuggestionValue}
- suggestions={this.suggestions}
- alwaysRenderSuggestions
- renderSuggestion={this.renderSuggestion}
- onSuggestionsFetchRequested={this.onSuggestionFetch}
- onSuggestionsClearRequested={this.onSuggestionClear}
- ref={this.autosuggestRef} />
- Value:
- <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+ <div className="metadataEntry-inputArea">
+ Key:
+ <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
+ getSuggestionValue={this.getSuggestionValue}
+ suggestions={[]}
+ alwaysRenderSuggestions={false}
+ renderSuggestion={this.renderSuggestion}
+ onSuggestionsFetchRequested={emptyFunction}
+ onSuggestionsClearRequested={emptyFunction}
+ ref={this.autosuggestRef} />
+ Value:
+ <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+ {this.considerChildOptions}
+ </div>
+ <div className="metadataEntry-keys" >
+ <ul>
+ {this._allSuggestions.slice().sort().map(s => <li key={s} onClick={action(() => { this._currentKey = s; this.previewValue(); })} >{s}</li>)}
+ </ul>
+ </div>
</div>
);
}
diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss
index dc122497f..26c2e0e1e 100644
--- a/src/client/views/OverlayView.scss
+++ b/src/client/views/OverlayView.scss
@@ -39,4 +39,9 @@
width: 10px;
height: 10px;
cursor: nwse-resize;
+}
+
+.overlayView-doc {
+ z-index: 1;
+ position: absolute;
} \ No newline at end of file
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 2f2579057..da4b71e5c 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -1,9 +1,17 @@
import * as React from "react";
import { observer } from "mobx-react";
-import { observable, action } from "mobx";
-import { Utils } from "../../Utils";
+import { observable, action, trace, computed } from "mobx";
+import { Utils, emptyFunction, returnOne, returnTrue, returnEmptyString, returnZero, returnFalse } from "../../Utils";
import './OverlayView.scss';
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { DocListCast, Doc } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/FieldSymbols";
+import { DocumentView } from "./nodes/DocumentView";
+import { Transform } from "../util/Transform";
+import { CollectionFreeFormDocumentView } from "./nodes/CollectionFreeFormDocumentView";
+import { DocumentContentsView } from "./nodes/DocumentContentsView";
+import { NumCast } from "../../new_fields/Types";
export type OverlayDisposer = () => void;
@@ -132,10 +140,67 @@ export class OverlayView extends React.Component {
return remove;
}
+ @computed get overlayDocs() {
+ return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => {
+ let offsetx = 0, offsety = 0;
+ let onPointerMove = action((e: PointerEvent) => {
+ if (e.buttons === 1) {
+ d.x = e.clientX + offsetx;
+ d.y = e.clientY + offsety;
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ });
+ let onPointerUp = action((e: PointerEvent) => {
+ document.removeEventListener("pointermove", onPointerMove);
+ document.removeEventListener("pointerup", onPointerUp);
+ e.stopPropagation();
+ e.preventDefault();
+ });
+
+ let onPointerDown = (e: React.PointerEvent) => {
+ offsetx = NumCast(d.x) - e.clientX;
+ offsety = NumCast(d.y) - e.clientY;
+ e.stopPropagation();
+ e.preventDefault();
+ document.addEventListener("pointermove", onPointerMove);
+ document.addEventListener("pointerup", onPointerUp);
+ };
+ return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)`, display: d.isMinimized ? "none" : "" }}>
+ <DocumentContentsView
+ Document={d}
+ ChromeHeight={returnZero}
+ isSelected={returnFalse}
+ select={emptyFunction}
+ layoutKey={"layout"}
+ bringToFront={emptyFunction}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ContentScaling={returnOne}
+ PanelWidth={returnOne}
+ PanelHeight={returnOne}
+ ScreenToLocalTransform={Transform.Identity}
+ renderDepth={1}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={returnEmptyString}
+ addDocTab={emptyFunction}
+ pinToPres={emptyFunction}
+ ContainingCollectionView={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne} />
+ </div>;
+ });
+ }
+
render() {
return (
- <div>
- {this._elements}
+ <div className="overlayView" id="overlayView">
+ <div>
+ {this._elements}
+ </div>
+ {this.overlayDocs}
</div>
);
}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index e7a5475ed..1aed51e64 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,13 +1,20 @@
-import { action, observable } from 'mobx';
+import { action, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
import "./PreviewCursor.scss";
+import { Docs } from '../documents/Documents';
+// import { Transform } from 'prosemirror-transform';
+import { Doc } from '../../new_fields/Doc';
+import { Transform } from "../util/Transform";
@observer
export class PreviewCursor extends React.Component<{}> {
private _prompt = React.createRef<HTMLDivElement>();
static _onKeyPress?: (e: KeyboardEvent) => void;
+ static _getTransform: () => Transform;
+ static _addLiveTextDoc: (doc: Doc) => void;
+ static _addDocument: (doc: Doc, allowDuplicates: false) => boolean;
@observable static _clickPoint = [0, 0];
@observable public static Visible = false;
//when focus is lost, this will remove the preview cursor
@@ -20,20 +27,73 @@ export class PreviewCursor extends React.Component<{}> {
document.addEventListener("keydown", this.onKeyPress);
document.addEventListener("paste", this.paste);
}
+
paste = (e: ClipboardEvent) => {
- console.log(e.clipboardData);
- if (e.clipboardData) {
- console.log(e.clipboardData.getData("text/html"));
- console.log(e.clipboardData.getData("text/csv"));
- console.log(e.clipboardData.getData("text/plain"));
+ if (PreviewCursor.Visible) {
+ if (e.clipboardData) {
+ let newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]);
+ runInAction(() => { PreviewCursor.Visible = false; });
+
+
+ if (e.clipboardData.getData("text/plain") !== "") {
+
+ // tests for youtube and makes video document
+ if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) {
+ const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/");
+ PreviewCursor._addDocument(Docs.Create.VideoDocument(url, {
+ title: url, width: 400, height: 315,
+ nativeWidth: 600, nativeHeight: 472.5,
+ x: newPoint[0], y: newPoint[1]
+ }), false);
+ return;
+ }
+
+ // tests for URL and makes web document
+ let re: any = /^https?:\/\//g;
+ if (re.test(e.clipboardData.getData("text/plain"))) {
+ const url = e.clipboardData.getData("text/plain");
+ PreviewCursor._addDocument(Docs.Create.WebDocument(url, {
+ title: url, width: 300, height: 300,
+ // nativeWidth: 300, nativeHeight: 472.5,
+ x: newPoint[0], y: newPoint[1]
+ }), false);
+ return;
+ }
+
+ // creates text document
+ let newBox = Docs.Create.TextDocument({
+ width: 200, height: 100,
+ x: newPoint[0],
+ y: newPoint[1],
+ title: "-pasted text-"
+ });
+
+ newBox.proto!.autoHeight = true;
+ PreviewCursor._addLiveTextDoc(newBox);
+ return;
+ }
+ //pasting in images
+ if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes("<img src=")) {
+ let re: any = /<img src="(.*?)"/g;
+ let arr: any[] = re.exec(e.clipboardData.getData("text/html"));
+
+ let img: Doc = Docs.Create.ImageDocument(
+ arr[1], {
+ width: 300, title: arr[1],
+ x: newPoint[0],
+ y: newPoint[1],
+ });
+ PreviewCursor._addDocument(img, false);
+ return;
+ }
+
+ }
}
}
@action
onKeyPress = (e: KeyboardEvent) => {
- // Mixing events between React and Native is finicky. In FormattedTextBox, we set the
- // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
- // the keyPress here. 112-
+ // Mixing events between React and Native is finicky.
//if not these keys, make a textbox if preview cursor is active!
if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" &&
e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" &&
@@ -41,17 +101,24 @@ export class PreviewCursor extends React.Component<{}> {
e.key !== "NumLock" &&
(e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys
!e.key.startsWith("Arrow") &&
- !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
- if (!e.ctrlKey && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
+ !e.defaultPrevented) {
+ if ((!e.ctrlKey || (e.keyCode >= 48 && e.keyCode <= 57)) && !e.metaKey) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) {
PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
PreviewCursor.Visible = false;
}
}
}
@action
- public static Show(x: number, y: number, onKeyPress: (e: KeyboardEvent) => void) {
+ public static Show(x: number, y: number,
+ onKeyPress: (e: KeyboardEvent) => void,
+ addLiveText: (doc: Doc) => void,
+ getTransform: () => Transform,
+ addDocument: (doc: Doc, allowDuplicates: false) => boolean) {
this._clickPoint = [x, y];
this._onKeyPress = onKeyPress;
+ this._addLiveTextDoc = addLiveText;
+ this._getTransform = getTransform;
+ this._addDocument = addDocument;
setTimeout(action(() => this.Visible = true), (1));
}
render() {
diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx
index 2b862a81e..8f06cf770 100644
--- a/src/client/views/ScriptBox.tsx
+++ b/src/client/views/ScriptBox.tsx
@@ -10,12 +10,15 @@ import { emptyFunction } from "../../Utils";
import { ScriptCast } from "../../new_fields/Types";
import { CompileScript } from "../util/Scripting";
import { ScriptField } from "../../new_fields/ScriptField";
+import { DragManager } from "../util/DragManager";
+import { EditableView } from "./EditableView";
export interface ScriptBoxProps {
onSave: (text: string, onError: (error: string) => void) => void;
onCancel?: () => void;
initialText?: string;
showDocumentIcons?: boolean;
+ setParams?: (p: string[]) => void;
}
@observer
@@ -56,23 +59,49 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
onFocus = this.onFocus;
onBlur = this.onBlur;
}
+ let params = <EditableView
+ contents={""}
+ display={"block"}
+ maxHeight={72}
+ height={35}
+ fontSize={28}
+ GetValue={() => ""}
+ SetValue={(value: string) => this.props.setParams && this.props.setParams(value.split(" ").filter(s => s !== " ")) ? true : true}
+ />;
return (
<div className="scriptBox-outerDiv">
+ <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
+ <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea>
+ <div style={{ background: "beige" }} >{params}</div>
+ </div>
<div className="scriptBox-toolbar">
<button onClick={e => { this.props.onSave(this._scriptText, this.onError); e.stopPropagation(); }}>Save</button>
<button onClick={e => { this.props.onCancel && this.props.onCancel(); e.stopPropagation(); }}>Cancel</button>
</div>
- <textarea className="scriptBox-textarea" onChange={this.onChange} value={this._scriptText} onFocus={onFocus} onBlur={onBlur}></textarea>
</div>
);
}
- public static EditClickScript(doc: Doc, fieldKey: string) {
+ //let l = docList(this.source[0].data).length; if (l) { let ind = this.target[0].index !== undefined ? (this.target[0].index+1) % l : 0; this.target[0].index = ind; this.target[0].proto = getProto(docList(this.source[0].data)[ind]);}
+ public static EditButtonScript(title: string, doc: Doc, fieldKey: string, clientX: number, clientY: number, prewrapper?: string, postwrapper?: string) {
let overlayDisposer: () => void = emptyFunction;
const script = ScriptCast(doc[fieldKey]);
let originalText: string | undefined = undefined;
- if (script) originalText = script.script.originalScript;
+ if (script) {
+ originalText = script.script.originalScript;
+ if (prewrapper && originalText.startsWith(prewrapper)) {
+ originalText = originalText.substr(prewrapper.length);
+ }
+ if (postwrapper && originalText.endsWith(postwrapper)) {
+ originalText = originalText.substr(0, originalText.length - postwrapper.length);
+ }
+ }
// tslint:disable-next-line: no-unnecessary-callback-wrapper
- let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
+ let params: string[] = [];
+ let setParams = (p: string[]) => params.splice(0, params.length, ...p);
+ let scriptingBox = <ScriptBox initialText={originalText} setParams={setParams} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
+ if (prewrapper) {
+ text = prewrapper + text + (postwrapper ? postwrapper : "");
+ }
const script = CompileScript(text, {
params: { this: Doc.name },
typecheck: false,
@@ -83,9 +112,12 @@ export class ScriptBox extends React.Component<ScriptBoxProps> {
onError(script.errors.map(error => error.messageText).join("\n"));
return;
}
+
+ params.length && DragManager.StartButtonDrag([], text, "a script", {}, params, (button: Doc) => { }, clientX, clientY);
+
doc[fieldKey] = new ScriptField(script);
overlayDisposer();
}} showDocumentIcons />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: `${doc.title || ""} OnClick` });
+ overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, { x: 400, y: 200, width: 500, height: 400, title: title });
}
}
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
deleted file mode 100644
index 33cb63df5..000000000
--- a/src/client/views/SearchBox.tsx
+++ /dev/null
@@ -1,183 +0,0 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faObjectGroup, faSearch } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import * as rp from 'request-promise';
-import { Doc } from '../../new_fields/Doc';
-import { Id } from '../../new_fields/FieldSymbols';
-import { NumCast } from '../../new_fields/Types';
-import { DocServer } from '../DocServer';
-import { Docs } from '../documents/Documents';
-import { SetupDrag } from '../util/DragManager';
-import { SearchItem } from './search/SearchItem';
-import "./SearchBox.scss";
-import { Utils } from '../../Utils';
-
-library.add(faSearch);
-library.add(faObjectGroup);
-
-@observer
-export class SearchBox extends React.Component {
- @observable
- searchString: string = "";
-
- @observable private _open: boolean = false;
- @observable private _resultsOpen: boolean = false;
-
- @observable
- private _results: Doc[] = [];
-
- @action.bound
- onChange(e: React.ChangeEvent<HTMLInputElement>) {
- this.searchString = e.target.value;
- }
-
- @action
- submitSearch = async () => {
- let query = this.searchString;
- //gets json result into a list of documents that can be used
- const results = await this.getResults(query);
-
- runInAction(() => {
- this._resultsOpen = true;
- this._results = results;
- });
- }
-
- @action
- getResults = async (query: string) => {
- let response = await rp.get(Utils.prepend('/search'), {
- qs: {
- query
- }
- });
- let res: string[] = JSON.parse(response);
- const fields = await DocServer.GetRefFields(res);
- const docs: Doc[] = [];
- for (const id of res) {
- const field = fields[id];
- if (field instanceof Doc) {
- docs.push(field);
- }
- }
- return docs;
- }
-
- @action
- handleClickFilter = (e: Event): void => {
- var className = (e.target as any).className;
- var id = (e.target as any).id;
- if (className !== "filter-button" && className !== "filter-form") {
- this._open = false;
- }
-
- }
-
- @action
- handleClickResults = (e: Event): void => {
- var className = (e.target as any).className;
- var id = (e.target as any).id;
- if (id !== "result") {
- this._resultsOpen = false;
- this._results = [];
- }
-
- }
-
- componentWillMount() {
- document.addEventListener('mousedown', this.handleClickFilter, false);
- document.addEventListener('mousedown', this.handleClickResults, false);
- }
-
- componentWillUnmount() {
- document.removeEventListener('mousedown', this.handleClickFilter, false);
- document.removeEventListener('mousedown', this.handleClickResults, false);
- }
-
- @action
- toggleFilterDisplay = () => {
- this._open = !this._open;
- }
-
- enter = (e: React.KeyboardEvent<HTMLInputElement>) => {
- if (e.key === "Enter") {
- this.submitSearch();
- }
- }
-
- collectionRef = React.createRef<HTMLSpanElement>();
- startDragCollection = async () => {
- const results = await this.getResults(this.searchString);
- const docs = results.map(doc => {
- const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
- if (isProto) {
- return Doc.MakeDelegate(doc);
- } else {
- return Doc.MakeAlias(doc);
- }
- });
- let x = 0;
- let y = 0;
- for (const doc of docs) {
- doc.x = x;
- doc.y = y;
- const size = 200;
- const aspect = NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth, 1);
- if (aspect > 1) {
- doc.height = size;
- doc.width = size / aspect;
- } else if (aspect > 0) {
- doc.width = size;
- doc.height = size * aspect;
- } else {
- doc.width = size;
- doc.height = size;
- }
- doc.zoomBasis = 1;
- x += 250;
- if (x > 1000) {
- x = 0;
- y += 300;
- }
- }
- return Docs.Create.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` });
- }
-
- // Useful queries:
- // Delegates of a document: {!join from=id to=proto_i}id:{protoId}
- // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId}
- render() {
- return (
- <div>
- <div className="searchBox-container">
- <div className="searchBox-bar">
- <span onPointerDown={SetupDrag(this.collectionRef, this.startDragCollection)} ref={this.collectionRef}>
- <FontAwesomeIcon icon="object-group" className="searchBox-barChild" size="lg" />
- </span>
- <input value={this.searchString} onChange={this.onChange} type="text" placeholder="Search..."
- className="searchBox-barChild searchBox-input" onKeyPress={this.enter}
- style={{ width: this._resultsOpen ? "500px" : undefined }} />
- {/* <button className="searchBox-barChild searchBox-filter" onClick={this.toggleFilterDisplay}>Filter</button> */}
- {/* <FontAwesomeIcon icon="search" size="lg" className="searchBox-barChild searchBox-submit" /> */}
- </div>
- {this._resultsOpen ? (
- <div className="searchBox-results">
- {this._results.map(result => <SearchItem doc={result} key={result[Id]} highlighting={[]} />)}
- </div>
- ) : null}
- </div>
- {this._open ? (
- <div className="filter-form" id="filter" style={this._open ? { display: "flex" } : { display: "none" }}>
- <div className="filter-form" id="header">Filter Search Results</div>
- <div className="filter-form" id="option">
- filter by collection, key, type of node
- </div>
-
- </div>
- ) : null}
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
deleted file mode 100644
index fd4b2420d..000000000
--- a/src/client/views/SearchItem.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import React = require("react");
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Doc } from "../../new_fields/Doc";
-import { DocumentManager } from "../util/DocumentManager";
-import { SetupDrag } from "../util/DragManager";
-
-
-export interface SearchProps {
- doc: Doc;
-}
-
-library.add(faCaretUp);
-library.add(faObjectGroup);
-library.add(faStickyNote);
-library.add(faFilePdf);
-library.add(faFilm);
-
-export class SearchItem extends React.Component<SearchProps> {
-
- onClick = () => {
- DocumentManager.Instance.jumpToDocument(this.props.doc, false);
- }
-
- //needs help
- // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
-
-
- public static DocumentIcon(layout: string) {
- let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
- layout.indexOf("ImageBox") !== -1 ? faImage :
- layout.indexOf("Formatted") !== -1 ? faStickyNote :
- layout.indexOf("Video") !== -1 ? faFilm :
- layout.indexOf("Collection") !== -1 ? faObjectGroup :
- faCaretUp;
- return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
- }
- onPointerEnter = (e: React.PointerEvent) => {
- Doc.BrushDoc(this.props.doc);
- }
- onPointerLeave = (e: React.PointerEvent) => {
- Doc.UnBrushDoc(this.props.doc);
- }
-
- collectionRef = React.createRef<HTMLDivElement>();
- startDocDrag = () => {
- let doc = this.props.doc;
- const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
- if (isProto) {
- return Doc.MakeDelegate(doc);
- } else {
- return Doc.MakeAlias(doc);
- }
- }
- render() {
- return (
- <div className="search-item" ref={this.collectionRef} id="result"
- onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
- onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
- <div className="search-title" id="result" >title: {this.props.doc.title}</div>
- {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
- {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
- </div>
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 393e97a7e..af3fcaa24 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,16 +1,15 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../new_fields/Doc";
-import { List } from "../../new_fields/List";
-import './DocumentDecorations.scss';
-import { DocumentView } from "./nodes/DocumentView";
-import { Template } from "./Templates";
-import React = require("react");
-import { undoBatch } from "../util/UndoManager";
+import { DocumentType } from "../documents/DocumentTypes";
import { DocumentManager } from "../util/DocumentManager";
-import { NumCast } from "../../new_fields/Types";
import { DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
+import { undoBatch } from "../util/UndoManager";
+import './DocumentDecorations.scss';
+import { DocumentView } from "./nodes/DocumentView";
+import { Template, Templates } from "./Templates";
+import React = require("react");
+import { Doc } from "../../new_fields/Doc";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -30,6 +29,17 @@ class TemplateToggle extends React.Component<{ template: Template, checked: bool
}
}
}
+@observer
+class ChromeToggle extends React.Component<{ checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>) => void }> {
+ render() {
+ return (
+ <li className="chromeToggle">
+ <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event)} />
+ Chrome
+ </li>
+ );
+ }
+}
export interface TemplateMenuProps {
docs: DocumentView[];
@@ -41,8 +51,8 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@observable private _hidden: boolean = true;
dragRef = React.createRef<HTMLUListElement>();
- constructor(props: TemplateMenuProps) {
- super(props);
+ toggleCustom = (e: React.MouseEvent): void => {
+ this.props.docs.map(dv => dv.toggleCustomView());
}
toggleFloat = (e: React.MouseEvent): void => {
@@ -80,55 +90,42 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
@action
toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
if (event.target.checked) {
- if (template.Name === "Bullet") {
- let topDocView = this.props.docs[0];
- topDocView.addTemplate(template);
- topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document));
- } else {
- this.props.docs.map(d => d.addTemplate(template));
- }
- this.props.templates.set(template, true);
+ this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = template.Name.toLowerCase());
} else {
- if (template.Name === "Bullet") {
- let topDocView = this.props.docs[0];
- topDocView.removeTemplate(template);
- topDocView.props.Document.subBulletDocs = undefined;
- } else {
- this.props.docs.map(d => d.removeTemplate(template));
- }
- this.props.templates.set(template, false);
+ this.props.docs.map(d => Doc.GetProto(d.layoutDoc)["show" + template.Name] = undefined);
}
}
@undoBatch
@action
clearTemplates = (event: React.MouseEvent) => {
- this.props.docs.map(d => d.clearTemplates());
- Array.from(this.props.templates.keys()).map(t => this.props.templates.set(t, false));
+ Templates.TemplateList.map(template => this.props.docs.map(d => d.layoutDoc["show" + template.Name] = false));
}
@action
- componentWillReceiveProps(nextProps: TemplateMenuProps) {
- // this._templates = nextProps.templates;
+ toggleTemplateActivity = (): void => {
+ this._hidden = !this._hidden;
}
+ @undoBatch
@action
- toggleTemplateActivity = (): void => {
- this._hidden = !this._hidden;
+ toggleChrome = (): void => {
+ this.props.docs.map(dv => dv.layoutDoc.chromeStatus = (dv.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled"));
}
render() {
let templateMenu: Array<JSX.Element> = [];
this.props.templates.forEach((checked, template) =>
templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
-
+ templateMenu.push(<ChromeToggle key={"chrome"} checked={this.props.docs[0].Document.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
return (
<div className="templating-menu" >
<div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
<ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
{templateMenu}
+ <button onClick={this.toggleCustom}>{this.props.docs[0].Document.nativeLayout ? "Native" : "Custom"}</button>
<button onClick={this.toggleFloat}>Float</button>
- <button onClick={this.clearTemplates}>Clear</button>
+ {/* <button onClick={this.clearTemplates}>Clear</button> */}
</ul>
</div>
);
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 236704fa2..ef78b60d4 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -57,43 +57,7 @@ export namespace Templates {
</div>
</div>` );
- export const Header = new Template("Header", TemplatePosition.InnerTop,
- `<div style = "display:flex; flex-direction:column; height:100%;" >
- <div style="width:100%; background-color: rgba(0, 0, 0, .4); color: white; ">
- <FormattedTextBox {...props} height={"min-content"} color={"white"} fieldKey={"header"} />
- </div>
- <div style="width:100%;height:100%;overflow:auto;">{layout}</div>
- </div > ` );
-
- export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
- `< div >
- <div style="height:100%; width:100%;position:absolute;">{layout}</div>
- <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
- <img id="isExpander" src="/assets/downarrow.png" width="15px" height="15px" />
- </div>
- </div > `
- );
-
- export function ImageOverlay(width: number, height: number, field: string = "thumbnail") {
- return (`< div >
- <div style="height:100%; width:100%; position:absolute;">{layout}</div>
- <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
- <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} />
- </div>
- </div > `);
- }
-
- export function TitleBar(datastring: string) {
- return (`<div>
- <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; z-index: 100">
- <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">${datastring}</span>
- </div>
- <div style="height:calc(100% - 25px);">
- <div style="width:100%;overflow:auto">{layout}</div>
- </div>
- </div>` );
- }
- export const TemplateList: Template[] = [Title, Header, Caption, Bullet];
+ export const TemplateList: Template[] = [Title, Caption];
export function sortTemplates(a: Template, b: Template) {
if (a.Position < b.Position) { return -1; }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index b6ed6aaa0..b7036b3ff 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -12,6 +12,7 @@ import { ContextMenu } from '../ContextMenu';
import { FieldViewProps } from '../nodes/FieldView';
import './CollectionBaseView.scss';
import { DateField } from '../../../new_fields/DateField';
+import { DocumentType } from '../../documents/DocumentTypes';
export enum CollectionViewType {
Invalid,
@@ -126,7 +127,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document;
let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey;
let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
- let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1);
+ let index = value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined));
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a8e723379..166fa0811 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -29,6 +29,8 @@ import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
import { Docs } from '../../documents/Documents';
import { DateField } from '../../../new_fields/DateField';
+import { List } from '../../../new_fields/List';
+import { DocumentType } from '../../documents/DocumentTypes';
library.add(faFile);
@observer
@@ -162,6 +164,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.stateChanged();
}
+ public Has = (document: Doc) => {
+ let docs = Cast(this.props.Document.data, listSpec(Doc));
+ if (!docs) {
+ return false;
+ }
+ return docs.includes(document);
+ }
+
//
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
@@ -535,6 +545,27 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}));
}
+ /**
+ * Adds a document to the presentation view
+ **/
+ @undoBatch
+ @action
+ public PinDoc(doc: Doc) {
+ //add this new doc to props.Document
+ let curPres = Cast(CurrentUserUtils.UserDocument.curPresentation, Doc) as Doc;
+ if (curPres) {
+ const data = Cast(curPres.data, listSpec(Doc));
+ if (data) {
+ data.push(doc);
+ } else {
+ curPres.data = new List([doc]);
+ }
+ if (!DocumentManager.Instance.getDocumentView(curPres)) {
+ this.addDocTab(curPres, undefined, "onRight");
+ }
+ }
+ }
+
componentDidMount() {
this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
this.props.glContainer.on("tab", this.onActiveContentItemChanged);
@@ -569,20 +600,22 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
ScreenToLocalTransform = () => {
- if (this._mainCont && this._mainCont!.children) {
+ if (this._mainCont && this._mainCont.children) {
let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement);
scale = Utils.GetScreenTransform(this._mainCont).scale;
return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale);
}
return Transform.Identity();
}
- get previewPanelCenteringOffset() { return this.nativeWidth && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; }
+ get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; }
addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => {
if (doc.dockingConfig) {
MainView.Instance.openWorkspace(doc);
} else if (location === "onRight") {
CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
+ } else if (location === "close") {
+ CollectionDockingView.Instance.CloseRightSplit(doc);
} else {
CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
}
@@ -598,17 +631,18 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
bringToFront={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
+ ruleProvider={undefined}
ContentScaling={this.contentScaling}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
renderDepth={0}
- selectOnLoad={false}
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
focus={emptyFunction}
backgroundColor={returnEmptyString}
addDocTab={this.addDocTab}
+ pinToPres={this.PinDoc}
ContainingCollectionView={undefined}
zoomToScale={emptyFunction}
getScale={returnOne} />;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 7e3061354..17a3f4f7c 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -40,6 +40,7 @@ export interface CellProps {
fieldKey: string;
renderDepth: number;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
isFocused: boolean;
changeFocusedCellByIndex: (row: number, col: number) => void;
@@ -148,11 +149,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
DataDoc: this.props.rowProps.original,
fieldKey: this.props.rowProps.column.id as string,
fieldExt: "",
+ ruleProvider: undefined,
ContainingCollectionView: this.props.CollectionView,
isSelected: returnFalse,
select: emptyFunction,
renderDepth: this.props.renderDepth + 1,
- selectOnLoad: false,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
active: returnFalse,
@@ -160,6 +161,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
PanelHeight: returnZero,
PanelWidth: returnZero,
addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
ContentScaling: returnOne
};
@@ -213,7 +215,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
isEditingCallback={this.isEditingCallback}
display={"inline"}
contents={contents}
- height={Number(MAX_ROW_HEIGHT)}
+ height={"auto"}
+ maxHeight={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
let field = props.Document[props.fieldKey];
if (Field.IsField(field)) {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4537dcc85..1a84f94c8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -32,6 +32,7 @@ import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, Collection
import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { DocumentType } from "../../documents/DocumentTypes";
library.add(faCog, faPlus, faSortUp, faSortDown);
@@ -161,6 +162,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined}
childDocs={this.childDocs}
renderDepth={this.props.renderDepth}
+ ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
width={this.previewWidth}
height={this.previewHeight}
getTransform={this.getPreviewTransform}
@@ -171,6 +173,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
setPreviewScript={this.setPreviewScript}
previewScript={this.previewScript}
/>
@@ -200,6 +203,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
active={this.props.active}
onDrop={this.onDrop}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
isSelected={this.props.isSelected}
isFocused={this.isFocused}
setFocused={this.setFocused}
@@ -251,6 +255,7 @@ export interface SchemaTableProps {
active: () => boolean;
onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
isSelected: () => boolean;
isFocused: (document: Doc) => boolean;
setFocused: (document: Doc) => void;
@@ -377,6 +382,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
fieldKey: this.props.fieldKey,
renderDepth: this.props.renderDepth,
addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
moveDocument: this.props.moveDocument,
setIsEditing: this.setCellIsEditing,
isEditable: isEditable,
@@ -897,6 +903,7 @@ interface CollectionSchemaPreviewProps {
fitToBox?: boolean;
width: () => number;
height: () => number;
+ ruleProvider: Doc | undefined;
showOverlays?: (doc: Doc) => { title?: string, caption?: string };
CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView;
onClick?: ScriptField;
@@ -907,6 +914,7 @@ interface CollectionSchemaPreviewProps {
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
setPreviewScript: (script: string) => void;
previewScript?: string;
}
@@ -990,6 +998,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
Document={this.props.Document}
fitToBox={this.props.fitToBox}
onClick={this.props.onClick}
+ ruleProvider={this.props.ruleProvider}
showOverlays={this.props.showOverlays}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
@@ -997,10 +1006,10 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
whenActiveChanged={this.props.whenActiveChanged}
ContainingCollectionView={this.props.CollectionView}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
parentActive={this.props.active}
ScreenToLocalTransform={this.getTransform}
renderDepth={this.props.renderDepth + 1}
- selectOnLoad={false}
ContentScaling={this.contentScaling}
PanelWidth={this.PanelWidth}
PanelHeight={this.PanelHeight}
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 2e4f6aff5..14a9dc9d9 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,7 +11,7 @@ import { listSpec } from "../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from "../../../new_fields/Types";
import { emptyFunction, Utils, numberRange } from "../../../Utils";
-import { DocumentType } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
@@ -34,6 +34,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
_docXfs: any[] = [];
_columnStart: number = 0;
@observable private cursor: CursorProperty = "grab";
+ @observable _scroll = 0; // used to force the document decoration to update when scrolling
@computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
@computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
@computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
@@ -133,7 +134,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
@computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : ScriptCast(this.Document.onChildClick); }
+ @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); }
getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) {
let height = () => this.getDocHeight(layoutDoc);
@@ -143,6 +144,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
DataDocument={dataDoc}
showOverlays={this.overlays}
renderDepth={this.props.renderDepth}
+ ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
fitToBox={this.props.fitToBox}
onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler}
width={width}
@@ -155,11 +157,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
setPreviewScript={emptyFunction}
previewScript={undefined}>
</CollectionSchemaPreview>;
}
- getDocHeight(d: Doc) {
+ getDocHeight(d?: Doc) {
+ if (!d) return 0;
let nw = NumCast(d.nativeWidth);
let nh = NumCast(d.nativeHeight);
if (!d.ignoreAspect && nw && nh) {
@@ -275,6 +279,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
getDocTransform(doc: Doc, dref: HTMLDivElement) {
+ if (!dref) return Transform.Identity();
+ let y = this._scroll; // required for document decorations to update when the text box container is scrolled
let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
@@ -285,15 +291,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
masonryChildren(docs: Doc[]) {
this._docXfs.length = 0;
return docs.map((d, i) => {
+ const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return (null);
+ }
let dref = React.createRef<HTMLDivElement>();
- let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1);
- let height = () => this.getDocHeight(layoutDoc);
- let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+ let height = () => this.getDocHeight(pair.layout);
+ let dxf = () => this.getDocTransform(pair.layout!, dref.current!);
let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
this._docXfs.push({ dxf: dxf, width: width, height: height });
- return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
- {this.getDisplayDoc(layoutDoc, d, dxf, width)}
+ return !pair.layout ? (null) : <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
+ {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
</div>;
});
}
@@ -302,6 +311,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) {
let cols = Math.max(1, Math.min(docList.length,
Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
+ if (isNaN(cols)) {
+ console.log("naN");
+ cols = 1;
+ }
return <div key={heading ? heading.heading : "empty"} className="collectionStackingView-masonrySection">
{!heading ? (null) :
<div key={`${heading.heading}`} className="collectionStackingView-sectionHeader" style={{ background: heading.color }}
@@ -350,8 +363,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
subItems.push({ description: `${this.props.Document.fillColumn ? "Variable Size" : "Autosize"} Column`, event: () => this.props.Document.fillColumn = !this.props.Document.fillColumn, icon: "plus" });
subItems.push({ description: `${this.props.Document.showTitles ? "Hide Titles" : "Show Titles"}`, event: () => this.props.Document.showTitles = !this.props.Document.showTitles ? "title" : "", icon: "plus" });
subItems.push({ description: `${this.props.Document.showCaptions ? "Hide Captions" : "Show Captions"}`, event: () => this.props.Document.showCaptions = !this.props.Document.showCaptions ? "caption" : "", icon: "plus" });
- subItems.push({ description: "Edit onChildClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onChildClick") });
ContextMenu.Instance.addItem({ description: "Stacking Options ...", subitems: subItems, icon: "eye" });
+
+ let existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
+ onClicks.push({ description: "Edit onChildClick script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Child Clicked...", this.props.Document, "onChildClick", obj.x, obj.y) });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
}
}
@@ -362,11 +379,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
contents: "+ ADD A GROUP"
};
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
-
- let sections = (this.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc) : [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]);
+ let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]];
+ if (this.sectionFilter) {
+ let entries = Array.from(this.Sections.entries());
+ sections = entries.sort(this.sortFunc);
+ }
return (
<div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
- ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
+ ref={this.createRef}
+ onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
+ onDrop={this.onDrop.bind(this)}
+ onContextMenu={this.onContextMenu}
+ onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
{sections.map(section => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1]))}
{!this.showAddAGroup ? (null) :
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 74c7ef305..185bec7a2 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
+import { action, observable, trace } from "mobx";
import { observer } from "mobx-react";
import { Doc, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
@@ -78,29 +78,23 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
let parent = this.props.parent;
parent._docXfs.length = 0;
return docs.map((d, i) => {
- let pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d);
+ const pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return (null);
+ }
let width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns);
let height = () => parent.getDocHeight(pair.layout);
let dref = React.createRef<HTMLDivElement>();
- let dxf = () => this.getDocTransform(pair.layout, dref.current!);
- this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
+ let dxf = () => parent.getDocTransform(pair.layout!, dref.current!);
+ parent._docXfs.push({ dxf: dxf, width: width, height: height });
let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap);
let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
return <div className={`collectionStackingView-${parent.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} >
- {this.props.parent.getDisplayDoc(pair.layout, pair.data, dxf, width)}
+ {parent.getDisplayDoc(pair.layout, pair.data, dxf, width)}
</div>;
});
}
- getDocTransform(doc: Doc, dref: HTMLDivElement) {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
- let outerXf = Utils.GetScreenTransform(this.props.parent._masonryGridRef!);
- let offset = this.props.parent.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
- return this.props.parent.props.ScreenToLocalTransform().
- translate(offset[0], offset[1]).
- scale(NumCast(doc.width, 1) / this.props.parent.columnWidth);
- }
-
getValue = (value: string): any => {
let parsed = parseInt(value);
if (!isNaN(parsed)) {
@@ -159,8 +153,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
addDocument = (value: string, shiftDown?: boolean) => {
let key = StrCast(this.props.parent.props.Document.sectionFilter);
- let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, title: value });
+ let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true });
newDoc[key] = this.getValue(this.props.heading);
+ let maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+ let heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3;
+ newDoc.heading = heading;
return this.props.parent.props.addDocument(newDoc);
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 7482f5665..e6fadd71e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -11,11 +11,12 @@ import { CurrentUserUtils } from "../../../server/authentication/models/current_
import { RouteStore } from "../../../server/RouteStore";
import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { Docs, DocumentOptions, DocumentType } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Docs, DocumentOptions } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { FieldViewProps } from "../nodes/FieldView";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox";
import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
@@ -33,6 +34,7 @@ export interface CollectionViewProps extends FieldViewProps {
export interface SubCollectionViewProps extends CollectionViewProps {
CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+ ruleProvider: Doc | undefined;
}
export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@@ -82,7 +84,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
let ind;
let doc = this.props.Document;
let id = CurrentUserUtils.id;
- let email = CurrentUserUtils.email;
+ let email = Doc.CurrentUserEmail;
let pos = { x: position[0], y: position[1] };
if (id && email) {
const proto = Doc.GetProto(doc);
@@ -112,7 +114,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) {
if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
this.childDocs.map(doc =>
Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined)
@@ -212,7 +214,17 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5 }));
return;
}
-
+ let matches: RegExpExecArray | null;
+ if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+ let newBox = Docs.Create.TextDocument({ ...options, width: 400, height: 200, title: "Awaiting title from Google Docs..." });
+ let proto = newBox.proto!;
+ proto.autoHeight = true;
+ proto[GoogleRef] = matches[2];
+ proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
+ proto.backgroundColor = "#eeeeff";
+ this.props.addDocument(newBox);
+ return;
+ }
let batch = UndoManager.StartBatch("collection view drop");
let promises: Promise<void>[] = [];
// tslint:disable-next-line:prefer-for-of
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 990979109..197e57808 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -31,7 +31,7 @@
position: relative;
width: 15px;
color: $intermediate-color;
- margin-top: 4px;
+ margin-top: 3px;
transform: scale(1.3, 1.3);
}
@@ -81,6 +81,9 @@
.treeViewItem-openRight {
display: none;
+ height: 17px;
+ background: gray;
+ width: 15px;
}
.treeViewItem-border {
@@ -95,15 +98,15 @@
.treeViewItem-openRight {
display: inline-block;
- height: 13px;
- margin-top: 2px;
- margin-left: 5px;
+ height: 17px;
+ background: #a8a7a7;
+ width: 15px;
// display: inline;
svg {
display: block;
padding: 0px;
- margin: 0px;
+ margin-left: 3px;
}
}
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 4b1fca18a..6217ef859 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -9,7 +9,8 @@ import { List } from '../../../new_fields/List';
import { Document, listSpec } from '../../../new_fields/Schema';
import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { emptyFunction, Utils } from '../../../Utils';
-import { Docs, DocUtils, DocumentType } from '../../documents/Documents';
+import { Docs, DocUtils } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
import { SelectionManager } from '../../util/SelectionManager';
@@ -27,6 +28,7 @@ import "./CollectionTreeView.scss";
import React = require("react");
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { ContextMenuProps } from '../ContextMenuItem';
export interface TreeViewProps {
@@ -35,9 +37,11 @@ export interface TreeViewProps {
containingCollection: Doc;
renderDepth: number;
deleteDoc: (doc: Doc) => boolean;
+ ruleProvider: Doc | undefined;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
panelWidth: () => number;
panelHeight: () => number;
addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
@@ -47,6 +51,9 @@ export interface TreeViewProps {
treeViewId: string;
parentKey: string;
active: () => boolean;
+ showHeaderFields: () => boolean;
+ preventTreeViewOpen: boolean;
+ renderedIds: string[];
}
library.add(faTrashAlt);
@@ -63,7 +70,12 @@ library.add(faArrowsAltH);
library.add(faPlus, faMinus);
@observer
/**
- * Component that takes in a document prop and a boolean whether it's collapsed or not.
+ * Renders a treeView of a collection of documents
+ *
+ * special fields:
+ * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden
+ * preventTreeViewOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+ * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
class TreeView extends React.Component<TreeViewProps> {
static loadId = "";
@@ -71,7 +83,9 @@ class TreeView extends React.Component<TreeViewProps> {
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; }
- @observable _collapsed: boolean = true;
+ @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
+ @computed get treeViewOpen() { return (BoolCast(this.props.document.treeViewOpen) && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; }
+ set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = c; }
@computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
@@ -144,7 +158,7 @@ class TreeView extends React.Component<TreeViewProps> {
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
let before = x[1] < bounds[1];
- let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed);
+ let inside = x[0] > bounds[0] + 75;
this._header!.current!.className = "treeViewItem-header";
if (inside) this._header!.current!.className += " treeViewItem-header-inside";
else if (before) this._header!.current!.className += " treeViewItem-header-above";
@@ -161,20 +175,21 @@ class TreeView extends React.Component<TreeViewProps> {
fontStyle={style}
fontSize={12}
GetValue={() => StrCast(this.props.document[key])}
- SetValue={(value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true}
- OnFillDown={(value: string) => {
+ SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true)}
+ OnFillDown={undoBatch((value: string) => {
Doc.GetProto(this.dataDoc)[key] = value;
let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined;
if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
return this.props.addDocument(doc);
- }}
- OnTab={() => this.props.indentDocument && this.props.indentDocument()}
+ })}
+ OnTab={() => { TreeView.loadId = ""; this.props.indentDocument && this.props.indentDocument(); }}
/>)
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
+ ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" });
ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) {
@@ -186,6 +201,7 @@ class TreeView extends React.Component<TreeViewProps> {
ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
}
ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
+ ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
e.stopPropagation();
e.preventDefault();
@@ -198,7 +214,7 @@ class TreeView extends React.Component<TreeViewProps> {
let rect = this._header!.current!.getBoundingClientRect();
let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
let before = x[1] < bounds[1];
- let inside = x[0] > bounds[0] + 75 || (!before && !this._collapsed);
+ let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen);
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.document;
@@ -252,16 +268,18 @@ class TreeView extends React.Component<TreeViewProps> {
doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
let rows: JSX.Element[] = [];
- for (let key of Object.keys(ids).sort()) {
+ for (let key of Object.keys(ids).slice().sort()) {
let contents = doc[key];
- let contentElement: JSX.Element[] | JSX.Element = [];
+ let contentElement: (JSX.Element | null)[] | JSX.Element = [];
if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
let remDoc = (doc: Doc) => this.remove(doc, key);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true);
contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
+ this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active,
+ this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen,
+ [...this.props.renderedIds, doc[Id]]);
} else {
contentElement = <EditableView
key="editableView"
@@ -286,14 +304,15 @@ class TreeView extends React.Component<TreeViewProps> {
const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
if (expandKey !== undefined) {
let remDoc = (doc: Doc) => this.remove(doc, expandKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true);
let docs = expandKey === "links" ? this.childLinks : this.childDocs;
return <ul key={expandKey + "more"}>
{!docs ? (null) :
TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc,
this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform,
- this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
+ this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
+ this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen,
+ [...this.props.renderedIds, this.props.document[Id]])}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -307,6 +326,7 @@ class TreeView extends React.Component<TreeViewProps> {
DataDocument={this.resolvedDataDoc}
renderDepth={this.props.renderDepth}
showOverlays={this.noOverlays}
+ ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
fitToBox={this.boundsOfCollectionDocument !== undefined}
width={this.docWidth}
height={this.docHeight}
@@ -318,6 +338,7 @@ class TreeView extends React.Component<TreeViewProps> {
active={this.props.active}
whenActiveChanged={emptyFunction as any}
addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
setPreviewScript={emptyFunction}>
</CollectionSchemaPreview>
</div>;
@@ -326,8 +347,8 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderBullet() {
- return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
- {<FontAwesomeIcon icon={this._collapsed ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
+ return <div className="bullet" title="view inline" onClick={action(() => this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
+ {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
</div>;
}
/**
@@ -341,31 +362,31 @@ class TreeView extends React.Component<TreeViewProps> {
let headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
- if (!this._collapsed) {
+ if (this.treeViewOpen) {
this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" :
this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
this.childDocs ? this.fieldKey : "fields";
}
- this._collapsed = false;
+ this.treeViewOpen = true;
})}>
{this.treeViewExpandedView}
</span>);
- let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
- let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : (
- <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
- <FontAwesomeIcon icon="angle-right" size="lg" />
- </div>);
+ let openRight = (<div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
+ <FontAwesomeIcon title="open in pane on right" icon="angle-right" size="lg" />
+ </div>);
return <>
- <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
+ <div className="docContainer" title="click to edit title" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
style={{
+ color: this.props.document.isMinimized ? "red" : "black",
background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
+ fontWeight: this.props.document.search_string ? "bold" : undefined,
outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
}} >
{this.editableView("title")}
</div >
- {headerElements}
+ {this.props.showHeaderFields() ? headerElements : (null)}
{openRight}
</>;
}
@@ -378,13 +399,13 @@ class TreeView extends React.Component<TreeViewProps> {
{this.renderTitle}
</div>
<div className="treeViewItem-border">
- {this._collapsed ? (null) : this.renderContent}
+ {!this.treeViewOpen || this.props.renderedIds.indexOf(this.props.document[Id]) !== -1 ? (null) : this.renderContent}
</div>
</li>
</div>;
}
public static GetChildElements(
- docs: Doc[],
+ docList: Doc[],
treeViewId: string,
containingCollection: Doc,
dataDoc: Doc | undefined,
@@ -394,12 +415,17 @@ class TreeView extends React.Component<TreeViewProps> {
move: DragManager.MoveFunction,
dropAction: dropActionType,
addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void,
+ pinToPres: (document: Doc) => void,
screenToLocalXf: () => Transform,
outerXf: () => { translateX: number, translateY: number },
active: () => boolean,
panelWidth: () => number,
- renderDepth: number
+ renderDepth: number,
+ showHeaderFields: () => boolean,
+ preventTreeViewOpen: boolean,
+ renderedIds: string[]
) {
+ let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0);
let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
let script = viewSpecScript.script;
@@ -414,38 +440,46 @@ class TreeView extends React.Component<TreeViewProps> {
});
}
- let descending = BoolCast(containingCollection.stackingHeadersSortDescending);
- docs.sort(function (a, b): 1 | -1 {
- let descA = descending ? b : a;
- let descB = descending ? a : b;
- let first = descA[String(containingCollection.sectionFilter)];
- let second = descB[String(containingCollection.sectionFilter)];
- // TODO find better way to sort how to sort..................
- if (typeof first === 'number' && typeof second === 'number') {
- return (first - second) > 0 ? 1 : -1;
- }
- if (typeof first === 'string' && typeof second === 'string') {
- return first > second ? 1 : -1;
- }
- if (typeof first === 'boolean' && typeof second === 'boolean') {
- // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load
- // return Number(descA.x) > Number(descB.x) ? 1 : -1;
- // }
- return first > second ? 1 : -1;
- }
- return descending ? 1 : -1;
- });
+ let ascending = Cast(containingCollection.sortAscending, "boolean", null);
+ if (ascending !== undefined) {
+ docs.sort(function (a, b): 1 | -1 {
+ let descA = ascending ? b : a;
+ let descB = ascending ? a : b;
+ let first = descA.title;
+ let second = descB.title;
+ // TODO find better way to sort how to sort..................
+ if (typeof first === 'number' && typeof second === 'number') {
+ return (first - second) > 0 ? 1 : -1;
+ }
+ if (typeof first === 'string' && typeof second === 'string') {
+ return first > second ? 1 : -1;
+ }
+ if (typeof first === 'boolean' && typeof second === 'boolean') {
+ // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load
+ // return Number(descA.x) > Number(descB.x) ? 1 : -1;
+ // }
+ return first > second ? 1 : -1;
+ }
+ return ascending ? 1 : -1;
+ });
+ }
let rowWidth = () => panelWidth() - 20;
return docs.map((child, i) => {
let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return (null);
+ }
let indent = i === 0 ? undefined : () => {
- if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) {
+ if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) {
let fieldKeysub = StrCast(docs[i - 1].layout).split("fieldKey")[1];
let fieldKey = fieldKeysub.split("\"")[1];
- Doc.AddDocToList(docs[i - 1], fieldKey, child);
- remove(child);
+ if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) {
+ Doc.AddDocToList(docs[i - 1], fieldKey, child);
+ docs[i - 1].treeViewOpen = true;
+ remove(child);
+ }
}
};
let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
@@ -460,6 +494,7 @@ class TreeView extends React.Component<TreeViewProps> {
dataDoc={pair.data}
containingCollection={containingCollection}
treeViewId={treeViewId}
+ ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc}
key={child[Id]}
indentDocument={indent}
renderDepth={renderDepth}
@@ -470,10 +505,14 @@ class TreeView extends React.Component<TreeViewProps> {
moveDocument={move}
dropAction={dropAction}
addDocTab={addDocTab}
+ pinToPres={pinToPres}
ScreenToLocalTransform={screenToLocalXf}
outerXf={outerXf}
parentKey={key}
- active={active} />;
+ active={active}
+ showHeaderFields={showHeaderFields}
+ preventTreeViewOpen={preventTreeViewOpen}
+ renderedIds={renderedIds} />;
});
}
}
@@ -515,6 +554,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ } else {
+ let layoutItems: ContextMenuProps[] = [];
+ layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
+ ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
}
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
@@ -553,34 +596,37 @@ export class CollectionTreeView extends CollectionSubView(Document) {
render() {
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
- let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+ let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false);
let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
return !this.childDocs ? (null) : (
<div id="body" className="collectionTreeView-dropTarget"
style={{ overflow: "auto", background: StrCast(this.props.Document.backgroundColor, "lightgray") }}
onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => (e.target as any).scrollHeight > (e.target as any).clientHeight && e.stopPropagation()}
+ onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={this.createTreeDropTarget}>
<EditableView
contents={this.resolvedDataDoc.title}
display={"block"}
- height={72}
+ maxHeight={72}
+ height={"auto"}
GetValue={() => StrCast(this.resolvedDataDoc.title)}
- SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true}
- OnFillDown={(value: string) => {
+ SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)}
+ OnFillDown={undoBatch((value: string) => {
Doc.GetProto(this.props.Document).title = value;
let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined;
if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
TreeView.loadId = doc[Id];
- Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
- }} />
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false);
+ })} />
{this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)}
{this.props.Document.allowClear ? this.renderClearButton : (null)}
<ul className="no-indent" style={{ width: "max-content" }} >
{
TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
- moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth)
+ moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
+ this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth, () => this.props.Document.chromeStatus !== "disabled",
+ BoolCast(this.props.Document.preventTreeViewOpen), [])
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7e1adaa19..bce4eb427 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -77,26 +77,30 @@ export class CollectionView extends React.Component<FieldViewProps> {
if (this.isAnnotationOverlay || this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking) {
return [(null), this.SubViewHelper(type, renderProps)];
}
- else {
- return [
- (<CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />),
- this.SubViewHelper(type, renderProps)
- ];
- }
+ return [
+ <CollectionViewBaseChrome CollectionView={this} key="chrome" type={type} collapse={this.collapse} />,
+ this.SubViewHelper(type, renderProps)
+ ];
}
get isAnnotationOverlay() { return this.props.fieldExt ? true : false; }
onContextMenu = (e: React.MouseEvent): void => {
if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- let subItems: ContextMenuProps[] = [];
+ let existingVm = ContextMenu.Instance.findByDescription("View Modes...");
+ let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; delete this.props.Document.usePivotLayout; }, icon: "signature" });
if (CollectionBaseView.InSafeMode()) {
ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
}
subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" });
subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" });
- subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" });
+ subItems.push({
+ description: "Stacking", event: () => {
+ this.props.Document.viewType = CollectionViewType.Stacking;
+ this.props.Document.autoHeight = true
+ }, icon: "ellipsis-v"
+ });
subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
switch (this.props.Document.viewType) {
case CollectionViewType.Freeform: {
@@ -105,10 +109,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
break;
}
}
- ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
+ !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
+
let existing = ContextMenu.Instance.findByDescription("Layout...");
let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
- layoutItems.push({ description: "Create Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
!existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 2427c8721..7217b6f30 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -7,7 +7,6 @@
z-index: 9001;
transition: top .5s;
background: lightgrey;
- padding: 10px;
.collectionViewChrome {
display: grid;
@@ -77,7 +76,7 @@
font-size: 75%;
background: rgb(238, 238, 238);
height: 100%;
- width: 150px;
+ width: 75px;
}
.collectionViewBaseChrome-viewSpecsMenu {
@@ -247,4 +246,75 @@
margin-left: 50px;
}
}
+}
+
+
+.commandEntry-outerDiv {
+ display: flex;
+ flex-direction: column;
+ width: 165px;
+ height: 40px;
+}
+.commandEntry-inputArea {
+ display:flex;
+ flex-direction: row;
+ width: 150px;
+ margin: auto 0 auto auto;
+}
+
+.react-autosuggest__container {
+ position: relative;
+ width: 100%;
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+.react-autosuggest__input {
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ width: 100%;
+}
+
+.react-autosuggest__input--focused {
+ outline: none;
+}
+
+.react-autosuggest__input--open {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.react-autosuggest__suggestions-container {
+ display: none;
+}
+
+.react-autosuggest__suggestions-container--open {
+ display: block;
+ position: fixed;
+ overflow-y: auto;
+ max-height: 400px;
+ width: 180px;
+ border: 1px solid #aaa;
+ background-color: #fff;
+ font-family: Helvetica, sans-serif;
+ font-weight: 300;
+ font-size: 16px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ z-index: 2;
+}
+
+.react-autosuggest__suggestions-list {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+.react-autosuggest__suggestion {
+ cursor: pointer;
+ padding: 10px 20px;
+}
+
+.react-autosuggest__suggestion--highlighted {
+ background-color: #ddd;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index ee18bb3a4..19a6f6c91 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -1,30 +1,26 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
import * as React from "react";
-import { CollectionView } from "./CollectionView";
-import "./CollectionViewChromes.scss";
-import { CollectionViewType } from "./CollectionBaseView";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils, emptyFunction } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
+import { CompileScript } from "../../util/Scripting";
import { undoBatch } from "../../util/UndoManager";
-import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx";
-import { observer } from "mobx-react";
-import { Doc, DocListCast, FieldResult } from "../../../new_fields/Doc";
+import { EditableView } from "../EditableView";
+import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
import { DocLike } from "../MetadataEntryMenu";
+import { CollectionViewType } from "./CollectionBaseView";
+import { CollectionView } from "./CollectionView";
+import "./CollectionViewChromes.scss";
import * as Autosuggest from 'react-autosuggest';
-import { EditableView } from "../EditableView";
-import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { Utils } from "../../../Utils";
import KeyRestrictionRow from "./KeyRestrictionRow";
-import { CompileScript } from "../../util/Scripting";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { CollectionSchemaView } from "./CollectionSchemaView";
-import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
-import { listSpec } from "../../../new_fields/Schema";
-import { List } from "../../../new_fields/List";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { threadId } from "worker_threads";
-import { DragManager } from "../../util/DragManager";
const datepicker = require('js-datepicker');
-import * as $ from 'jquery';
-import { firebasedynamiclinks } from "googleapis/build/src/apis/firebasedynamiclinks";
interface CollectionViewChromeProps {
CollectionView: CollectionView;
@@ -44,15 +40,46 @@ let stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
+ _templateCommand = {
+ title: "set template", script: "this.target.childLayout = this.source ? this.source[0] : undefined", params: ["target", "source"],
+ initialize: emptyFunction,
+ immediate: (draggedDocs: Doc[]) => this.props.CollectionView.props.Document.childLayout = draggedDocs.length ? draggedDocs[0] : undefined
+ };
+ _contentCommand = {
+ // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"], // bcz: doesn't look like we can do async stuff in scripting...
+ title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"],
+ initialize: emptyFunction,
+ immediate: (draggedDocs: Doc[]) => Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(draggedDocs.map((d: any) => Doc.MakeAlias(d)))
+ };
+ _viewCommand = {
+ title: "restore view", script: "this.target.panX = this.restoredPanX; this.target.panY = this.restoredPanY; this.target.scale = this.restoredScale;", params: ["target"],
+ immediate: (draggedDocs: Doc[]) => { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1; },
+ initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document.panX; button.restoredPanY = this.props.CollectionView.props.Document.panY; button.restoredScale = this.props.CollectionView.props.Document.scale; }
+ };
+ _freeform_commands = [this._contentCommand, this._templateCommand, this._viewCommand];
+ _stacking_commands = [this._contentCommand, this._templateCommand];
+ _masonry_commands = [this._contentCommand, this._templateCommand];
+ _tree_commands = [];
+ private get _buttonizableCommands() {
+ switch (this.props.type) {
+ case CollectionViewType.Tree: return this._tree_commands;
+ case CollectionViewType.Stacking: return this._stacking_commands;
+ case CollectionViewType.Masonry: return this._stacking_commands;
+ case CollectionViewType.Freeform: return this._freeform_commands;
+ }
+ return [];
+ }
+ private _picker: any;
+ private _commandRef = React.createRef<HTMLInputElement>();
+ private _autosuggestRef = React.createRef<Autosuggest>();
+ @observable private _currentKey: string = "";
@observable private _viewSpecsOpen: boolean = false;
@observable private _dateWithinValue: string = "";
@observable private _dateValue: Date | string = "";
@observable private _keyRestrictions: [JSX.Element, string][] = [];
+ @observable private suggestions: string[] = [];
@computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
- private _picker: any;
- private _datePickerElGuid = Utils.GenerateGuid();
-
getFilters = (script: string) => {
let re: any = /(!)?\(\(\(doc\.(\w+)\s+&&\s+\(doc\.\w+\s+as\s+\w+\)\.includes\(\"(\w+)\"\)/g;
let arr: any[] = re.exec(script);
@@ -91,11 +118,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
componentDidMount = () => {
- setTimeout(() => this._picker = datepicker("#" + this._datePickerElGuid, {
- disabler: (date: Date) => date > new Date(),
- onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
- dateSelected: new Date()
- }), 1000);
let fields: Filter[] = [];
if (this.filterValue) {
@@ -220,25 +242,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
subChrome = () => {
switch (this.props.type) {
- case CollectionViewType.Stacking: return (
- <CollectionStackingViewChrome
- key="collchrome"
- CollectionView={this.props.CollectionView}
- type={this.props.type} />);
- case CollectionViewType.Schema: return (
- <CollectionSchemaViewChrome
- key="collchrome"
- CollectionView={this.props.CollectionView}
- type={this.props.type}
- />);
- case CollectionViewType.Tree: return (
- <CollectionTreeViewChrome
- key="collchrome"
- CollectionView={this.props.CollectionView}
- type={this.props.type}
- />);
- default:
- return null;
+ case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ default: return null;
}
}
@@ -294,16 +301,82 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
- if (de.data instanceof DragManager.DocumentDragData) {
- if (de.data.draggedDocuments.length) {
- this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0];
- e.stopPropagation();
- return true;
- }
+ if (de.data instanceof DragManager.DocumentDragData && de.data.draggedDocuments.length) {
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.data.draggedDocuments));
+ e.stopPropagation();
}
return true;
}
+ datePickerRef = (node: HTMLInputElement) => {
+ if (node) {
+ try {
+ this._picker = datepicker("#" + node.id, {
+ disabler: (date: Date) => date > new Date(),
+ onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
+ dateSelected: new Date()
+ });
+ } catch (e) {
+ console.log("date picker exception:" + e);
+ }
+ }
+ }
+
+ renderSuggestion = (suggestion: string) => {
+ return <p>{suggestion}</p>;
+ }
+ getSuggestionValue = (suggestion: string) => suggestion;
+
+ @action
+ onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
+ this._currentKey = newValue;
+ }
+ onSuggestionFetch = async ({ value }: { value: string }) => {
+ const sugg = await this.getKeySuggestions(value);
+ runInAction(() => this.suggestions = sugg);
+ }
+ @action
+ onSuggestionClear = () => {
+ this.suggestions = [];
+ }
+ getKeySuggestions = async (value: string): Promise<string[]> => {
+ return this._buttonizableCommands.filter(c => c.title.indexOf(value) !== -1).map(c => c.title);
+ }
+
+ autoSuggestDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ }
+
+ private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
+ private _sensitivity: number = 16;
+
+ dragCommandDown = (e: React.PointerEvent) => {
+
+ this._startDragPosition = { x: e.clientX, y: e.clientY };
+ document.addEventListener("pointermove", this.dragPointerMove);
+ document.addEventListener("pointerup", this.dragPointerUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ dragPointerMove = (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ let [dx, dy] = [e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y];
+ if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c =>
+ DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title,
+ { target: this.props.CollectionView.props.Document }, c.params, c.initialize, e.clientX, e.clientY));
+ document.removeEventListener("pointermove", this.dragPointerMove);
+ document.removeEventListener("pointerup", this.dragPointerUp);
+ }
+ }
+ dragPointerUp = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragPointerMove);
+ document.removeEventListener("pointerup", this.dragPointerUp);
+
+ }
+
render() {
let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled";
return (
@@ -333,7 +406,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
</select>
<div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
<input className="collectionViewBaseChrome-viewSpecsInput"
- placeholder="FILTER DOCUMENTS"
+ placeholder="FILTER"
value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
onChange={(e) => { }}
onPointerDown={this.openViewSpecs}
@@ -349,7 +422,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<div className="collectionViewBaseChrome-viewSpecsMenu-row">
<div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft">
CREATED WITHIN:
- </div>
+ </div>
<select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle"
style={{ textTransform: "uppercase", textAlign: "center" }}
value={this._dateWithinValue}
@@ -364,27 +437,33 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<option value="1y">1 year of</option>
</select>
<input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- id={this._datePickerElGuid}
+ id={Utils.GenerateGuid()}
+ ref={this.datePickerRef}
value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
onPointerDown={this.openDatePicker}
placeholder="Value" />
</div>
<div className="collectionViewBaseChrome-viewSpecsMenu-lastRow">
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}>
- ADD KEY RESTRICTION
- </button>
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}>
- APPLY FILTER
- </button>
- <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.clearFilter}>
- CLEAR
- </button>
+ <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}> ADD KEY RESTRICTION </button>
+ <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}> APPLY FILTER </button>
+ <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.clearFilter}> CLEAR </button>
</div>
</div>
</div>
- <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{}}>
- TEMPLATE
+ <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
+ <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <div className="commandEntry-inputArea" onPointerDown={this.autoSuggestDown} >
+ <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
+ getSuggestionValue={this.getSuggestionValue}
+ suggestions={this.suggestions}
+ alwaysRenderSuggestions={true}
+ renderSuggestion={this.renderSuggestion}
+ onSuggestionsFetchRequested={this.onSuggestionFetch}
+ onSuggestionsClearRequested={this.onSuggestionClear}
+ ref={this._autosuggestRef} />
+ </div>
+ </div>
</div>
</div>
{this.subChrome()}
@@ -453,18 +532,13 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
render() {
return (
<div className="collectionStackingViewChrome-cont">
- <button className="collectionStackingViewChrome-sort" onClick={this.toggleSort}>
- <div className="collectionStackingViewChrome-sortLabel">
- Sort
- </div>
- <div className="collectionStackingViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
- <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
- </div>
- </button>
<div className="collectionStackingViewChrome-sectionFilter-cont">
<div className="collectionStackingViewChrome-sectionFilter-label">
GROUP ITEMS BY:
- </div>
+ </div>
+ <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+ <FontAwesomeIcon icon="caret-up" size="2x" color="white" />
+ </div>
<div className="collectionStackingViewChrome-sectionFilter">
<EditableView
GetValue={() => this.sectionFilter}
@@ -570,7 +644,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
- @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); }
+ @computed private get descending() { return Cast(this.props.CollectionView.props.Document.sortAscending, "boolean", null); }
@computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); }
getKeySuggestions = async (value: string): Promise<string[]> => {
@@ -618,7 +692,11 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
return true;
}
- @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; };
+ @action toggleSort = () => {
+ if (this.props.CollectionView.props.Document.sortAscending) this.props.CollectionView.props.Document.sortAscending = undefined;
+ else if (this.props.CollectionView.props.Document.sortAscending === undefined) this.props.CollectionView.props.Document.sortAscending = false;
+ else this.props.CollectionView.props.Document.sortAscending = true;
+ }
@action resetValue = () => { this._currentKey = this.sectionFilter; };
render() {
@@ -628,42 +706,10 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
<div className="collectionTreeViewChrome-sortLabel">
Sort
</div>
- <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}>
+ <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.descending === undefined ? "90" : this.descending ? "180" : "0"}deg)` }}>
<FontAwesomeIcon icon="caret-up" size="2x" color="white" />
</div>
</button>
- <div className="collectionTreeViewChrome-sectionFilter-cont">
- <div className="collectionTreeViewChrome-sectionFilter-label">
- GROUP ITEMS BY:
- </div>
- <div className="collectionTreeViewChrome-sectionFilter">
- <EditableView
- GetValue={() => this.sectionFilter}
- autosuggestProps={
- {
- resetValue: this.resetValue,
- value: this._currentKey,
- onChange: this.onKeyChange,
- autosuggestProps: {
- inputProps:
- {
- value: this._currentKey,
- onChange: this.onKeyChange
- },
- getSuggestionValue: this.getSuggestionValue,
- suggestions: this.suggestions,
- alwaysRenderSuggestions: true,
- renderSuggestion: this.renderSuggestion,
- onSuggestionsFetchRequested: this.onSuggestionFetch,
- onSuggestionsClearRequested: this.onSuggestionClear
- }
- }}
- oneLine
- SetValue={this.setValue}
- contents={this.sectionFilter ? this.sectionFilter : "N/A"}
- />
- </div>
- </div>
</div>
);
}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 17111af58..d8475a467 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -38,8 +38,8 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
return () => {
col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2;
- const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2;
+ const newPanX = NumCast(target.x) + NumCast(target.width) / 2;
+ const newPanY = NumCast(target.y) + NumCast(target.height) / 2;
col.panX = newPanX;
col.panY = newPanY;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index fc5212edd..cfd18ad35 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -1,8 +1,9 @@
.collectionfreeformlinkview-linkLine {
stroke: black;
transform: translate(10000px,10000px);
- opacity: 0.5;
+ opacity: 0.8;
pointer-events: all;
+ stroke-width: 3px;
}
.collectionfreeformlinkview-linkCircle {
stroke: rgb(0,0,0);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 6af87b138..df089eb00 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -39,10 +39,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
// let l = this.props.LinkDocs;
let a = this.props.A;
let b = this.props.B;
- let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / NumCast(a.zoomBasis, 1) / 2);
- let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / NumCast(a.zoomBasis, 1) / 2);
- let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2);
- let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2);
+ let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2);
+ let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2);
+ let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2);
+ let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2);
let text = "";
// let first = this.props.LinkDocs[0];
// if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : "");
@@ -50,7 +50,6 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
return (
<>
<line key="linkLine" className="collectionfreeformlinkview-linkLine"
- style={{ strokeWidth: `${2 * 1 / 2}` }}
x1={`${x1}`} y1={`${y1}`}
x2={`${x2}`} y2={`${y2}`} />
{/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 2d94f1b8e..a25627dd1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -31,8 +31,8 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
// let srcTarg = srcDoc;
// let x1 = NumCast(srcDoc.x);
// let x2 = NumCast(dstDoc.x);
- // let x1w = NumCast(srcDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1);
- // let x2w = NumCast(dstDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1);
+ // let x1w = NumCast(srcDoc.width, -1);
+ // let x2w = NumCast(dstDoc.width, -1);
// if (x1w < 0 || x2w < 0 || i === j) { }
// else {
// let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
@@ -120,9 +120,9 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
render() {
return (
<div className="collectionfreeformlinksview-container">
- {/* <svg className="collectionfreeformlinksview-svgCanvas">
+ <svg className="collectionfreeformlinksview-svgCanvas">
{this.uniqueConnections}
- </svg> */}
+ </svg>
{this.props.children}
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 3193f5624..b8148852d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -55,7 +55,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
ctx.stroke();
// ctx.font = "10px Arial";
- // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+ // ctx.fillText(Doc.CurrentUserEmail[0].toUpperCase(), 10, 10);
}
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1c6393795..d9fc388cd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,17 +1,18 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, IReactionDisposer, reaction } from "mobx";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { action, computed, IReactionDisposer, observable, reaction, trace } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast, FieldResult, Field, Opt } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue, DateCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { DocServer } from "../../../DocServer";
+import { Docs } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
@@ -29,8 +30,8 @@ import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
import { pageSchema } from "../../nodes/ImageBox";
import { OverlayElementOptions, OverlayView } from "../../OverlayView";
import PDFMenu from "../../pdf/PDFMenu";
-import { CollectionSubView } from "../CollectionSubView";
import { ScriptBox } from "../../ScriptBox";
+import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
@@ -39,7 +40,9 @@ import React = require("react");
import v5 = require("uuid/v5");
import { Timeline } from "../../animationtimeline/Timeline";
import { number } from "prop-types";
-import { DocumentType, Docs } from "../../../documents/Documents";
+import { DocServer } from "../../../DocServer";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
@@ -153,6 +156,7 @@ export namespace PivotView {
y={pos.y}
width={pos.width}
height={pos.height}
+ jitterRotation={NumCast(target.props.Document.jitterRotation)}
{...target.getChildDocumentViewProps(doc)}
/>,
bounds: {
@@ -167,8 +171,6 @@ export namespace PivotView {
return prev;
}, elements);
- target.resetSelectOnLoaded();
-
return docViews;
};
@@ -179,7 +181,6 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema)
@observer
export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
- private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
private _lastX: number = 0;
private _lastY: number = 0;
private get _pwidth() { return this.props.PanelWidth(); }
@@ -187,13 +188,29 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _timelineRef = React.createRef<Timeline>();
private inkKey = "ink";
private _childLayoutDisposer?: IReactionDisposer;
+ private _childDisposer?: IReactionDisposer;
componentDidMount() {
- this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)],
- async (args) => args[1] instanceof Doc &&
- this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc), undefined)));
+ this._childDisposer = reaction(() => this.childDocs,
+ async (childDocs) => {
+ let childLayout = Cast(this.props.Document.childLayout, Doc) as Doc;
+ childLayout && childDocs.map(async doc => {
+ if (!Doc.AreProtosEqual(childLayout, (await doc).layout as Doc)) {
+ Doc.ApplyTemplateTo(childLayout, doc, undefined);
+ }
+ });
+ });
+ this._childLayoutDisposer = reaction(() => Cast(this.props.Document.childLayout, Doc),
+ async (childLayout) => {
+ this.childDocs.map(async doc => {
+ if (!Doc.AreProtosEqual(childLayout as Doc, (await doc).layout as Doc)) {
+ Doc.ApplyTemplateTo(childLayout as Doc, doc, undefined);
+ }
+ });
+ });
}
componentWillUnmount() {
+ this._childDisposer && this._childDisposer();
this._childLayoutDisposer && this._childLayoutDisposer();
}
@@ -213,8 +230,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return bounds;
}
+ @computed get actualContentBounds() {
+ return this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
+ }
+
@computed get contentBounds() {
- let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
+ let bounds = this.actualContentBounds;
let res = {
panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -239,13 +260,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
private addLiveTextBox = (newBox: Doc) => {
- this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
+ let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0);
+ let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0;
+ if (heading === 0) {
+ let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 :
+ DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0);
+ heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading);
+ }
+ !this.props.Document.isRuleProvider && (newBox.heading = heading);
this.addDocument(newBox, false);
}
private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
this.props.addDocument(newBox, false);
this.bringToFront(newBox);
- this.updateClusters();
+ this.updateCluster(newBox);
return true;
}
private selectDocuments = (docs: Doc[]) => {
@@ -294,7 +323,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
if (super.drop(e, de)) {
if (de.data instanceof DragManager.DocumentDragData) {
if (de.data.droppedDocuments.length) {
- let z = NumCast(de.data.draggedDocuments[0].z);
+ let z = NumCast(de.data.droppedDocuments[0].z);
let x = (z ? xpo : xp) - de.data.xOffset;
let y = (z ? ypo : yp) - de.data.yOffset;
let dropX = NumCast(de.data.droppedDocuments[0].x);
@@ -313,7 +342,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.bringToFront(d);
});
- this.updateClusters();
+ de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]);
}
}
else if (de.data instanceof DragManager.AnnotationDragData) {
@@ -348,9 +377,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}, -1);
if (cluster !== -1) {
let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+
+ // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view
+ let prevSelected = SelectionManager.SelectedDocuments();
this.selectDocuments(eles);
let clusterDocs = SelectionManager.SelectedDocuments();
SelectionManager.DeselectAll();
+ prevSelected.map(dv => SelectionManager.SelectDoc(dv, true));
+
let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
de.moveDocument = this.props.moveDocument;
const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
@@ -368,6 +402,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return false;
}
@observable sets: (Doc[])[] = [];
+
+ @undoBatch
@action
updateClusters() {
this.sets.length = 0;
@@ -396,12 +432,42 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.sets.map((set, i) => set.map(member => member.cluster = i));
}
+ @undoBatch
+ @action
+ updateCluster(doc: Doc) {
+ if (this.props.Document.useClusters) {
+ this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
+ let preferredInd = NumCast(doc.cluster);
+ doc.cluster = -1;
+ this.sets.map((set, i) => set.map(member => {
+ if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) {
+ doc.cluster = i;
+ }
+ }));
+ if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) {
+ doc.cluster = preferredInd;
+ }
+ this.sets.map((set, i) => {
+ if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) {
+ doc.cluster = i;
+ }
+ });
+ if (doc.cluster === -1) {
+ doc.cluster = this.sets.length;
+ this.sets.push([doc]);
+ } else {
+ for (let i = this.sets.length; i <= doc.cluster; i++) !this.sets[i] && this.sets.push([]);
+ this.sets[doc.cluster].push(doc);
+ }
+ }
+ }
+
getClusterColor = (doc: Doc) => {
if (this.props.Document.useClusters) {
let cluster = NumCast(doc.cluster);
if (this.sets.length <= cluster) {
- setTimeout(() => this.updateClusters(), 0);
- return;
+ setTimeout(() => this.updateCluster(doc), 0);// this.updateClusters(), 0);
+ return "";
}
let set = this.sets.length > cluster ? this.sets[cluster] : undefined;
let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
@@ -625,20 +691,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getScale = () => this.Document.scale ? this.Document.scale : 1;
- getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
- let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout);
+ getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
- DataDoc: pair.data,
- Document: pair.layout,
+ DataDoc: childData,
+ Document: childLayout,
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
+ ruleProvider: this.props.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider,
onClick: this.props.onClick,
- ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform,
+ ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
- selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
- PanelWidth: pair.layout[WidthSym],
- PanelHeight: pair.layout[HeightSym],
+ PanelWidth: childLayout[WidthSym],
+ PanelHeight: childLayout[HeightSym],
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
focus: this.focusDocument,
@@ -647,6 +712,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
zoomToScale: this.zoomToScale,
getScale: this.getScale
};
@@ -658,10 +724,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
+ ruleProvider: this.props.ruleProvider,
onClick: this.props.onClick,
ScreenToLocalTransform: this.getTransform,
renderDepth: this.props.renderDepth,
- selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
PanelWidth: layoutDoc[WidthSym],
PanelHeight: layoutDoc[HeightSym],
ContentScaling: returnOne,
@@ -672,6 +738,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
addDocTab: this.props.addDocTab,
+ pinToPres: this.props.pinToPres,
zoomToScale: this.zoomToScale,
getScale: this.getScale
};
@@ -727,7 +794,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const initScript = this.Document.arrangeInit;
const script = this.Document.arrangeScript;
let state: any = undefined;
- const docs = this.childDocs;
+ let docs = this.childDocs;
+ let overlayDocs = DocListCast(this.props.Document.localOverlays);
+ overlayDocs && docs.push(...overlayDocs);
let elements: ViewDefResult[] = [];
if (initScript) {
const initResult = initScript.script.run({ docs, collection: this.Document });
@@ -746,24 +815,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
{ x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
state = pos.state === undefined ? state : pos.state;
- prev.push({
- ele: <CollectionFreeFormDocumentView key={doc[Id]}
- x={script ? pos.x : undefined} y={script ? pos.y : undefined}
- width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
- bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined
- });
+ let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc);
+ if (pair.layout && !(pair.data instanceof Promise)) {
+ prev.push({
+ ele: <CollectionFreeFormDocumentView key={doc[Id]}
+ ruleProvider={this.props.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
+ jitterRotation={NumCast(this.props.Document.jitterRotation)}
+ x={script ? pos.x : undefined} y={script ? pos.y : undefined}
+ width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+ bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) }
+ });
+ }
}
// }
return prev;
}, elements);
- this.resetSelectOnLoaded();
-
return docviews;
}
- resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
-
@computed.struct
get views() {
let source = this.elements;
@@ -806,6 +876,27 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}, "arrange contents");
}
+ autoFormat = () => {
+ this.props.Document.isRuleProvider = !this.props.Document.isRuleProvider;
+ this.childDocs.map(child => child.heading = undefined);
+ this.childDocs.map(child => {
+ DocListCast(child.layout instanceof Doc ? child.layout.data : child.data).map(heading => {
+ let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading);
+ let disp = (child.data_ext instanceof Doc) && pair.layout && (child.data_ext[`Layout[${pair.layout[Id]}]`] as Doc);
+ if (disp && NumCast(disp.heading) > 0) {
+ if (disp.backgroundColor !== disp.defaultBackgroundColor) {
+ Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(disp.heading)] = disp.backgroundColor;
+ }
+ }
+ if (pair.layout && NumCast(pair.layout.heading) > 0) {
+ if (pair.layout.backgroundColor !== pair.layout.defaultBackgroundColor) {
+ Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(pair.layout.heading)] = pair.layout.backgroundColor;
+ }
+ }
+ })
+ })
+ }
+
analyzeStrokes = async () => {
let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
if (!data) {
@@ -817,32 +908,71 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onContextMenu = (e: React.MouseEvent) => {
let layoutItems: ContextMenuProps[] = [];
- layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
+
+ if (this.childDocs.some(d => BoolCast(d.isTemplate))) {
+ layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
+ }
layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({
description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
event: async () => {
Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes
Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
this.props.Document.useClusters = !this.props.Document.useClusters;
+ this.updateClusters();
},
icon: !this.props.Document.useClusters ? "braille" : "braille"
});
+ layoutItems.push({ description: `${this.props.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: !this.props.Document.isRuleProvider ? "chalkboard" : "chalkboard" });
+ layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
+ layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+ layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
layoutItems.push({
- description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`,
- event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground,
- icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
+ description: "Import document", icon: "upload", event: () => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".zip";
+ input.onchange = async _e => {
+ const files = input.files;
+ if (!files) return;
+ const file = files[0];
+ let formData = new FormData();
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const upload = Utils.prepend("/uploadDoc");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json === "error") {
+ return;
+ }
+ const doc = await DocServer.GetRefField(json);
+ if (!doc || !(doc instanceof Doc)) {
+ return;
+ }
+ const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY);
+ doc.x = x, doc.y = y;
+ this.props.addDocument &&
+ this.props.addDocument(doc, false);
+ };
+ input.click();
+ }
});
- layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
- ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
- let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
- let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
- analyzers.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
- !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" });
- this._timelineRef.current!.timelineContextMenu(e.nativeEvent);
+ let noteItems: ContextMenuProps[] = [];
+ let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+ notes.map((node, i) => noteItems.push({ description: (i + 1) + ": " + StrCast(node.title), event: () => this.createText(i), icon: "eye" }));
+ layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" })
+ ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
}
+ createText = (noteStyle: number) => {
+ let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY);
+ let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+ let text = Docs.Create.TextDocument({ width: 200, height: 100, x: pt[0], y: pt[1], autoHeight: true, title: StrCast(notes[noteStyle % notes.length].title) });
+ text.layout = notes[noteStyle % notes.length];
+ this.addLiveTextBox(text);
+ }
private childViews = () => [
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
@@ -880,6 +1010,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
};
}
render() {
+ this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x;
+ this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y;
+ this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x);
+ this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y);
const easing = () => this.props.Document.panTransformType === "Ease";
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
return (
@@ -920,7 +1054,6 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
@observer
class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
@computed get backgroundView() {
- let props = this.props;
return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index aad26efa0..cc5e887b2 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,11 +1,11 @@
import * as htmlToImage from "html-to-image";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, FieldResult } from "../../../../new_fields/Doc";
+import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
-import { Cast, NumCast } from "../../../../new_fields/Types";
+import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
import { Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
@@ -20,6 +20,9 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView";
import "./MarqueeView.scss";
import React = require("react");
import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField";
+import { string } from "prop-types";
+import { listSpec } from "../../../../new_fields/Schema";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -93,9 +96,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
} else if (!e.ctrlKey) {
- let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
- newBox.proto!.autoHeight = true;
- this.props.addLiveTextDocument(newBox);
+ this.props.addLiveTextDocument(
+ Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" }));
+ } else if (e.keyCode > 48 && e.keyCode <= 57) {
+ let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
+ let text = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, autoHeight: true, title: "-typed text-" });
+ text.layout = notes[(e.keyCode - 49) % notes.length];
+ this.props.addLiveTextDocument(text);
}
e.stopPropagation();
}
@@ -203,7 +210,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
onClick = (e: React.MouseEvent): void => {
if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
- PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress);
+ PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
// let the DocumentView stopPropagation of this event when it selects this document
} else { // why do we get a click event when the cursor have moved a big distance?
// let's cut it off here so no one else has to deal with it.
@@ -273,14 +280,33 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
return d;
});
}
+ let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
+ "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
+ let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string"));
+ if (!colorPalette) this.props.container.props.Document.colorPalette = new List<string>(defaultPalette);
+ let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]);
+ let usedPaletted = new Map<string, number>();
+ [...this.props.activeDocuments(), this.props.container.props.Document].map(child => {
+ let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor);
+ if (palette.indexOf(bg) !== -1) {
+ palette.splice(palette.indexOf(bg), 1);
+ if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
+ else usedPaletted.set(bg, 1);
+ }
+ });
+ usedPaletted.delete("#f1efeb");
+ usedPaletted.delete("white");
+ usedPaletted.delete("rgba(255,255,255,1)");
+ let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
+ let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
let inkData = this.ink ? this.ink.inkData : undefined;
let newCollection = Docs.Create.FreeformDocument(selected, {
x: bounds.left,
y: bounds.top,
panX: 0,
panY: 0,
- backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
- defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
+ backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
+ defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
width: bounds.width,
height: bounds.height,
title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
@@ -303,7 +329,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
selected = [newCollection];
newCollection.x = bounds.left + bounds.width;
summary.proto!.subBulletDocs = new List<Doc>(selected);
- summary.templates = new List<string>([Templates.Bullet.Layout]);
let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
container.viewType = CollectionViewType.Stacking;
container.autoHeight = true;
@@ -373,7 +398,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
marqueeSelect(selectBackgrounds: boolean = true) {
let selRect = this.Bounds;
let selection: Doc[] = [];
- this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
+ this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => {
var x = NumCast(doc.x);
var y = NumCast(doc.y);
var w = NumCast(doc.width);
@@ -383,7 +408,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
if (!selection.length && selectBackgrounds) {
- this.props.activeDocuments().map(doc => {
+ this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => {
var x = NumCast(doc.x);
var y = NumCast(doc.y);
var w = NumCast(doc.width);
@@ -393,6 +418,22 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
}
+ if (!selection.length) {
+ let left = this._downX < this._lastX ? this._downX : this._lastX;
+ let top = this._downY < this._lastY ? this._downY : this._lastY;
+ let topLeft = this.props.getContainerTransform().transformPoint(left, top);
+ let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => {
+ var x = NumCast(doc.x);
+ var y = NumCast(doc.y);
+ var w = NumCast(doc.width);
+ var h = NumCast(doc.height);
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
+ selection.push(doc);
+ }
+ });
+ }
return selection;
}
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss
index fc5f2410c..fc5f2410c 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/linking/LinkEditor.scss
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index ecb3e9db4..ecb3e9db4 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
new file mode 100644
index 000000000..9eeed1cc8
--- /dev/null
+++ b/src/client/views/linking/LinkFollowBox.scss
@@ -0,0 +1,93 @@
+@import "../globalCssVariables";
+
+.linkFollowBox-main {
+ position: absolute;
+ background: whitesmoke;
+ color: grey;
+ border-radius: 15px;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
+ border: solid #BBBBBBBB 5px;
+ pointer-events: all;
+
+ .linkFollowBox-header {
+ height: 50px;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 16px;
+ width: 100%;
+ }
+
+ .direction-indicator {
+ font-size: 12px;
+ }
+
+ .closeDocument {
+ position: relative;
+ max-width: 30px;
+ top: -20px;
+ left: 460px;
+ color: $darker-alt-accent
+ }
+
+ .closeDocument:hover {
+ color: $main-accent;
+ }
+
+ .topHeader {
+ width: 100%;
+ height: 25px;
+ }
+
+ .linkFollowBox-footer {
+ height: 50px;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ button {
+ background-color: $darker-alt-accent;
+ width: 30%;
+ }
+ }
+
+ .linkFollowBox-content {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-column-gap: 5px;
+ margin-left: 5px;
+ margin-right: 5px;
+
+ .linkFollowBox-item {
+ background-color: $light-color;
+ width: 100%;
+ height: 100%;
+
+ .linkFollowBox-itemContent {
+ padding: 5px;
+ font-size: 12px;
+ overflow: scroll;
+
+ input[type=radio] {
+ border: 0px;
+ margin-right: 5px;
+ }
+ }
+
+ .title {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-transform: uppercase;
+ color: $light-color;
+ background-color: $lighter-alt-accent;
+ width: 100%;
+ height: 30px;
+ border-bottom: solid $darker-alt-accent 5px;
+ font-size: 12px;
+ text-align: center;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
new file mode 100644
index 000000000..f8807641b
--- /dev/null
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -0,0 +1,611 @@
+import { observable, computed, action, runInAction, reaction, IReactionDisposer } from "mobx";
+import React = require("react");
+import { observer } from "mobx-react";
+import { FieldViewProps, FieldView } from "../nodes/FieldView";
+import { Doc, DocListCastAsync } from "../../../new_fields/Doc";
+import { undoBatch } from "../../util/UndoManager";
+import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";
+import { CollectionViewType } from "../collections/CollectionBaseView";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentView } from "../nodes/DocumentView";
+import "./LinkFollowBox.scss";
+import { SearchUtil } from "../../util/SearchUtil";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { listSpec } from "../../../new_fields/Schema";
+import { DocServer } from "../../DocServer";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faTimes } from '@fortawesome/free-solid-svg-icons';
+import { docs_v1 } from "googleapis";
+
+enum FollowModes {
+ OPENTAB = "Open in Tab",
+ OPENRIGHT = "Open in Right Split",
+ OPENFULL = "Open Full Screen",
+ PAN = "Pan to Document",
+ INPLACE = "Open In Place"
+}
+
+enum FollowOptions {
+ ZOOM = "Zoom",
+ NOZOOM = "No Zoom",
+}
+
+@observer
+export class LinkFollowBox extends React.Component<FieldViewProps> {
+
+ public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); }
+ public static Instance: LinkFollowBox | undefined;
+ @observable static linkDoc: Doc | undefined = undefined;
+ @observable static destinationDoc: Doc | undefined = undefined;
+ @observable static sourceDoc: Doc | undefined = undefined;
+ @observable selectedMode: string = "";
+ @observable selectedContext: Doc | undefined = undefined;
+ @observable selectedContextAliases: Doc[] | undefined = undefined;
+ @observable selectedOption: string = "";
+ @observable selectedContextString: string = "";
+ @observable sourceView: DocumentView | undefined = undefined;
+ @observable canPan: boolean = false;
+ @observable shouldUseOnlyParentContext = false;
+ _contextDisposer?: IReactionDisposer;
+
+ @observable private _docs: { col: Doc, target: Doc }[] = [];
+ @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
+
+ constructor(props: FieldViewProps) {
+ super(props);
+ LinkFollowBox.Instance = this;
+ this.resetVars();
+ this.props.Document.isBackground = true;
+ }
+
+ componentDidMount = () => {
+ this.resetVars();
+
+ this._contextDisposer = reaction(
+ () => this.selectedContextString,
+ async () => {
+ let ref = await DocServer.GetRefField(this.selectedContextString);
+ runInAction(() => {
+ if (ref instanceof Doc) {
+ this.selectedContext = ref;
+ }
+ });
+ if (this.selectedContext instanceof Doc) {
+ let aliases = await SearchUtil.GetViewsOfDocument(this.selectedContext);
+ runInAction(() => { this.selectedContextAliases = aliases; });
+ }
+ }
+ );
+ }
+
+ componentWillUnmount = () => {
+ this._contextDisposer && this._contextDisposer();
+ }
+
+ async resetPan() {
+ if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionView) {
+ let colDoc = this.sourceView.props.ContainingCollectionView.props.Document;
+ runInAction(() => { this.canPan = false; });
+ if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) {
+ let docs = Cast(colDoc.data, listSpec(Doc), []);
+ let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));
+
+ aliases.forEach(alias => {
+ if (docs.filter(doc => doc === alias).length > 0) {
+ runInAction(() => { this.canPan = true; });
+ }
+ });
+ }
+ }
+ }
+
+ @action
+ resetVars = () => {
+ this.selectedContext = undefined;
+ this.selectedContextString = "";
+ this.selectedMode = "";
+ this.selectedOption = "";
+ LinkFollowBox.linkDoc = undefined;
+ LinkFollowBox.sourceDoc = undefined;
+ LinkFollowBox.destinationDoc = undefined;
+ this.sourceView = undefined;
+ this.canPan = false;
+ this.shouldUseOnlyParentContext = false;
+ }
+
+ async fetchDocuments() {
+ if (LinkFollowBox.destinationDoc) {
+ let dest: Doc = LinkFollowBox.destinationDoc;
+ let aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(dest));
+ const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${dest[Id]}"` });
+ const map: Map<Doc, Doc> = new Map;
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
+ allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
+ docs.forEach(doc => map.delete(doc));
+ runInAction(async () => {
+ this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
+ this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+ let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc;
+ runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest }));
+ });
+ }
+ }
+
+ @action
+ setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => {
+ this.resetVars();
+
+ LinkFollowBox.linkDoc = linkDoc;
+ LinkFollowBox.sourceDoc = source;
+ LinkFollowBox.destinationDoc = dest;
+ this.fetchDocuments();
+
+ SelectionManager.SelectedDocuments().forEach(dv => {
+ if (dv.props.Document === LinkFollowBox.sourceDoc) {
+ this.sourceView = dv;
+ }
+ });
+
+ this.resetPan();
+ }
+
+ unhighlight = () => {
+ Doc.UnhighlightAll();
+ document.removeEventListener("pointerdown", this.unhighlight);
+ }
+
+ @action
+ highlightDoc = () => {
+ if (LinkFollowBox.destinationDoc) {
+ document.removeEventListener("pointerdown", this.unhighlight);
+ Doc.HighlightDoc(LinkFollowBox.destinationDoc);
+ window.setTimeout(() => {
+ document.addEventListener("pointerdown", this.unhighlight);
+ }, 10000);
+ }
+ }
+
+ @undoBatch
+ openFullScreen = () => {
+ if (LinkFollowBox.destinationDoc) {
+ let view: DocumentView | null = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc);
+ view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+ SelectionManager.DeselectAll();
+ }
+ }
+
+ @undoBatch
+ openColFullScreen = (options: { context: Doc }) => {
+ if (LinkFollowBox.destinationDoc) {
+ if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
+ const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
+ options.context.panX = newPanX;
+ options.context.panY = newPanY;
+ }
+ let view = DocumentManager.Instance.getDocumentView(options.context);
+ view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
+ this.highlightDoc();
+ SelectionManager.DeselectAll();
+ }
+ }
+
+ // should container be a doc or documentview or what? This one needs work and is more long term
+ @undoBatch
+ openInContainer = (options: { container: Doc }) => {
+
+ }
+
+ _addDocTab: (undefined | ((doc: Doc, dataDoc: Doc | undefined, where: string) => void));
+
+ setAddDocTab = (addFunc: (doc: Doc, dataDoc: Doc | undefined, where: string) => void) => {
+ this._addDocTab = addFunc;
+ }
+
+ @undoBatch
+ openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => {
+ if (LinkFollowBox.destinationDoc) {
+ options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
+ if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
+ const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
+ options.context.panX = newPanX;
+ options.context.panY = newPanY;
+ }
+ CollectionDockingView.Instance.AddRightSplit(options.context, undefined);
+
+ if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
+
+ this.highlightDoc();
+ SelectionManager.DeselectAll();
+ }
+ }
+
+ @undoBatch
+ openLinkRight = () => {
+ if (LinkFollowBox.destinationDoc) {
+ let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
+ CollectionDockingView.Instance.AddRightSplit(alias, undefined);
+ this.highlightDoc();
+ SelectionManager.DeselectAll();
+ }
+
+ }
+
+ @undoBatch
+ jumpToLink = async (options: { shouldZoom: boolean }) => {
+ if (LinkFollowBox.destinationDoc && LinkFollowBox.linkDoc) {
+ let jumpToDoc: Doc = LinkFollowBox.destinationDoc;
+ let pdfDoc = FieldValue(Cast(LinkFollowBox.destinationDoc, Doc));
+ if (pdfDoc) {
+ jumpToDoc = pdfDoc;
+ }
+ let proto = Doc.GetProto(LinkFollowBox.linkDoc);
+ let targetContext = await Cast(proto.targetContext, Doc);
+ let sourceContext = await Cast(proto.sourceContext, Doc);
+ const shouldZoom = options ? options.shouldZoom : false;
+
+ let dockingFunc = (document: Doc) => { this._addDocTab && this._addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
+
+ if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext);
+ }
+ else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
+ }
+ else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined,
+ NumCast((LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 ? LinkFollowBox.linkDoc.anchor2Page : LinkFollowBox.linkDoc.anchor1Page)));
+
+ }
+ else {
+ DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, dockingFunc);
+ }
+
+ this.highlightDoc();
+ SelectionManager.DeselectAll();
+ }
+ }
+
+ @undoBatch
+ openLinkTab = () => {
+ if (LinkFollowBox.destinationDoc) {
+ let fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
+ // this.prosp.addDocTab is empty -- use the link source's addDocTab
+ this._addDocTab && this._addDocTab(fullScreenAlias, undefined, "inTab");
+
+ this.highlightDoc();
+ SelectionManager.DeselectAll();
+ }
+ }
+
+ @undoBatch
+ openLinkColTab = (options: { context: Doc, shouldZoom: boolean }) => {
+ if (LinkFollowBox.destinationDoc) {
+ options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
+ if (NumCast(options.context.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc.width) / 2;
+ const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc.height) / 2;
+ options.context.panX = newPanX;
+ options.context.panY = newPanY;
+ }
+ this._addDocTab && this._addDocTab(options.context, undefined, "inTab");
+ if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
+
+ this.highlightDoc();
+ SelectionManager.DeselectAll();
+ }
+ }
+
+ @undoBatch
+ openLinkInPlace = (options: { shouldZoom: boolean }) => {
+
+ if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) {
+ let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
+ let y = NumCast(LinkFollowBox.sourceDoc.y);
+ let x = NumCast(LinkFollowBox.sourceDoc.x);
+
+ let width = NumCast(LinkFollowBox.sourceDoc.width);
+ let height = NumCast(LinkFollowBox.sourceDoc.height);
+
+ alias.x = x + width + 30;
+ alias.y = y;
+ alias.width = width;
+ alias.height = height;
+
+ if (this.sourceView && this.sourceView.props.addDocument) {
+ this.sourceView.props.addDocument(alias, false);
+ }
+
+ this.jumpToLink({ shouldZoom: options.shouldZoom });
+
+ this.highlightDoc();
+ SelectionManager.DeselectAll();
+ }
+ }
+
+ //set this to be the default link behavior, can be any of the above
+ public defaultLinkBehavior: (options?: any) => void = this.jumpToLink;
+
+ @action
+ currentLinkBehavior = () => {
+ // this.resetPan();
+ if (LinkFollowBox.destinationDoc) {
+ if (this.selectedContextString === "") {
+ this.selectedContextString = "self";
+ this.selectedContext = LinkFollowBox.destinationDoc;
+ }
+ if (this.selectedOption === "") this.selectedOption = FollowOptions.NOZOOM;
+ let shouldZoom: boolean = this.selectedOption === FollowOptions.NOZOOM ? false : true;
+ let notOpenInContext: boolean = this.selectedContextString === "self" || this.selectedContextString === LinkFollowBox.destinationDoc[Id];
+
+ if (this.selectedMode === FollowModes.INPLACE) {
+ if (shouldZoom !== undefined) this.openLinkInPlace({ shouldZoom: shouldZoom });
+ }
+ else if (this.selectedMode === FollowModes.OPENFULL) {
+ if (notOpenInContext) this.openFullScreen();
+ else this.selectedContext && this.openColFullScreen({ context: this.selectedContext });
+ }
+ else if (this.selectedMode === FollowModes.OPENRIGHT) {
+ if (notOpenInContext) this.openLinkRight();
+ else this.selectedContext && this.openLinkColRight({ context: this.selectedContext, shouldZoom: shouldZoom });
+ }
+ else if (this.selectedMode === FollowModes.OPENTAB) {
+ if (notOpenInContext) this.openLinkTab();
+ else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom });
+ }
+ else if (this.selectedMode === FollowModes.PAN) {
+ this.jumpToLink({ shouldZoom: shouldZoom });
+ }
+ else return;
+ }
+ }
+
+ @action
+ handleModeChange = (e: React.ChangeEvent) => {
+ let target = e.target as HTMLInputElement;
+ this.selectedMode = target.value;
+ this.selectedContext = undefined;
+ this.selectedContextString = "";
+
+ this.shouldUseOnlyParentContext = (this.selectedMode === FollowModes.INPLACE || this.selectedMode === FollowModes.PAN);
+
+ if (this.shouldUseOnlyParentContext) {
+ if (this.sourceView && this.sourceView.props.ContainingCollectionView) {
+ this.selectedContext = this.sourceView.props.ContainingCollectionView.props.Document;
+ this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionView.props.Document.title));
+ }
+ }
+ }
+
+ @action
+ handleOptionChange = (e: React.ChangeEvent) => {
+ let target = e.target as HTMLInputElement;
+ this.selectedOption = target.value;
+ }
+
+ @action
+ handleContextChange = (e: React.ChangeEvent) => {
+ let target = e.target as HTMLInputElement;
+ this.selectedContextString = target.value;
+ // selectedContext is updated in reaction
+ this.selectedOption = "";
+ }
+
+ @computed
+ get canOpenInPlace() {
+ if (this.sourceView && this.sourceView.props.ContainingCollectionView) {
+ let colView = this.sourceView.props.ContainingCollectionView;
+ let colDoc = colView.props.Document;
+ if (colDoc.viewType && colDoc.viewType === CollectionViewType.Freeform) return true;
+ }
+ return false;
+ }
+
+ @computed
+ get availableModes() {
+ return (
+ <div>
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.OPENRIGHT}
+ checked={this.selectedMode === FollowModes.OPENRIGHT}
+ onChange={this.handleModeChange}
+ disabled={false} />
+ {FollowModes.OPENRIGHT}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.OPENTAB}
+ checked={this.selectedMode === FollowModes.OPENTAB}
+ onChange={this.handleModeChange}
+ disabled={false} />
+ {FollowModes.OPENTAB}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.OPENFULL}
+ checked={this.selectedMode === FollowModes.OPENFULL}
+ onChange={this.handleModeChange}
+ disabled={false} />
+ {FollowModes.OPENFULL}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.PAN}
+ checked={this.selectedMode === FollowModes.PAN}
+ onChange={this.handleModeChange}
+ disabled={!this.canPan} />
+ {FollowModes.PAN}
+ </label><br />
+ <label><input
+ type="radio"
+ name="mode"
+ value={FollowModes.INPLACE}
+ checked={this.selectedMode === FollowModes.INPLACE}
+ onChange={this.handleModeChange}
+ disabled={!this.canOpenInPlace} />
+ {FollowModes.INPLACE}
+ </label><br />
+ </div>
+ );
+ }
+
+ @computed
+ get parentName() {
+ if (this.sourceView && this.sourceView.props.ContainingCollectionView) {
+ let colView = this.sourceView.props.ContainingCollectionView;
+ return colView.props.Document.title;
+ }
+ }
+
+ @computed
+ get parentID(): string {
+ if (this.sourceView && this.sourceView.props.ContainingCollectionView) {
+ let colView = this.sourceView.props.ContainingCollectionView;
+ return StrCast(colView.props.Document[Id]);
+ }
+ return "col";
+ }
+
+ @computed
+ get availableContexts() {
+ return (
+ this.shouldUseOnlyParentContext ?
+ <label><input
+ type="radio" disabled={true}
+ name="context"
+ value={this.parentID}
+ checked={true} />
+ {this.parentName} (Parent Collection)
+ </label>
+ :
+ <div>
+ <label><input
+ type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
+ name="context"
+ value={LinkFollowBox.destinationDoc ? StrCast(LinkFollowBox.destinationDoc[Id]) : "self"}
+ checked={LinkFollowBox.destinationDoc ? this.selectedContextString === StrCast(LinkFollowBox.destinationDoc[Id]) || this.selectedContextString === "self" : true}
+ onChange={this.handleContextChange} />
+ Open Self
+ </label><br />
+ {[...this._docs, ...this._otherDocs].map(doc => {
+ if (doc && doc.target && doc.col.title !== "Recently Closed") {
+ return <div key={doc.col[Id] + doc.target[Id]}><label key={doc.col[Id] + doc.target[Id]}>
+ <input
+ type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
+ name="context"
+ value={StrCast(doc.col[Id])}
+ checked={this.selectedContextString === StrCast(doc.col[Id])}
+ onChange={this.handleContextChange} />
+ {doc.col.title}
+ </label><br /></div>;
+ }
+ })}
+ </div>
+ );
+ }
+
+ @computed
+ get shouldShowZoom(): boolean {
+ if (this.selectedMode === FollowModes.OPENFULL) return false;
+ if (this.shouldUseOnlyParentContext) return true;
+ if (LinkFollowBox.destinationDoc ? this.selectedContextString === LinkFollowBox.destinationDoc[Id] : "self") return false;
+
+ let contextMatch: boolean = false;
+ if (this.selectedContextAliases) {
+ this.selectedContextAliases.forEach(alias => {
+ if (alias.viewType === CollectionViewType.Freeform) contextMatch = true;
+ });
+ }
+ if (contextMatch) return true;
+
+ return false;
+ }
+
+ @computed
+ get availableOptions() {
+ if (LinkFollowBox.destinationDoc) {
+ return (
+ this.shouldShowZoom ?
+ <div>
+ <label><input
+ type="radio"
+ name="option"
+ value={FollowOptions.ZOOM}
+ checked={this.selectedOption === FollowOptions.ZOOM}
+ onChange={this.handleOptionChange}
+ disabled={false} />
+ {FollowOptions.ZOOM}
+ </label><br />
+ <label><input
+ type="radio"
+ name="option"
+ value={FollowOptions.NOZOOM}
+ checked={this.selectedOption === FollowOptions.NOZOOM}
+ onChange={this.handleOptionChange}
+ disabled={false} />
+ {FollowOptions.NOZOOM}
+ </label><br />
+ </div>
+ :
+ <div>No Available Options</div>
+ );
+ }
+ return null;
+ }
+
+ render() {
+ return (
+ <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document.height), width: NumCast(this.props.Document.width) }}>
+ <div className="linkFollowBox-header">
+ <div className="topHeader">
+ {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
+ <div onClick={() => this.props.Document.isMinimized = true} className="closeDocument"><FontAwesomeIcon icon={faTimes} size="lg" /></div>
+ </div>
+ <div className=" direction-indicator">{LinkFollowBox.linkDoc ?
+ LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
+ : "" : ""}</div>
+ </div>
+ <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document.height) - 110 }}>
+ <div className="linkFollowBox-item">
+ <div className="linkFollowBox-item title">Mode</div>
+ <div className="linkFollowBox-itemContent">
+ {LinkFollowBox.linkDoc ? this.availableModes : "Please select a link to view modes"}
+ </div>
+ </div>
+ <div className="linkFollowBox-item">
+ <div className="linkFollowBox-item title">Context</div>
+ <div className="linkFollowBox-itemContent">
+ {this.selectedMode !== "" ? this.availableContexts : "Please select a mode to view contexts"}
+ </div>
+ </div>
+ <div className="linkFollowBox-item">
+ <div className="linkFollowBox-item title">Options</div>
+ <div className="linkFollowBox-itemContent">
+ {this.selectedContextString !== "" ? this.availableOptions : "Please select a context to view options"}
+ </div>
+ </div>
+ </div>
+ <div className="linkFollowBox-footer">
+ <button
+ onClick={this.resetVars}>
+ Clear Link
+ </button>
+ <div style={{ width: 20 }}></div>
+ <button
+ onClick={this.currentLinkBehavior}
+ disabled={(LinkFollowBox.linkDoc) ? false : true}>
+ Follow Link
+ </button>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss
index a4018bd2d..a4018bd2d 100644
--- a/src/client/views/nodes/LinkMenu.scss
+++ b/src/client/views/linking/LinkMenu.scss
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx
index 1a4af04f8..842ce45b1 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/linking/LinkMenu.tsx
@@ -1,6 +1,6 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { DocumentView } from "./DocumentView";
+import { DocumentView } from "../nodes/DocumentView";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
@@ -12,9 +12,6 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
library.add(faTrash);
-import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { DocumentType } from "../../documents/Documents";
interface Props {
docView: DocumentView;
@@ -42,6 +39,7 @@ export class LinkMenu extends React.Component<Props> {
linkItems.push(
<LinkMenuGroup
key={groupType}
+ docView={this.props.docView}
sourceDoc={this.props.docView.props.Document}
group={group}
groupType={groupType}
diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index e04044266..b6a24b0c8 100644
--- a/src/client/views/nodes/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -1,6 +1,6 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { DocumentView } from "./DocumentView";
+import { DocumentView } from "../nodes/DocumentView";
import { LinkMenuItem } from "./LinkMenuItem";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
@@ -22,6 +22,8 @@ interface LinkMenuGroupProps {
groupType: string;
showEditor: (linkDoc: Doc) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ docView: DocumentView;
+
}
@observer
@@ -83,9 +85,13 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
let groupItems = this.props.group.map(linkDoc => {
let destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc);
if (destination && this.props.sourceDoc) {
- return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]} groupType={this.props.groupType}
+ return <LinkMenuItem key={destination[Id] + this.props.sourceDoc[Id]}
+ groupType={this.props.groupType}
addDocTab={this.props.addDocTab}
- linkDoc={linkDoc} sourceDoc={this.props.sourceDoc} destinationDoc={destination} showEditor={this.props.showEditor} />;
+ linkDoc={linkDoc}
+ sourceDoc={this.props.sourceDoc}
+ destinationDoc={destination}
+ showEditor={this.props.showEditor} />;
}
});
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index a119eb39b..19a0023e9 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -1,18 +1,17 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faEye, faTimes, faArrowRight, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
+import { faArrowRight, faChevronDown, faChevronUp, faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, observable } from 'mobx';
import { observer } from "mobx-react";
-import { DocumentManager } from "../../util/DocumentManager";
-import { undoBatch } from "../../util/UndoManager";
+import { Doc } from '../../../new_fields/Doc';
+import { Cast, StrCast } from '../../../new_fields/Types';
+import { DragLinkAsDocument } from '../../util/DragManager';
+import { LinkManager } from '../../util/LinkManager';
+import { ContextMenu } from '../ContextMenu';
+import { MainView } from '../MainView';
+import { LinkFollowBox } from './LinkFollowBox';
import './LinkMenu.scss';
import React = require("react");
-import { Doc, DocListCastAsync } from '../../../new_fields/Doc';
-import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
-import { observable, action } from 'mobx';
-import { LinkManager } from '../../util/LinkManager';
-import { DragLinkAsDocument } from '../../util/DragManager';
-import { CollectionDockingView } from '../collections/CollectionDockingView';
-import { SelectionManager } from '../../util/SelectionManager';
library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp);
@@ -29,45 +28,14 @@ interface LinkMenuItemProps {
export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
private _drag = React.createRef<HTMLDivElement>();
@observable private _showMore: boolean = false;
- @action toggleShowMore() { this._showMore = !this._showMore; }
-
- @undoBatch
- onFollowLink = async (e: React.PointerEvent): Promise<void> => {
- e.stopPropagation();
- e.persist();
- let jumpToDoc = this.props.destinationDoc;
- let pdfDoc = FieldValue(Cast(this.props.destinationDoc, Doc));
- if (pdfDoc) {
- jumpToDoc = pdfDoc;
- }
- let proto = Doc.GetProto(this.props.linkDoc);
- let targetContext = await Cast(proto.targetContext, Doc);
- let sourceContext = await Cast(proto.sourceContext, Doc);
- let self = this;
-
-
- let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
- if (e.ctrlKey) {
- dockingFunc = (document: Doc) => CollectionDockingView.Instance.AddRightSplit(document, undefined);
- }
-
- if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!);
- }
- else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
- }
- else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((this.props.destinationDoc === self.props.linkDoc.anchor2 ? self.props.linkDoc.anchor2Page : self.props.linkDoc.anchor1Page)));
- }
- else {
- DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, dockingFunc);
- }
+ @action toggleShowMore() {
+ this._showMore = !this._showMore;
}
onEdit = (e: React.PointerEvent): void => {
e.stopPropagation();
this.props.showEditor(this.props.linkDoc);
+ //SelectionManager.DeselectAll();
}
renderMetadata = (): JSX.Element => {
@@ -100,6 +68,12 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
onLinkButtonUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
+
+ if (LinkFollowBox.Instance !== undefined) {
+ LinkFollowBox.Instance.props.Document.isMinimized = false;
+ LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+ LinkFollowBox.Instance.setAddDocTab(this.props.addDocTab);
+ }
e.stopPropagation();
}
@@ -113,6 +87,30 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
e.stopPropagation();
}
+ onContextMenu = (e: React.MouseEvent) => {
+ e.preventDefault();
+ ContextMenu.Instance.addItem({ description: "Open in Link Follower", event: () => this.openLinkFollower(), icon: "link" });
+ ContextMenu.Instance.addItem({ description: "Follow Default Link", event: () => this.followDefault(), icon: "arrow-right" });
+ ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
+ }
+
+ @action.bound
+ async followDefault() {
+ if (LinkFollowBox.Instance !== undefined) {
+ LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+ LinkFollowBox.Instance.defaultLinkBehavior();
+ }
+ }
+
+ @action.bound
+ async openLinkFollower() {
+ if (LinkFollowBox.Instance !== undefined) {
+ LinkFollowBox.Instance.props.Document.isMinimized = false;
+ MainView.Instance.toggleLinkFollowBox(false);
+ LinkFollowBox.Instance.setLinkDocs(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc);
+ }
+ }
+
render() {
let keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType);
@@ -127,7 +125,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
{canExpand ? <div title="Show more" className="button" onPointerDown={() => this.toggleShowMore()}>
<FontAwesomeIcon className="fa-icon" icon={this._showMore ? "chevron-up" : "chevron-down"} size="sm" /></div> : <></>}
<div title="Edit link" className="button" onPointerDown={this.onEdit}><FontAwesomeIcon className="fa-icon" icon="edit" size="sm" /></div>
- <div title="Follow link" className="button" onPointerDown={this.onFollowLink}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
+ <div title="Follow link" className="button" onClick={this.followDefault} onContextMenu={this.onContextMenu}><FontAwesomeIcon className="fa-icon" icon="arrow-right" size="sm" /></div>
</div>
</div>
{this._showMore ? this.renderMetadata() : <></>}
diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/ButtonBox.scss
index 5ed435505..75a790667 100644
--- a/src/client/views/nodes/ButtonBox.scss
+++ b/src/client/views/nodes/ButtonBox.scss
@@ -3,14 +3,29 @@
height: 100%;
pointer-events: all;
border-radius: inherit;
- display:table;
+ display:flex;
+ flex-direction: column;
}
.buttonBox-mainButton {
width: 100%;
height: 100%;
border-radius: inherit;
+ text-align: center;
+ display: table;
+}
+.buttonBox-mainButtonCenter {
+ height: 100%;
display:table-cell;
vertical-align: middle;
- text-align: center;
+}
+
+.buttonBox-params {
+ display:flex;
+ flex-direction: row;
+}
+
+.buttonBox-missingParam {
+ 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 8b6f11aac..68d3b8ae1 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -1,21 +1,21 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit } from '@fortawesome/free-regular-svg-icons';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
import * as React from 'react';
-import { FieldViewProps, FieldView } from './FieldView';
-import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { Doc, DocListCastAsync } from '../../../new_fields/Doc';
+import { List } from '../../../new_fields/List';
+import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
import { ScriptField } from '../../../new_fields/ScriptField';
+import { BoolCast, StrCast, Cast } from '../../../new_fields/Types';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
import { DocComponent } from '../DocComponent';
+import './ButtonBox.scss';
+import { FieldView, FieldViewProps } from './FieldView';
+import { ContextMenuProps } from '../ContextMenuItem';
import { ContextMenu } from '../ContextMenu';
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit } from '@fortawesome/free-regular-svg-icons';
-import { emptyFunction } from '../../../Utils';
-import { ScriptBox } from '../ScriptBox';
-import { CompileScript } from '../../util/Scripting';
-import { OverlayView } from '../OverlayView';
-import { Doc } from '../../../new_fields/Doc';
-import './ButtonBox.scss';
-import { observer } from 'mobx-react';
-import { DocumentIconContainer } from './DocumentIcon';
-import { StrCast } from '../../../new_fields/Types';
library.add(faEdit as any);
@@ -30,11 +30,55 @@ const ButtonDocument = makeInterface(ButtonSchema);
@observer
export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(ButtonDocument) {
public static LayoutString() { return FieldView.LayoutString(ButtonBox); }
+ private dropDisposer?: DragManager.DragDropDisposer;
+ @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+
+
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ let funcs: ContextMenuProps[] = [];
+ funcs.push({
+ description: "Clear Script Params", event: () => {
+ let params = Cast(this.props.Document.buttonParams, listSpec("string"));
+ params && params.map(p => this.props.Document[p] = undefined)
+ }, icon: "trash"
+ });
+
+ ContextMenu.Instance.addItem({ description: "OnClick...", subitems: funcs, icon: "asterisk" });
+ }
+
+ @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);
+ e.stopPropagation();
+ }
+ }
+ // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")")
render() {
+ let params = Cast(this.props.Document.buttonParams, listSpec("string"));
+ let missingParams = params && params.filter(p => this.props.Document[p] === undefined);
+ params && params.map(async p => await DocListCastAsync(this.props.Document[p])); // bcz: really hacky form of prefetching ...
return (
- <div className="buttonBox-outerDiv" >
- <div className="buttonBox-mainButton" style={{ background: StrCast(this.props.Document.backgroundColor), color: StrCast(this.props.Document.color, "black") }} >{this.Document.text || this.Document.title}</div>
+ <div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <div className="buttonBox-mainButton" style={{ background: StrCast(this.props.Document.backgroundColor), color: StrCast(this.props.Document.color, "black") }} >
+ <div className="buttonBox-mainButtonCenter">
+ {(this.Document.text || this.Document.title)}
+ </div>
+ </div>
+ <div className="buttonBox-params" >
+ {!missingParams || !missingParams.length ? (null) : missingParams.map(m => <div key={m} className="buttonBox-missingParam">{m}</div>)}
+ </div>
</div>
);
}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index ee596c841..4872a7aa1 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,24 +1,24 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, FieldValue, NumCast, StrCast, Cast } from "../../../new_fields/Types";
import { Transform } from "../../util/Transform";
import { DocComponent } from "../DocComponent";
import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
import { Doc } from "../../../new_fields/Doc";
-import { returnEmptyString } from "../../../Utils";
+import { random } from "animejs";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
x?: number;
y?: number;
width?: number;
height?: number;
+ jitterRotation: number;
}
const schema = createSchema({
- zoomBasis: "number",
zIndex: "number",
});
@@ -28,22 +28,36 @@ const FreeformDocument = makeInterface(schema, positionSchema);
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
- @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}) `; }
- @computed get X() { return this.props.x !== undefined ? this.props.x : this.Document.x || 0; }
- @computed get Y() { return this.props.y !== undefined ? this.props.y : this.Document.y || 0; }
- @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.width !== undefined ? this.props.width : this.Document.width || 0; }
- @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.props.height !== undefined ? this.props.height : this.Document.height || 0; }
- @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
+ @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
+ @computed get X() { return this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; }
+ @computed get Y() { return this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; }
+ @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.Document.width || 0; }
+ @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.Document.height || 0; }
@computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
@computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
@computed get scaleToOverridingWidth() { return this.width / NumCast(this.props.Document.width, this.width); }
+ @computed get renderScriptDim() {
+ if (this.Document.renderScript) {
+ let someView = Cast(this.Document.someView, Doc);
+ let minimap = Cast(this.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);
+ return { x: x, y: y, width: w, height: h };
+ }
+ }
+ return undefined;
+ }
+
contentScaling = () => this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? this.width / this.nativeWidth : 1;
panelWidth = () => this.props.PanelWidth();
panelHeight = () => this.props.PanelHeight();
getTransform = (): Transform => this.props.ScreenToLocalTransform()
.translate(-this.X, -this.Y)
- .scale(1 / this.contentScaling()).scale(1 / this.zoom / this.scaleToOverridingWidth)
+ .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth)
animateBetweenIcon = (icon: number[], stime: number, maximizing: boolean) => {
this.props.bringToFront(this.props.Document);
@@ -60,10 +74,13 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
}
borderRounding = () => {
- let br = StrCast(this.props.Document.layout instanceof Doc ? this.props.Document.layout.borderRounding : this.props.Document.borderRounding);
+ let ruleProvider = this.props.ruleProvider;
+ let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
+ let br = StrCast(this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout.borderRounding : this.props.Document.borderRounding);
+ br = !br && ruleRounding ? ruleRounding : br;
if (br.endsWith("%")) {
let percent = Number(br.substr(0, br.length - 1)) / 100;
- let nativeDim = Math.min(NumCast(this.props.Document.nativeWidth), NumCast(this.props.Document.nativeHeight));
+ let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight));
let minDim = percent * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight()));
return minDim;
}
@@ -75,6 +92,12 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
clusterColorFunc = (doc: Doc) => this.clusterColor;
+ get layoutDoc() {
+ // if this document's layout field contains a document (ie, a rendering template), then we will use that
+ // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
+ return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
+ }
+
render() {
const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
return (
@@ -83,13 +106,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
transformOrigin: "left top",
position: "absolute",
backgroundColor: "transparent",
- boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :
- this.clusterColor ? (
- this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :
- `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : undefined,
+ 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.layoutDoc.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` : // if it's a background & has a cluster color, make the shadow spread really big
+ this.clusterColor ? (
+ `${this.clusterColor} ${StrCast(this.layoutDoc.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : // if it's just in a cluster, make the shadown roughly match the cluster border extent
+ StrCast(this.layoutDoc.boxShadow, ""),
borderRadius: this.borderRounding(),
transform: this.transform,
- transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
+ transition: hasPosition ? "transform 1s" : StrCast(this.layoutDoc.transition),
width: this.width,
height: this.height,
zIndex: this.Document.zIndex || 0,
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c73e960d8..d0e117fe4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -13,6 +13,8 @@ import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
import { DragBox } from "./DragBox";
import { ButtonBox } from "./ButtonBox";
+import { PresBox } from "./PresBox";
+import { LinkFollowBox } from "../linking/LinkFollowBox";
import { IconBox } from "./IconBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
@@ -29,6 +31,7 @@ import { List } from "../../../new_fields/List";
import { Doc } from "../../../new_fields/Doc";
import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox";
import { ScriptField } from "../../../new_fields/ScriptField";
+import { fromPromise } from "mobx-utils";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
@@ -67,7 +70,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
}
get dataDoc() {
- if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+ if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
// if there is no dataDoc (ie, we're not rendering a template layout), but this document
// has a template layout document, then we will render the template layout but use
// this document as the data document for the layout.
@@ -107,7 +110,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
return <ObserverJsxParser
blacklistedAttrs={[]}
- components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, YoutubeBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox }}
bindings={this.CreateBindings()}
jsx={this.finalLayout}
showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4f1ec1cdb..2f0dbf0c9 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, runInAction, trace, observable } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import * as rp from "request-promise";
import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -9,12 +9,13 @@ import { List } from "../../../new_fields/List";
import { ObjectField } from "../../../new_fields/ObjectField";
import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
import { ScriptField } from '../../../new_fields/ScriptField';
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { RouteStore } from '../../../server/RouteStore';
import { emptyFunction, returnTrue, Utils } from "../../../Utils";
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";
@@ -33,15 +34,13 @@ import { DocComponent } from "../DocComponent";
import { EditableView } from '../EditableView';
import { MainView } from '../MainView';
import { OverlayView } from '../OverlayView';
-import { PresentationView } from "../presentationview/PresentationView";
import { ScriptBox } from '../ScriptBox';
import { ScriptingRepl } from '../ScriptingRepl';
-import { Template } from "./../Templates";
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import { FormattedTextBox } from './FormattedTextBox';
import React = require("react");
-import { IDisposable } from '../../northstar/utils/IDisposable';
+import { CompileScript, Scripting } from '../../util/Scripting';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
library.add(fa.faTrash);
@@ -90,14 +89,15 @@ export interface DocumentViewProps {
renderDepth: number;
showOverlays?: (doc: Doc) => { title?: string, caption?: string };
ContentScaling: () => number;
+ ruleProvider: Doc | undefined;
PanelWidth: () => number;
PanelHeight: () => number;
focus: (doc: Doc, willZoom: boolean, scale?: number) => void;
- selectOnLoad: boolean;
parentActive: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
zoomToScale: (scale: number) => void;
backgroundColor: (doc: Doc) => string | undefined;
@@ -139,6 +139,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _lastTap: number = 0;
private _doubleTap = false;
private _hitExpander = false;
+ private _hitTemplateDrag = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
_animateToIconDisposer?: IReactionDisposer;
@@ -147,14 +148,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
public get ContentDiv() { return this._mainCont.current; }
@computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
@computed get topMost(): boolean { return this.props.renderDepth === 0; }
- @computed get templates(): List<string> {
- let field = this.props.Document.templates;
- if (field && field instanceof List) {
- return field;
- }
- return new List<string>();
- }
- set templates(templates: List<string>) { this.props.Document.templates = templates; }
screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
@action
@@ -228,7 +221,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
get dataDoc() {
- if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
+ if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) {
// if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document
// has a template layout document, then we will render the template layout but use
// this document as the data document for the layout.
@@ -236,7 +229,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
}
- startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) {
+ startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean, applyAsTemplate?: boolean) {
if (this._mainCont.current) {
let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])];
@@ -247,6 +240,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dragData.xOffset = xoff;
dragData.yOffset = yoff;
dragData.moveDocument = this.props.moveDocument;
+ dragData.applyAsTemplate = applyAsTemplate;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
dragComplete: action(emptyFunction)
@@ -292,9 +286,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onClick = async (e: React.MouseEvent) => {
- if (e.nativeEvent.cancelBubble) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing.
- e.stopPropagation();
+ if (e.nativeEvent.cancelBubble || SelectionManager.IsSelected(this)) return; // needed because EditableView may stopPropagation which won't apparently stop this event from firing.
if (this.onClickHandler && this.onClickHandler.script) {
+ e.stopPropagation();
this.onClickHandler.script.run({ this: this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document });
e.preventDefault();
return;
@@ -302,6 +296,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let altKey = e.altKey;
let ctrlKey = e.ctrlKey;
if (this._doubleTap && this.props.renderDepth) {
+ e.stopPropagation();
let fullScreenAlias = Doc.MakeAlias(this.props.Document);
fullScreenAlias.templates = new List<string>();
Doc.UseDetailLayout(fullScreenAlias);
@@ -313,10 +308,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ if (BoolCast(this.props.Document.ignoreClick)) {
+ return;
+ }
+ e.stopPropagation();
SelectionManager.SelectDoc(this, e.ctrlKey);
let isExpander = (e.target as any).id === "isExpander";
- if (BoolCast(this.props.Document.isButton) || isExpander) {
- SelectionManager.DeselectAll();
+ if (BoolCast(this.props.Document.isButton) || this.props.Document.type === DocumentType.BUTTON || isExpander) {
let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
@@ -327,6 +325,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
// let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ SelectionManager.DeselectAll();
let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
if (altKey || ctrlKey) {
@@ -355,7 +354,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
else if (linkedDocs.length) {
- let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document));
+ SelectionManager.DeselectAll();
+ let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored);
+ let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0);
+ if (firstUnshown.length) first = [firstUnshown[0]];
let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]];
// @TODO: shouldn't always follow target context
@@ -368,8 +370,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false,
document => { // open up target if it's not already in view ...
+ let cv = this.props.ContainingCollectionView; // bcz: ugh --- maybe need to have a props.unfocus() method so that we leave things in the state we found them??
+ let px = cv && cv.props.Document.panX;
+ let py = cv && cv.props.Document.panY;
+ let s = cv && cv.props.Document.scale;
this.props.focus(this.props.Document, true, 1); // by zooming into the button document first
- setTimeout(() => this.props.addDocTab(document, undefined, maxLocation), 1000); // then after the 1sec animation, open up the target in a new tab
+ setTimeout(() => {
+ this.props.addDocTab(document, undefined, maxLocation);
+ cv && (cv.props.Document.panX = px);
+ cv && (cv.props.Document.panY = py);
+ cv && (cv.props.Document.scale = s);
+ }, 1000); // then after the 1sec animation, open up the target in a new tab
},
linkedFwdPage[altKey ? 1 : 0], targetContext);
}
@@ -377,29 +388,35 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
}
+
+
onPointerDown = (e: React.PointerEvent): void => {
if (e.nativeEvent.cancelBubble) return;
this._downX = e.clientX;
this._downY = e.clientY;
this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
- // if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) {
- // CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]);
- // e.stopPropagation();
- // } else {
+ 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) e.stopPropagation(); // events stop at the lowest document that is active.
document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
- // }
}
onPointerMove = (e: PointerEvent): void => {
- if (!e.cancelBubble && this.active) {
+ if (e.cancelBubble && this.active) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+ else if (!e.cancelBubble && this.active) {
if (!this.props.Document.excludeFromLibrary && (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3)) {
if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander);
+ this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, 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
@@ -423,17 +440,76 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@undoBatch
+ makeNativeViewClicked = (): void => {
+ makeNativeView(this.props.Document);
+ }
+ @undoBatch
+ makeCustomViewClicked = (): void => {
+ this.props.Document.nativeLayout = this.props.Document.layout;
+ this.props.Document.nativeType = this.props.Document.type;
+ this.props.Document.nonCustomAutoHeight = this.props.Document.autoHeight;
+ this.props.Document.nonCustomWidth = this.props.Document.nativeWidth;
+ this.props.Document.nonCustomHeight = this.props.Document.nativeHeight;
+ this.props.Document.nonCustomNativeWidth = this.props.Document.nativeWidth;
+ this.props.Document.nonCustomNativeHeight = this.props.Document.nativeHeight;
+ this.props.Document.nonCustomIgnoreAspect = this.props.Document.ignoreAspect;
+ PromiseValue(Cast(this.props.Document.customLayout, Doc)).then(custom => {
+ if (custom) {
+ this.props.Document.type = DocumentType.TEMPLATE;
+ this.props.Document.layout = custom;
+ !custom.nativeWidth && (this.props.Document.nativeWidth = 0);
+ !custom.nativeHeight && (this.props.Document.nativeHeight = 0);
+ !custom.nativeWidth && (this.props.Document.ignoreAspect = true);
+ this.props.Document.autoHeight = this.props.Document.autoHeight;
+ this.props.Document.width = this.props.Document.customWidth;
+ this.props.Document.height = this.props.Document.customHeight;
+ this.props.Document.nativeWidth = this.props.Document.customNativeWidth;
+ this.props.Document.nativeHeight = this.props.Document.customNativeHeight;
+ this.props.Document.ignoreAspect = this.props.Document.ignoreAspect;
+ this.props.Document.customAutoHeight = undefined;
+ this.props.Document.customWidth = undefined;
+ this.props.Document.customHeight = undefined;
+ this.props.Document.customNativeWidth = undefined;
+ this.props.Document.customNativeHeight = undefined;
+ this.props.Document.customIgnoreAspect = undefined;
+ } else {
+ let options = { title: "data", width: NumCast(this.props.Document.width), x: -NumCast(this.props.Document.width) / 2, y: -NumCast(this.props.Document.height) / 2, };
+ let fieldTemplate = this.props.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) :
+ this.props.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) :
+ Docs.Create.ImageDocument("http://www.cs.brown.edu", options);
+
+ fieldTemplate.backgroundColor = StrCast(this.props.Document.backgroundColor);
+ fieldTemplate.heading = 1;
+ fieldTemplate.autoHeight = true;
+
+ let docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: StrCast(this.Document.title) + "layout", width: NumCast(this.props.Document.width) + 20, height: Math.max(100, NumCast(this.props.Document.height) + 45) });
+ let proto = Doc.GetProto(docTemplate);
+ Doc.MakeMetadataFieldTemplate(fieldTemplate, proto, true);
+
+ Doc.ApplyTemplateTo(docTemplate, this.props.Document, undefined, false);
+ Doc.GetProto(this.dataDoc || this.props.Document).customLayout = this.props.Document.layout;
+ }
+ });
+ }
+
+ @undoBatch
makeBtnClicked = (): void => {
let doc = Doc.GetProto(this.props.Document);
- doc.isButton = !BoolCast(doc.isButton);
- if (doc.isButton) {
- if (!doc.nativeWidth) {
- doc.nativeWidth = this.props.Document[WidthSym]();
- doc.nativeHeight = this.props.Document[HeightSym]();
- }
+ if (doc.isButton || doc.onClick) {
+ doc.isButton = false;
+ doc.onClick = undefined;
} else {
- doc.nativeWidth = doc.nativeHeight = undefined;
+ doc.isButton = true;
}
+
+ // if (doc.isButton) {
+ // if (!doc.nativeWidth) {
+ // doc.nativeWidth = this.props.Document[WidthSym]();
+ // doc.nativeHeight = this.props.Document[HeightSym]();
+ // }
+ // } else {
+ // doc.nativeWidth = doc.nativeHeight = undefined;
+ // }
}
@undoBatch
@@ -457,6 +533,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`);
}
+ if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) {
+ Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc);
+ e.stopPropagation();
+ }
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.Document;
@@ -493,28 +573,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- @action
- addTemplate = (template: Template) => {
- this.templates.push(template.Layout);
- this.templates = this.templates;
- }
-
- @action
- removeTemplate = (template: Template) => {
- for (let i = 0; i < this.templates.length; i++) {
- if (this.templates[i] === template.Layout) {
- this.templates.splice(i, 1);
- break;
- }
- }
- this.templates = this.templates;
- }
- @action
- clearTemplates = () => {
- this.templates.length = 0;
- this.templates = this.templates;
- }
-
@undoBatch
@action
freezeNativeDimensions = (): void => {
@@ -528,15 +586,44 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@undoBatch
@action
+ makeIntoPortal = (): void => {
+ if (!DocListCast(this.props.Document.links).find(doc => {
+ if (Cast(doc.anchor2, Doc) instanceof Doc && (Cast(doc.anchor2, Doc) as Doc)!.title === this.props.Document.title + ".portal") return true;
+ return false;
+ })) {
+ let portalID = (this.props.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, "");
+ DocServer.GetRefField(portalID).then(existingPortal => {
+ let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: portalID });
+ DocUtils.MakeLink(this.props.Document, portal, undefined, portalID);
+ Doc.GetProto(this.props.Document).isButton = true;
+ })
+ }
+ }
+ @undoBatch
+ @action
+ toggleCustomView = (): void => {
+ if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) {
+ Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc)
+ } else {
+ if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) {
+ this.makeCustomViewClicked();
+ } else if (this.Document.nativeLayout) {
+ this.makeNativeViewClicked();
+ }
+ }
+ }
+
+ @undoBatch
+ @action
makeBackground = (): void => {
- this.props.Document.isBackground = !this.props.Document.isBackground;
- this.props.Document.isBackground && this.props.bringToFront(this.props.Document, true);
+ this.layoutDoc.isBackground = !this.layoutDoc.isBackground;
+ this.layoutDoc.isBackground && this.props.bringToFront(this.layoutDoc, true);
}
@undoBatch
@action
toggleLockPosition = (): void => {
- this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true;
+ this.layoutDoc.lockedPosition = BoolCast(this.layoutDoc.lockedPosition) ? undefined : true;
}
listen = async () => {
@@ -571,38 +658,54 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.dataDoc, "onRight"), icon: "caret-square-right" });
subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" });
cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
- let existingMake = ContextMenu.Instance.findByDescription("Make...");
- let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : [];
- makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
- makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
- makes.push({ description: "Edit OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") });
- makes.push({
- description: "Make Portal", event: () => {
- let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" });
- //Doc.GetProto(this.props.Document).subBulletDocs = new List<Doc>([portal]);
- //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
- //Doc.GetProto(this.props.Document).templates = new List<string>([Templates.Bullet.Layout]);
- //let coll = Docs.Create.StackingDocument([this.props.Document, portal], { x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".cont" });
- //this.props.addDocument && this.props.addDocument(coll);
- //this.props.removeDocument && this.props.removeDocument(this.props.Document);
- DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal");
- this.makeBtnClicked();
+
+ let existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
+ let 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: () => {
+ let compiled = CompileScript("toggleDetail(this)", {
+ params: { this: "Doc" },
+ typecheck: false,
+ editable: true,
+ });
+ if (compiled.compiled) {
+ this.Document.onClick = new ScriptField(compiled);
+ }
}, icon: "window-restore"
});
- !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" });
+ onClicks.push({ description: this.layoutDoc.ignoreClick ? "Select" : "Do Nothing", event: () => this.layoutDoc.ignoreClick = !this.layoutDoc.ignoreClick, icon: this.layoutDoc.ignoreClick ? "unlock" : "lock" });
+ onClicks.push({ description: this.props.Document.isButton || this.props.Document.onClick ? "Remove Click Behavior" : "Follow Link", event: this.makeBtnClicked, 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.ContainingCollectionView && this.props.ContainingCollectionView.props.Document;
+ 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 existing = ContextMenu.Instance.findByDescription("Layout...");
let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
-
- layoutItems.push({ description: `${this.props.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.props.Document.chromeStatus = (this.props.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
- layoutItems.push({ description: `${this.props.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.props.Document.autoHeight = !this.props.Document.autoHeight, icon: "plus" });
- layoutItems.push({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
- layoutItems.push({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
+ layoutItems.push({ description: this.props.Document.isBackground ? "As Foreground" : "As Background", event: this.makeBackground, icon: this.props.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" })
+ }
+ layoutItems.push({ description: `${this.layoutDoc.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.layoutDoc.chromeStatus = (this.layoutDoc.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" });
+ layoutItems.push({ description: `${this.layoutDoc.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" });
+ layoutItems.push({ description: this.props.Document.ignoreAspect || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
+ layoutItems.push({ description: this.layoutDoc.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.layoutDoc.lockedPosition) ? "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.props.Document.detailedLayout && !this.props.Document.isTemplate) {
layoutItems.push({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
}
+ if (this.props.Document.type !== DocumentType.COL && this.props.Document.type !== DocumentType.TEMPLATE) {
+ layoutItems.push({ description: "Use Custom Layout", event: this.makeCustomViewClicked, icon: "concierge-bell" });
+ } else if (this.props.Document.nativeLayout) {
+ layoutItems.push({ description: "Use Native Layout", event: this.makeNativeViewClicked, icon: "concierge-bell" });
+ }
!existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
if (!ClientUtils.RELEASE) {
let copies: ContextMenuProps[] = [];
@@ -614,54 +717,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
+ 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({
- description: "Download document", icon: "download", event: () => {
- const a = document.createElement("a");
- const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
- a.href = url;
- a.download = `DocExport-${this.props.Document[Id]}.zip`;
- a.click();
+ description: "Download document", icon: "download", event: async () => {
+ let y = 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': '*' }
+ }));
+ console.log(y);
+ // const a = document.createElement("a");
+ // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ // a.href = url;
+ // a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ // a.click();
}
});
- cm.addItem({
- description: "Import document", icon: "upload", event: () => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = ".zip";
- input.onchange = async _e => {
- const files = input.files;
- if (!files) return;
- const file = files[0];
- let formData = new FormData();
- formData.append('file', file);
- formData.append('remap', "true");
- const upload = Utils.prepend("/uploadDoc");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json === "error") {
- return;
- }
- const doc = await DocServer.GetRefField(json);
- if (!doc || !(doc instanceof Doc)) {
- return;
- }
- const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY);
- doc.x = x, doc.y = y;
- this.props.addDocument && this.props.addDocument(doc, false);
- };
- input.click();
- }
- });
+ cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, StrCast(this.props.Document.title), this.props.addDocument, this.props.removeDocument), icon: "file" });
cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
type User = { email: string, userDocumentId: string };
let usersMenu: ContextMenuProps[] = [];
try {
let stuff = await rp.get(Utils.prepend(RouteStore.getUsers));
const users: User[] = JSON.parse(stuff);
- usersMenu = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({
+ usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({
description: email, event: async () => {
const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc);
if (!userDocument) {
@@ -684,6 +763,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
runInAction(() => {
cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" });
+ if (!ClientUtils.RELEASE) {
+ let setWriteMode = (mode: DocServer.WriteMode) => {
+ DocServer.AclsMode = mode;
+ const mode1 = mode;
+ const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground;
+ DocServer.setFieldWriteMode("x", mode1);
+ DocServer.setFieldWriteMode("y", mode1);
+ DocServer.setFieldWriteMode("width", mode1);
+ DocServer.setFieldWriteMode("height", mode1);
+
+ DocServer.setFieldWriteMode("panX", mode2);
+ DocServer.setFieldWriteMode("panY", mode2);
+ DocServer.setFieldWriteMode("scale", mode2);
+ DocServer.setFieldWriteMode("viewType", mode2);
+ };
+ let 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" });
+ }
+
if (!this.topMost) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
@@ -709,7 +812,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
isSelected={this.isSelected}
select={this.select}
onClick={this.onClickHandler}
- selectOnLoad={this.props.selectOnLoad}
layoutKey={"layout"}
fitToBox={BoolCast(this.props.Document.fitToBox) ? true : this.props.fitToBox}
DataDoc={this.dataDoc} />);
@@ -718,7 +820,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
chromeHeight = () => {
let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined;
let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle);
- return showTitle ? 25 : 0;
+ let templates = Cast(this.layoutDoc.templates, listSpec("string"));
+ if (!showOverlays && templates instanceof List) {
+ templates.map(str => {
+ if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title";
+ });
+ }
+ return (showTitle ? 25 : 0) + 1;// bcz: why 8??
}
get layoutDoc() {
@@ -729,9 +837,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
render() {
- let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ?
+ let ruleProvider = this.props.ruleProvider;
+ let ruleColor = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(this.props.Document.heading)]) : undefined;
+ let ruleRounding = ruleProvider ? StrCast(Doc.GetProto(ruleProvider)["ruleRounding_" + NumCast(this.props.Document.heading)]) : undefined;
+ let colorSet = this.layoutDoc.backgroundColor !== this.layoutDoc.defaultBackgroundColor;
+ let clusterCol = this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground;
+
+ let backgroundColor = this.layoutDoc.isBackground || (clusterCol && !colorSet) ?
this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
- StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
+ ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
let foregroundColor = StrCast(this.layoutDoc.color);
var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
@@ -746,19 +860,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
}
let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
- let brushDegree = Doc.IsBrushedDegree(this.props.Document);
- let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding);
- let localScale = this.props.ScreenToLocalTransform().Scale * brushDegree;
+ let fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
+ let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding, ruleRounding);
+ let localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
+ let searchHighlight = (!this.props.Document.search_fields ? (null) :
+ <div key="search" style={{ position: "absolute", background: "yellow", bottom: "-20px", borderRadius: "5px", transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
+ {StrCast(this.props.Document.search_fields)}
+ </div>);
return (
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
color: foregroundColor,
- outlineColor: ["transparent", "maroon", "maroon"][brushDegree],
- outlineStyle: ["none", "dashed", "solid"][brushDegree],
- outlineWidth: brushDegree && !borderRounding ? `${localScale}px` : "0px",
- border: brushDegree && borderRounding ? `${["none", "dashed", "solid"][brushDegree]} ${["transparent", "maroon", "maroon"][brushDegree]} ${localScale}px` : undefined,
+ outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree],
+ outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree],
+ outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px",
+ border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined,
borderRadius: "inherit",
background: backgroundColor,
width: nativeWidth,
@@ -769,10 +887,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
>
- {!showTitle && !showCaption ? this.contents :
+ {!showTitle && !showCaption ?
+ this.props.Document.search_fields ? <div>
+ {this.contents}
+ {searchHighlight}
+ </div> :
+ this.contents :
<div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
-
- <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 33px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
+ <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 29px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
{this.contents}
</div>
{!showTitle ? (null) :
@@ -794,12 +916,77 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
{!showCaption ? (null) :
<div style={{ position: "absolute", bottom: 0, transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
- <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} selectOnLoad={this.props.selectOnLoad} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
+ <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
</div>
}
+ {searchHighlight}
</div>
}
</div>
);
}
-} \ No newline at end of file
+}
+
+
+let makeNativeView = (doc: any): void => {
+ doc.layout = doc.nativeLayout;
+ doc.nativeLayout = undefined;
+ doc.type = doc.nativeType;
+
+ doc.customAutoHeight = doc.autoHeight;
+ doc.customWidth = doc.width;
+ doc.customHeight = doc.height;
+ doc.customNativeWidth = doc.nativeWidth;
+ doc.customNativeHeight = doc.nativeHeight;
+ doc.customIgnoreAspect = doc.ignoreAspect;
+
+ doc.autoHeight = doc.nonCustomAutoHeight;
+ doc.width = doc.nonCustomWidth;
+ doc.height = doc.nonCustomHeight;
+ doc.nativeWidth = doc.nonCustomNativeWidth;
+ doc.nativeHeight = doc.nonCustomNativeHeight;
+ doc.ignoreAspect = doc.nonCustomIgnoreAspect;
+ doc.nonCustomAutoHeight = undefined;
+ doc.nonCustomWidth = undefined;
+ doc.nonCustomHeight = undefined;
+ doc.nonCustomNativeWidth = undefined;
+ doc.nonCustomNativeHeight = undefined;
+ doc.nonCustomIgnoreAspect = undefined;
+}
+let makeCustomView = (doc: any): void => {
+ doc.nativeLayout = doc.layout;
+ doc.nativeType = doc.type;
+ doc.nonCustomAutoHeight = doc.autoHeight;
+ doc.nonCustomWidth = doc.nativeWidth;
+ doc.nonCustomHeight = doc.nativeHeight;
+ doc.nonCustomNativeWidth = doc.nativeWidth;
+ doc.nonCustomNativeHeight = doc.nativeHeight;
+ doc.nonCustomIgnoreAspect = doc.ignoreAspect;
+ let custom = doc.customLayout as Doc;
+ if (custom instanceof Doc) {
+ doc.type = DocumentType.TEMPLATE;
+ doc.layout = custom;
+ !custom.nativeWidth && (doc.nativeWidth = 0);
+ !custom.nativeHeight && (doc.nativeHeight = 0);
+ !custom.nativeWidth && (doc.ignoreAspect = true);
+ doc.autoHeight = doc.autoHeight;
+ doc.width = doc.customWidth;
+ doc.height = doc.customHeight;
+ doc.nativeWidth = doc.customNativeWidth;
+ doc.nativeHeight = doc.customNativeHeight;
+ doc.ignoreAspect = doc.ignoreAspect;
+ doc.customAutoHeight = undefined;
+ doc.customWidth = undefined;
+ doc.customHeight = undefined;
+ doc.customNativeWidth = undefined;
+ doc.customNativeHeight = undefined;
+ doc.customIgnoreAspect = undefined;
+ }
+}
+Scripting.addGlobal(function toggleDetail(doc: any) {
+ if (doc.type !== DocumentType.COL && doc.type !== DocumentType.TEMPLATE) {
+ makeCustomView(doc);
+ } else if (doc.nativeLayout) {
+ makeNativeView(doc);
+ }
+}); \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 3287949e2..943d181d6 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -30,15 +30,16 @@ export interface FieldViewProps {
leaveNativeSize?: boolean;
fitToBox?: boolean;
ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
+ ruleProvider: Doc | undefined;
Document: Doc;
DataDoc?: Doc;
onClick?: ScriptField;
isSelected: () => boolean;
select: (isCtrlPressed: boolean) => void;
renderDepth: number;
- selectOnLoad: boolean;
addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
removeDocument?: (document: Doc) => boolean;
moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
@@ -57,6 +58,7 @@ export interface FieldViewProps {
export class FieldView extends React.Component<FieldViewProps> {
public static LayoutString(fieldType: { name: string }, fieldStr: string = "data", fieldExt: string = "") {
return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} fieldExt={"${fieldExt}"} />`;
+ //"<ImageBox {...props} />"
}
@computed
@@ -78,6 +80,9 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) {
return <ImageBox {...this.props} leaveNativeSize={true} />;
}
+ // else if (field instaceof PresBox) {
+ // return <PresBox {...this.props} />;
+ // }
else if (field instanceof IconField) {
return <IconBox {...this.props} />;
}
@@ -103,7 +108,6 @@ export class FieldView extends React.Component<FieldViewProps> {
// PanelWidth={returnHundred}
// PanelHeight={returnHundred}
// renderDepth={0} //TODO Why is renderDepth reset?
- // selectOnLoad={false}
// focus={emptyFunction}
// isSelected={this.props.isSelected}
// select={returnFalse}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 1b537cc52..0d7277cbe 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -4,7 +4,6 @@
width: 100%;
height: 100%;
min-height: 100%;
- font-family: $serif;
}
.ProseMirror:focus {
@@ -65,4 +64,113 @@
.em {
font-style: italic;
-} \ No newline at end of file
+}
+
+.userMarkOpen {
+ background: rgba(255, 255, 0, 0.267);
+ display: inline;
+}
+.userMark {
+ 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;
+ align-content: center;
+}
+
+footnote {
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ div {
+ padding : 0 !important;
+ }
+}
+
+footnote::after {
+ content: counter(prosemirror-footnote);
+ vertical-align: super;
+ font-size: 75%;
+ counter-increment: prosemirror-footnote;
+}
+
+.ProseMirror {
+ counter-reset: prosemirror-footnote;
+ }
+
+.footnote-tooltip {
+ cursor: auto;
+ font-size: 75%;
+ position: absolute;
+ left: -30px;
+ top: calc(100% + 10px);
+ background: silver;
+ padding: 3px;
+ border-radius: 2px;
+ max-width: 100px;
+ min-width: 50px;
+ width: max-content;
+}
+
+.prosemirror-attribution {
+ font-size: 8px;
+}
+.footnote-tooltip::before {
+ border: 5px solid silver;
+ border-top-width: 0px;
+ border-left-color: transparent;
+ border-right-color: transparent;
+ position: absolute;
+ top: -5px;
+ left: 27px;
+ content: " ";
+ height: 0;
+ width: 0;
+}
+
+.formattedTextBox-summarizer {
+ opacity :0.5;
+ position: relative;
+ width:40px;
+ height:20px;
+}
+.formattedTextBox-summarizer::after{
+ content: "←" ;
+}
+
+.formattedTextBox-summarizer-collapsed {
+ opacity :0.5;
+ position: relative;
+ width:40px;
+ height:20px;
+}
+.formattedTextBox-summarizer-collapsed::after {
+ content: "...";
+}
+
+ol { counter-reset: deci1 0;}
+.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 }
+.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 }
+.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 }
+.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 }
+.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 }
+.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 }
+.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 }
+.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; width: 30}
+.decimal2:before { content: counter(deci1) "." counter(deci2) ")"; counter-increment: deci2; display:inline-block; width: 35}
+.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ")"; counter-increment: deci3; display:inline-block; width: 35}
+.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ")"; counter-increment: deci4; display:inline-block; width: 40}
+.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ")"; counter-increment: deci5; display:inline-block; width: 40}
+.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ")"; counter-increment: deci6; display:inline-block; 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; width: 50}
+.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ")"; counter-increment: ualph; display:inline-block; width: 35 }
+.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ")"; counter-increment: lroman;display:inline-block; 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; width: 35}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index b04b041a9..77e29632e 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,44 +1,49 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, Lambda, observable, reaction } from "mobx";
+import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons';
+import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-import { Fragment, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model";
-import { EditorState, Plugin, Transaction } from "prosemirror-state";
+import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } from "prosemirror-model";
+import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { DateField } from '../../../new_fields/DateField';
import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc";
import { Copy, Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
-import { RichTextField } from "../../../new_fields/RichTextField";
+import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField";
+import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils } from '../../../Utils';
+import { Utils, numberRange, timenow } from '../../../Utils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from "../../util/DragManager";
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import { inpRules } from "../../util/RichTextRules";
-import { ImageResizeView, schema, SummarizedView } from "../../util/RichTextSchema";
+import { ImageResizeView, schema, SummarizedView, OrderedListView, FootnoteView } 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 { DocComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
-import { MainOverlayTextBox } from '../MainOverlayTextBox';
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
+import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
+import { DocumentDecorations } from '../DocumentDecorations';
+import { DictationManager } from '../../util/DictationManager';
+import { ReplaceStep } from 'prosemirror-transform';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment';
+import { inputRules } from 'prosemirror-inputrules';
library.add(faEdit);
-library.add(faSmile, faTextHeight);
+library.add(faSmile, faTextHeight, faUpload);
-// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
-//
+export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
export interface FormattedTextBoxProps {
isOverlay?: boolean;
@@ -53,31 +58,42 @@ const richTextSchema = createSchema({
documentText: "string"
});
+export const GoogleRef = "googleDocId";
+
type RichTextDocument = makeInterface<[typeof richTextSchema]>;
const RichTextDocument = makeInterface(richTextSchema);
+type PullHandler = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => void;
+
@observer
export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string = "data") {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
- public static Instance: FormattedTextBox;
- private _ref: React.RefObject<HTMLDivElement>;
+ private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
+ private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
- private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
private _applyingChange: boolean = false;
private _linkClicked = "";
- private _reactionDisposer: Opt<IReactionDisposer>;
+ private _nodeClicked: any;
+ private _undoTyping?: UndoManager.Batch;
private _searchReactionDisposer?: Lambda;
+ 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 dropDisposer?: DragManager.DragDropDisposer;
- public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
- @observable _entered = false;
+ @observable private _fontSize = 13;
+ @observable private _fontFamily = "Arial";
+ @observable private _fontAlign = "";
+ @observable private _entered = false;
@observable public static InputBoxOverlay?: FormattedTextBox = undefined;
+ public static SelectOnLoad = "";
public static InputBoxOverlayScroll: number = 0;
public static IsFragment(html: string) {
return html.indexOf("data-pm-slice") !== -1;
@@ -106,88 +122,123 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@undoBatch
public setFontColor(color: string) {
- let self = this;
- if (this._editorView!.state.selection.from === this._editorView!.state.selection.to) return false;
- if (this._editorView!.state.selection.to - this._editorView!.state.selection.from > this._editorView!.state.doc.nodeSize - 3) {
+ let 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.props.Document.color = color;
}
- let colorMark = this._editorView!.state.schema.mark(this._editorView!.state.schema.marks.pFontColor, { color: color });
- this._editorView!.dispatch(this._editorView!.state.tr.addMark(this._editorView!.state.selection.from,
- this._editorView!.state.selection.to, colorMark));
+ let 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;
}
constructor(props: FieldViewProps) {
super(props);
- FormattedTextBox.Instance = this;
- this._ref = React.createRef();
if (this.props.isOverlay) {
DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
}
-
- document.addEventListener("paste", this.paste);
}
+ public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
+
@computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "dummy"); }
@computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
- paste = (e: ClipboardEvent) => {
- if (e.clipboardData && this._editorView) {
- let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`;
- for (let i = 0; i < e.clipboardData.items.length; i++) {
- let item = e.clipboardData.items.item(i);
- if (item.type === "text/plain") {
- item.getAsString((text) => {
- let pdfPasteIndex = text.indexOf(pdfPasteText);
- if (pdfPasteIndex > -1) {
- let insertText = text.substr(0, pdfPasteIndex);
- const tx = this._editorView!.state.tr.insertText(insertText);
- // tx.setSelection(new Selection(tx.))
- const state = this._editorView!.state;
- this._editorView!.dispatch(tx);
- if (FormattedTextBox._toolTipTextMenu) {
- // this._toolTipTextMenu.makeLinkWithState(state)
- }
- e.stopPropagation();
- e.preventDefault();
- }
- });
+ // this should be internal to prosemirror, but is needed
+ // here to make sure that footnote view nodes in the overlay editor
+ // get removed when they're not selected.
+
+ syncNodeSelection(view: any, sel: any) {
+ if (sel instanceof NodeSelection) {
+ var desc = view.docView.descAt(sel.from);
+ if (desc !== view.lastSelectedViewDesc) {
+ if (view.lastSelectedViewDesc) {
+ view.lastSelectedViewDesc.deselectNode();
+ view.lastSelectedViewDesc = null;
}
+ if (desc) { desc.selectNode(); }
+ view.lastSelectedViewDesc = desc;
+ }
+ } else {
+ if (view.lastSelectedViewDesc) {
+ view.lastSelectedViewDesc.deselectNode();
+ view.lastSelectedViewDesc = null;
}
}
}
+ linkOnDeselect: Map<string, string> = new Map();
+
+ doLinkOnDeselect() {
+ Array.from(this.linkOnDeselect.entries()).map(entry => {
+ let key = entry[0];
+ let value = entry[1];
+ let 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);
+ DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument);
+ if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; }
+ else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true);
+ })
+ });
+ })
+ this.linkOnDeselect.clear();
+ }
+
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
+ let metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata);
+ if (metadata) {
+ let 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("::");
+ if (split.length > 1 && split[1]) {
+ let key = split[0];
+ let 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 mval = this._editorView!.state.schema.marks.metadataVal.create();
+ let 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);
- FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = true);
this._editorView.updateState(state);
- FormattedTextBox._toolTipTextMenu && (FormattedTextBox._toolTipTextMenu.HackToFixTextSelectionGlitch = false);
- if (state.selection.empty && FormattedTextBox._toolTipTextMenu) {
- const marks = tx.storedMarks;
- console.log(marks);
- if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
+ this.syncNodeSelection(this._editorView, this._editorView.state.selection); // bcz: ugh -- shouldn't be needed but without this the overlay view's footnote popup doesn't get deselected
+ if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) {
+ FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks);
}
+
this._applyingChange = true;
- const fieldkey = "preview";
- if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
- if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now()));
- this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"));
+ this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
+ this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
this._applyingChange = false;
- let title = StrCast(this.dataDoc.title);
- if (title && title.startsWith("-") && this._editorView && !this.Document.customTitle) {
- let str = this._editorView.state.doc.textContent;
- let titlestr = str.substr(0, Math.min(40, str.length));
- this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
- }
+ this.updateTitle();
+ this.tryUpdateHeight();
+ }
+ }
+
+ 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));
+ this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
}
}
public highlightSearchTerms = (terms: String[]) => {
if (this._editorView && (this._editorView as any).docView) {
- const fieldkey = "preview";
const doc = this._editorView.state.doc;
const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
@@ -217,23 +268,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
});
- // const fieldkey = 'search_string';
- // if (Object.keys(this.props.Document).indexOf(fieldkey) !== -1) {
- // this.props.Document[fieldkey] = undefined;
- // }
- // else this.props.Document.proto![fieldkey] = undefined;
- // }
}
}
-
+ setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => {
+ let view = this._editorView!;
+ let mid = view.state.doc.resolve(Math.round((start + end) / 2));
+ let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened });
+ view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark).setSelection(new TextSelection(mid)));
+ }
protected createDropTarget = (ele: HTMLDivElement) => {
this._proseRef = ele;
- if (this.dropDisposer) {
- this.dropDisposer();
- }
- if (ele) {
- this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
- }
+ this.dropDisposer && this.dropDisposer();
+ ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }));
}
@undoBatch
@@ -241,41 +287,112 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
drop = async (e: Event, de: DragManager.DropEvent) => {
// We're dealing with a link to a document
if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) {
+ let target = de.data.embeddableSourceDoc;
// We're dealing with an internal document drop
let url = de.data.urlField.url.href;
let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image;
- this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url })));
+ let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y });
+ this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] })));
+ DocUtils.MakeLink(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]);
e.stopPropagation();
- } else {
- if (de.data instanceof DragManager.DocumentDragData) {
- this.props.Document.layout = de.data.draggedDocuments[0];
- de.data.draggedDocuments[0].isTemplate = true;
+ } else if (de.data instanceof DragManager.DocumentDragData) {
+ const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0];
+ if (draggedDoc && draggedDoc.type === DocumentType.TEXT) {
+ if (!Doc.AreProtosEqual(draggedDoc, this.props.Document)) {
+ draggedDoc.isTemplate = true;
+ if (typeof (draggedDoc.layout) === "string") {
+ let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc);
+ layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`);
+ this.props.Document.layout = layoutDelegateToOverrideFieldKey;
+ } else {
+ this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc;
+ }
+ }
e.stopPropagation();
}
}
}
- componentDidMount() {
- const config = {
+ recordKeyHandler = (e: KeyboardEvent) => {
+ if (SelectionManager.SelectedDocuments().length && this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) {
+ if (e.key === "R" && e.altKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ this.recordBullet();
+ }
+ }
+ }
+
+ recordBullet = async () => {
+ let completedCue = "end session";
+ let results = await DictationManager.Controls.listen({
+ interimHandler: this.setCurrentBulletContent,
+ continuous: { indefinite: false },
+ terminators: [completedCue, "bullet", "next"]
+ });
+ if (results && [DictationManager.Controls.Infringed, completedCue].includes(results)) {
+ DictationManager.Controls.stop();
+ return;
+ }
+ this.nextBullet(this._editorView!.state.selection.to);
+ setTimeout(this.recordBullet, 2000);
+ }
+
+ setCurrentBulletContent = (value: string) => {
+ if (this._editorView) {
+ let state = this._editorView.state;
+ let from = state.selection.from;
+ let 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);
+ this._editorView.dispatch(state.tr.setSelection(updated));
+ }
+ }
+
+ nextBullet = (pos: number) => {
+ if (this._editorView) {
+ let frag = Fragment.fromArray(this.newListItems(2));
+ let slice = new Slice(frag, 2, 2);
+ let state = this._editorView.state;
+ this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice)));
+ pos += 4;
+ state = this._editorView.state;
+ this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos)));
+ }
+ }
+
+ private newListItems = (count: number) => {
+ return numberRange(count).map(x => schema.nodes.list_item.create(undefined, schema.nodes.paragraph.create()));
+ }
+
+ _keymap: any = undefined;
+ @computed get config() {
+ this._keymap = buildKeymap(schema);
+ return {
schema,
- inpRules, //these currently don't do anything, but could eventually be helpful
plugins: this.props.isOverlay ? [
+ inputRules(inpRules),
this.tooltipTextMenuPlugin(),
history(),
- keymap(buildKeymap(schema)),
+ keymap(this._keymap),
keymap(baseKeymap),
// this.tooltipLinkingMenuPlugin(),
new Plugin({
props: {
attributes: { class: "ProseMirror-example-setup-style" }
}
- })
+ }),
+ formattedTextBoxCommentPlugin
] : [
history(),
- keymap(buildKeymap(schema)),
+ keymap(this._keymap),
keymap(baseKeymap),
]
};
+ }
+
+ componentDidMount() {
if (!this.props.isOverlay) {
this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
@@ -287,19 +404,48 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}, { fireImmediately: true });
}
+ this.pullFromGoogleDoc(this.checkState);
+ this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true);
+
this._reactionDisposer = reaction(
() => {
const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined;
- return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
+ return field ? field.Data : Blank;
},
- field2 => this._editorView && !this._applyingChange &&
- this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2)))
+ incomingValue => {
+ if (this._editorView && !this._applyingChange) {
+ let updatedState = JSON.parse(incomingValue);
+ this._editorView.updateState(EditorState.fromJSON(this.config, updatedState));
+ this.tryUpdateHeight();
+ }
+ }
);
- this.props.isOverlay && (this._heightReactionDisposer = reaction(
+ this._pullReactionDisposer = reaction(
+ () => this.props.Document[Pulls],
+ () => {
+ if (!DocumentDecorations.hasPulledHack) {
+ DocumentDecorations.hasPulledHack = true;
+ let unchanged = this.dataDoc.unchanged;
+ this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState);
+ }
+ }
+ );
+
+ this._pushReactionDisposer = reaction(
+ () => this.props.Document[Pushes],
+ () => {
+ if (!DocumentDecorations.hasPushedHack) {
+ DocumentDecorations.hasPushedHack = true;
+ this.pushToGoogleDoc();
+ }
+ }
+ );
+
+ this._heightReactionDisposer = reaction(
() => this.props.Document[WidthSym](),
() => this.tryUpdateHeight()
- ));
+ );
this._textReactionDisposer = reaction(
() => this.extensionDoc,
@@ -311,7 +457,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this.dataDoc.lastModified = undefined;
}
}, { fireImmediately: true });
- this.setupEditor(config, this.dataDoc, this.props.fieldKey);
+
+
+ this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
this._searchReactionDisposer = reaction(() => {
return StrCast(this.props.Document.search_string);
@@ -328,6 +476,120 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this.unhighlightSearchTerms();
}
}, { fireImmediately: true });
+
+
+ this._rulesReactionDisposer = reaction(() => {
+ let ruleProvider = this.props.ruleProvider;
+ let heading = NumCast(this.props.Document.heading);
+ if (ruleProvider instanceof Doc) {
+ return {
+ align: StrCast(ruleProvider["ruleAlign_" + heading], ""),
+ font: StrCast(ruleProvider["ruleFont_" + heading], "Arial"),
+ size: NumCast(ruleProvider["ruleSize_" + heading], 13)
+ };
+ }
+ return undefined;
+ },
+ action((rules: any) => {
+ this._fontFamily = rules ? rules.font : "Arial";
+ this._fontSize = rules ? rules.size : 13;
+ rules && setTimeout(() => {
+ const view = this._editorView!;
+ if (this._proseRef) {
+ let 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));
+ } else if (n.node && n.node.type === view.state.schema.nodes.paragraph) {
+ view.dispatch(view.state.tr.setNodeMarkup(0, n.node.type, { ...n.node.attrs, align: rules.align }));
+ }
+ this.tryUpdateHeight();
+ }
+ }, 0);
+ }), { fireImmediately: true }
+ );
+
+ setTimeout(() => this.tryUpdateHeight(), 0);
+ }
+
+ pushToGoogleDoc = async () => {
+ this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+ let modes = GoogleApiClientUtils.WriteMode;
+ let mode = modes.Replace;
+ let reference: Opt<GoogleApiClientUtils.Reference> = Cast(this.dataDoc[GoogleRef], "string");
+ if (!reference) {
+ mode = modes.Insert;
+ reference = { service: GoogleApiClientUtils.Service.Documents, title: StrCast(this.dataDoc.title) };
+ }
+ let redo = async () => {
+ let data = Cast(this.dataDoc.data, RichTextField);
+ if (this._editorView && reference && data) {
+ let content = data[ToPlainText]();
+ let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode });
+ response && (this.dataDoc[GoogleRef] = response.documentId);
+ let pushSuccess = response !== undefined && !("errors" in response);
+ dataDoc.unchanged = pushSuccess;
+ DocumentDecorations.Instance.startPushOutcome(pushSuccess);
+ }
+ };
+ let undo = () => {
+ let content = exportState.body;
+ if (reference && content) {
+ GoogleApiClientUtils.Docs.write({ reference, content, mode });
+ }
+ };
+ UndoManager.AddEvent({ undo, redo });
+ redo();
+ });
+ }
+
+ pullFromGoogleDoc = async (handler: PullHandler) => {
+ let dataDoc = this.dataDoc;
+ let documentId = StrCast(dataDoc[GoogleRef]);
+ let exportState: GoogleApiClientUtils.ReadResult = {};
+ if (documentId) {
+ exportState = await GoogleApiClientUtils.Docs.read({ identifier: documentId });
+ }
+ UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls);
+ }
+
+ updateState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+ let pullSuccess = false;
+ if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
+ const data = Cast(dataDoc.data, RichTextField);
+ if (data instanceof RichTextField) {
+ pullSuccess = true;
+ dataDoc.data = new RichTextField(data[FromPlainText](exportState.body));
+ setTimeout(() => {
+ if (this._editorView) {
+ let state = this._editorView.state;
+ let end = state.doc.content.size - 1;
+ this._editorView.dispatch(state.tr.setSelection(TextSelection.create(state.doc, end, end)));
+ }
+ }, 0);
+ dataDoc.title = exportState.title;
+ this.Document.customTitle = true;
+ dataDoc.unchanged = true;
+ }
+ } else {
+ delete dataDoc[GoogleRef];
+ }
+ DocumentDecorations.Instance.startPullOutcome(pullSuccess);
+ }
+
+ checkState = (exportState: GoogleApiClientUtils.ReadResult, dataDoc: Doc) => {
+ if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) {
+ let data = Cast(dataDoc.data, RichTextField);
+ if (data) {
+ let storedPlainText = data[ToPlainText]() + "\n";
+ let receivedPlainText = exportState.body;
+ let storedTitle = dataDoc.title;
+ let receivedTitle = exportState.title;
+ let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle;
+ dataDoc.unchanged = unchanged;
+ DocumentDecorations.Instance.setPullState(unchanged);
+ }
+ }
}
clipboardTextSerializer = (slice: Slice): string => {
@@ -384,7 +646,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (link) {
cbe.clipboardData!.setData("dash/linkDoc", link[Id]);
linkId = link[Id];
- let frag = addMarkToFrag(slice.content);
+ let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title)));
slice = new Slice(frag, slice.openStart, slice.openEnd);
var tr = view.state.tr.replaceSelection(slice);
view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"));
@@ -394,24 +656,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return true;
- function addMarkToFrag(frag: Fragment) {
+ function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) {
const nodes: Node[] = [];
- frag.forEach(node => nodes.push(addLinkMark(node)));
+ frag.forEach(node => nodes.push(marker(node)));
return Fragment.fromArray(nodes);
}
- function addLinkMark(node: Node) {
+ function addLinkMark(node: Node, title: string) {
if (!node.isText) {
- const content = addMarkToFrag(node.content);
+ const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title));
return node.copy(content);
}
const marks = [...node.marks];
const linkIndex = marks.findIndex(mark => mark.type.name === "link");
- const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight" });
- if (linkIndex !== -1) {
- marks.splice(linkIndex, 1, link);
- } else {
- marks.push(link);
- }
+ const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight", title: title, docref: true });
+ marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link);
return node.mark(marks);
}
}
@@ -429,47 +687,70 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
if (this._proseRef) {
+ let self = this;
+ this._editorView && this._editorView.destroy();
this._editorView = new EditorView(this._proseRef, {
state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- image(node, view, getPos) { return new ImageResizeView(node, view, getPos); },
+ image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
star(node, view, getPos) { return new SummarizedView(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,
});
+ (this._editorView as any).isOverlay = this.props.isOverlay;
if (startup) {
Doc.GetProto(doc).documentText = undefined;
this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
}
}
- if (this.props.selectOnLoad) {
- if (!this.props.isOverlay) this.props.select(false);
- else this._editorView!.focus();
- this.tryUpdateHeight();
+ let selectOnLoad = this.props.Document[Id] === FormattedTextBox.SelectOnLoad;
+ if (selectOnLoad) {
+ FormattedTextBox.SelectOnLoad = "";
+ this.props.select(false);
+ }
+ else if (this.props.isOverlay) 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: timenow() })];
+ }
+ getFont(font: string) {
+ switch (font) {
+ case "Arial": return schema.marks.arial.create();
+ case "Times New Roman": return schema.marks.timesNewRoman.create();
+ case "Georgia": return schema.marks.georgia.create();
+ case "Comic Sans MS": return schema.marks.comicSans.create();
+ case "Tahoma": return schema.marks.tahoma.create();
+ case "Impact": return schema.marks.impact.create();
+ case "ACrimson Textrial": return schema.marks.crimson.create();
}
+ return schema.marks.arial.create();
}
componentWillUnmount() {
- this._editorView && this._editorView.destroy();
+ 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._editorView && this._editorView.destroy();
}
+
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));
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 (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
- //this._toolTipTextMenu.tooltip.style.opacity = "0";
- }
}
let ctrlKey = e.ctrlKey;
if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
@@ -481,6 +762,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
}
+ 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);
+ href = link && link.attrs.href;
+ location = link && link.attrs.location;
+ }
if (href) {
if (href.indexOf(Utils.prepend("/doc/")) === 0) {
this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
@@ -501,7 +789,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
} else if (jumpToDoc) {
DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
-
+ } else {
+ DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"));
}
}
});
@@ -522,10 +811,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
e.preventDefault();
}
}
+
onPointerUp = (e: React.PointerEvent): void => {
- if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
- //this._toolTipTextMenu.tooltip.style.opacity = "1";
- }
+ FormattedTextBoxComment.textBox = this;
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
@@ -533,6 +821,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@action
onFocused = (e: React.FocusEvent): void => {
+ document.removeEventListener("keypress", this.recordKeyHandler);
+ document.addEventListener("keypress", this.recordKeyHandler);
+ this.tryUpdateHeight();
if (!this.props.isOverlay) {
FormattedTextBox.InputBoxOverlay = this;
} else {
@@ -549,6 +840,17 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
onClick = (e: React.MouseEvent): void => {
+ // 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.
+ if (this.props.isSelected() && e.nativeEvent.offsetX < 40) {
+ let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
+ 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)) {
+ this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
+ }
+ }
+ }
this._proseRef!.focus();
if (this._linkClicked) {
this._linkClicked = "";
@@ -570,7 +872,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
}
});
- //this.props.Document.tooltip = self._toolTipTextMenu;
}
tooltipLinkingMenuPlugin() {
@@ -582,94 +883,71 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
});
}
onBlur = (e: any) => {
+ document.removeEventListener("keypress", this.recordKeyHandler);
if (this._undoTyping) {
this._undoTyping.end();
this._undoTyping = undefined;
}
+ this.doLinkOnDeselect();
}
- public _undoTyping?: UndoManager.Batch;
onKeyPress = (e: React.KeyboardEvent) => {
if (e.key === "Escape") {
SelectionManager.DeselectAll();
}
e.stopPropagation();
- if (e.key === "Tab") e.preventDefault();
- // stop propagation doesn't seem to stop propagation of native keyboard events.
- // so we set a flag on the native event that marks that the event's been handled.
- (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
- 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));
- this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+ if (e.key === "Tab" || e.key === "Enter") {
+ e.preventDefault();
}
+ this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }));
+
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");
}
- this.tryUpdateHeight();
}
@action
tryUpdateHeight() {
- if (this.props.Document.autoHeight && this.props.isOverlay) {
- let xf = this._ref.current!.getBoundingClientRect();
- let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height);
+ const ChromeHeight = this.props.ChromeHeight;
+ let sh = this._ref.current ? this._ref.current.scrollHeight : 0;
+ if (!this.props.isOverlay && this.props.Document.autoHeight && sh !== 0) {
let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
let dh = NumCast(this.props.Document.height, 0);
- let sh = scrBounds.height;
- const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight;
this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0));
this.dataDoc.nativeHeight = nh ? sh : undefined;
}
}
- @action
- onPointerEnter = (e: React.PointerEvent) => {
- this._entered = true;
- }
- @action
- onPointerLeave = (e: React.PointerEvent) => {
- this._entered = false;
- }
-
- specificContextMenu = (e: React.MouseEvent): void => {
- // let subitems: ContextMenuProps[] = [];
- // subitems.push({
- // description: BoolCast(this.props.Document.autoHeight) ? "Manual Height" : "Auto Height",
- // event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight)), icon: "expand-arrows-alt"
- // });
- // ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" });
- }
render() {
- let self = this;
let style = this.props.isOverlay ? "scroll" : "hidden";
let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : "";
- let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground ||
- (this.props.Document.isButton && !this.props.isSelected()) ? "none" : "all";
+ let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground
+ //|| (this.props.Document.isButton && !this.props.isSelected())
+ ? "none" : "all";
Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
return (
<div className={`formattedTextBox-cont-${style}`} ref={this._ref}
style={{
overflowY: this.props.Document.autoHeight ? "hidden" : "auto",
- height: this.props.height ? this.props.height : undefined,
+ height: this.props.Document.autoHeight ? "max-content" : this.props.height ? this.props.height : undefined,
background: this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : undefined,
opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
pointerEvents: interactive,
- fontSize: "13px"
+ fontSize: this._fontSize,
+ fontFamily: this._fontFamily,
}}
onKeyDown={this.onKeyPress}
onFocus={this.onFocused}
onClick={this.onClick}
- onContextMenu={this.specificContextMenu}
onBlur={this.onBlur}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
onMouseDown={this.onMouseDown}
onWheel={this.onPointerWheel}
- onPointerEnter={this.onPointerEnter}
- onPointerLeave={this.onPointerLeave}
+ onPointerEnter={action(() => this._entered = true)}
+ onPointerLeave={action(() => this._entered = false)}
>
- <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap" }} />
+ <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: (this.props.Document.isButton && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
</div>
);
}
diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss
new file mode 100644
index 000000000..792cee182
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBoxComment.scss
@@ -0,0 +1,34 @@
+.FormattedTextBox-tooltip {
+ position: absolute;
+ pointer-events: none;
+ z-index: 20;
+ background: white;
+ border: 1px solid silver;
+ border-radius: 2px;
+ padding: 2px 10px;
+ margin-bottom: 7px;
+ -webkit-transform: translateX(-50%);
+ transform: translateX(-50%);
+ }
+ .FormattedTextBox-tooltip:before {
+ content: "";
+ height: 0; width: 0;
+ position: absolute;
+ left: 50%;
+ margin-left: -5px;
+ bottom: -6px;
+ border: 5px solid transparent;
+ border-bottom-width: 0;
+ border-top-color: silver;
+ }
+ .FormattedTextBox-tooltip:after {
+ content: "";
+ height: 0; width: 0;
+ position: absolute;
+ left: 50%;
+ margin-left: -5px;
+ bottom: -4.5px;
+ border: 5px solid transparent;
+ border-bottom-width: 0;
+ border-top-color: white;
+ } \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
new file mode 100644
index 000000000..2d2fff06d
--- /dev/null
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -0,0 +1,152 @@
+import { Plugin, EditorState } from "prosemirror-state";
+import './FormattedTextBoxComment.scss';
+import { ResolvedPos, Mark } from "prosemirror-model";
+import { EditorView } from "prosemirror-view";
+import { Doc } from "../../../new_fields/Doc";
+import { schema } from "../../util/RichTextSchema";
+import { DocServer } from "../../DocServer";
+import { Utils } from "../../../Utils";
+import { StrCast } from "../../../new_fields/Types";
+
+export let formattedTextBoxCommentPlugin = new Plugin({
+ view(editorView) { return new FormattedTextBoxComment(editorView); }
+});
+export function findOtherUserMark(marks: Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail);
+}
+export function findUserMark(marks: Mark[]): Mark | undefined {
+ return marks.find(m => m.attrs.userid);
+}
+export function findLinkMark(marks: Mark[]): Mark | undefined {
+ return marks.find(m => m.type === schema.marks.link);
+}
+export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+ let before = 0;
+ let nbef = rpos.nodeBefore;
+ while (nbef && finder(nbef.marks)) {
+ before += nbef.nodeSize;
+ rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize);
+ rpos && (nbef = rpos.nodeBefore);
+ }
+ return before;
+}
+export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) {
+ let after = 0;
+ let naft = rpos.nodeAfter;
+ while (naft && finder(naft.marks)) {
+ after += naft.nodeSize;
+ rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize);
+ rpos && (naft = rpos.nodeAfter);
+ }
+ return after;
+}
+
+
+export class FormattedTextBoxComment {
+ static tooltip: HTMLElement;
+ static tooltipText: HTMLElement;
+ static start: number;
+ static end: number;
+ static mark: Mark;
+ static opened: boolean;
+ static textBox: any;
+ constructor(view: any) {
+ if (!FormattedTextBoxComment.tooltip) {
+ const root = document.getElementById("root");
+ let input = document.createElement("input");
+ input.type = "checkbox";
+ FormattedTextBoxComment.tooltip = document.createElement("div");
+ FormattedTextBoxComment.tooltipText = document.createElement("div");
+ FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
+ FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
+ FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
+ FormattedTextBoxComment.tooltip.appendChild(input);
+ FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
+ let keep = e.target && (e.target as any).type === "checkbox";
+ FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
+ FormattedTextBoxComment.textBox && FormattedTextBoxComment.textBox.setAnnotation(
+ FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
+ FormattedTextBoxComment.opened, keep);
+ };
+ 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) {
+ 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;
+ // Don't do anything if the document/selection didn't change
+ if (lastState && lastState.doc.eq(state.doc) &&
+ lastState.selection.eq(state.selection)) return;
+
+ let set = "none";
+ if (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;
+ if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) {
+ FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, 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";
+ set = "";
+ }
+ }
+ 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);
+ 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)));
+ }
+ // 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 = "";
+ }
+ }
+ FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
+ }
+
+ destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; }
+}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index b1afa3f7d..00c069e1f 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -65,6 +65,7 @@
display:flex;
align-items: center;
height:100%;
+ overflow:hidden;
.imageBox-fadeBlocker {
width:100%;
height:100%;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 708e00576..95f304641 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -89,10 +89,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.DocumentDragData) {
de.data.droppedDocuments.forEach(action((drop: Doc) => {
- if (de.mods === "CtrlKey") {
- Doc.ApplyTemplateTo(drop, this.props.Document, this.props.DataDoc);
- e.stopPropagation();
- } else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
+ if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url);
e.stopPropagation();
} else if (de.mods === "MetaKey") {
@@ -219,12 +216,13 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
funcs.push({ description: "Record 1sec audio", event: this.recordAudioAnnotation, icon: "expand-arrows-alt" });
funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" });
- let modes: ContextMenuProps[] = [];
+ let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
+ let 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" })
ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" });
- ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" });
}
}
@@ -264,13 +262,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
onDotDown(index: number) {
this.Document.curPage = index;
}
-
- @computed get fieldExtensionDoc() {
- return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
- }
-
@computed private get url() {
- let data = Cast(Doc.GetProto(this.props.Document).data, ImageField);
+ let data = Cast(Doc.GetProto(this.props.Document)[this.props.fieldKey], ImageField);
return data ? data.url.href : undefined;
}
@@ -321,7 +314,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
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;
- if (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1) {
+ if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) {
setTimeout(action(() => {
layoutdoc.height = layoutdoc[WidthSym]() * aspect;
layoutdoc.nativeHeight = realsize.height;
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 0d4b377dd..ee70942de 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -2,26 +2,21 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { CompileScript, ScriptOptions, CompiledScript } from "../../util/Scripting";
-import { FieldView, FieldViewProps } from './FieldView';
-import "./KeyValueBox.scss";
-import { KeyValuePair } from "./KeyValuePair";
-import React = require("react");
-import { NumCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
-import { Doc, Field, FieldResult, DocListCastAsync } from "../../../new_fields/Doc";
-import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
-import { SetupDrag } from "../../util/DragManager";
-import { Docs } from "../../documents/Documents";
-import { RawDataOperationParameters } from "../../northstar/model/idea/idea";
-import { Templates } from "../Templates";
+import { Doc, Field, FieldResult } from "../../../new_fields/Doc";
import { List } from "../../../new_fields/List";
-import { TextField } from "../../util/ProsemirrorCopy/prompt";
import { RichTextField } from "../../../new_fields/RichTextField";
-import { ImageField } from "../../../new_fields/URLField";
-import { SelectionManager } from "../../util/SelectionManager";
import { listSpec } from "../../../new_fields/Schema";
-import { CollectionViewType } from "../collections/CollectionBaseView";
+import { ComputedField, ScriptField } from "../../../new_fields/ScriptField";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { ImageField } from "../../../new_fields/URLField";
+import { Docs } from "../../documents/Documents";
+import { SetupDrag } from "../../util/DragManager";
+import { CompiledScript, CompileScript, ScriptOptions } from "../../util/Scripting";
import { undoBatch } from "../../util/UndoManager";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./KeyValueBox.scss";
+import { KeyValuePair } from "./KeyValuePair";
+import React = require("react");
export type KVPScript = {
script: CompiledScript;
@@ -128,7 +123,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
let rows: JSX.Element[] = [];
let i = 0;
const self = this;
- for (let key of Object.keys(ids).sort()) {
+ for (let key of Object.keys(ids).slice().sort()) {
rows.push(<KeyValuePair doc={realDoc} ref={(function () {
let oldEl: KeyValuePair | undefined;
return (el: KeyValuePair) => {
@@ -203,7 +198,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
return;
}
let previousViewType = fieldTemplate.viewType;
- Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(parentStackingDoc));
+ Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(parentStackingDoc));
previousViewType && (fieldTemplate.viewType = previousViewType);
Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 534a42efc..7e0f3735d 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -55,12 +55,12 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
Document: this.props.doc,
DataDoc: this.props.doc,
ContainingCollectionView: undefined,
+ ruleProvider: undefined,
fieldKey: this.props.keyName,
fieldExt: "",
isSelected: returnFalse,
select: emptyFunction,
renderDepth: 1,
- selectOnLoad: false,
active: returnFalse,
whenActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
@@ -68,6 +68,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
PanelWidth: returnZero,
PanelHeight: returnZero,
addDocTab: returnZero,
+ pinToPres: returnZero,
ContentScaling: returnOne
};
let contents = <FieldView {...props} />;
@@ -112,7 +113,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
<div className="keyValuePair-td-value-container">
<EditableView
contents={contents}
- height={36}
+ maxHeight={36}
+ height={"auto"}
GetValue={() => {
return Field.toKeyValueString(props.Document, props.fieldKey);
}}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 6450cb826..df35b603c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -33,7 +33,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
@observable private _pdf: Opt<Pdfjs.PDFDocumentProxy>;
@computed get containingCollectionDocument() { return this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document; }
- @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+ @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+
+
@computed get fieldExtensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); }
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
@@ -48,7 +50,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
componentDidMount() {
this.props.setPdfBox && this.props.setPdfBox(this);
- const pdfUrl = Cast(this.props.Document.data, PdfField);
+ 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));
}
@@ -78,7 +80,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
@action
public GotoPage(p: number) {
- if (p > 0 && p <= NumCast(this.props.Document.numPages)) {
+ if (p > 0 && p <= NumCast(this.dataDoc.numPages)) {
this.props.Document.curPage = p;
this.props.Document.panY = (p - 1) * NumCast(this.dataDoc.nativeHeight);
}
@@ -87,7 +89,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
@action
public ForwardPage() {
let cp = this.GetPage() + 1;
- if (cp <= NumCast(this.props.Document.numPages)) {
+ if (cp <= NumCast(this.dataDoc.numPages)) {
this.props.Document.curPage = cp;
this.props.Document.panY = (cp - 1) * NumCast(this.dataDoc.nativeHeight);
}
@@ -185,11 +187,12 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
}
+
render() {
- const pdfUrl = Cast(this.props.Document.data, PdfField);
+ const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
return (!(pdfUrl instanceof PdfField) || !this._pdf ?
- <div>{`pdf, ${this.props.Document.data}, not found`}</div> :
+ <div>{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}</div> :
<div className={classname}
onScroll={this.onScroll}
style={{ marginTop: `${this.containingCollectionDocument ? NumCast(this.containingCollectionDocument.panY) : 0}px` }}
@@ -198,7 +201,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
<PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)}
Document={this.props.Document} DataDoc={this.props.DataDoc}
addDocTab={this.props.addDocTab} setPanY={this.setPanY}
- addDocument={this.props.addDocument}
+ pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} />
{this.settingsPanel()}
</div>);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
new file mode 100644
index 000000000..e376fbddb
--- /dev/null
+++ b/src/client/views/nodes/PresBox.tsx
@@ -0,0 +1,529 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
+import { DocumentManager } from "../../util/DocumentManager";
+import { undoBatch } from "../../util/UndoManager";
+import PresentationElement from "../presentationview/PresentationElement";
+import PresentationViewList from "../presentationview/PresentationList";
+import "../presentationview/PresentationView.scss";
+import { FieldView, FieldViewProps } from './FieldView';
+import { ContextMenu } from "../ContextMenu";
+
+library.add(faArrowLeft);
+library.add(faArrowRight);
+library.add(faPlay);
+library.add(faStop);
+library.add(faPlus);
+library.add(faTimes);
+library.add(faMinus);
+library.add(faEdit);
+
+
+export interface PresViewProps {
+ Documents: List<Doc>;
+}
+
+const expandedWidth = 450;
+
+@observer
+export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
+
+
+ public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); }
+
+ public static Instance: PresBox;
+
+ //Keeping track of the doc for the current presentation -- bcz: keeping a list of current presentations shouldn't be needed. Let users create them, store them, as they see fit.
+ @computed get curPresentation() { return this.props.Document; }
+
+ //mapping from docs to their rendered component
+ @observable presElementsMappings: Map<Doc, PresentationElement> = new Map();
+ //variable that holds all the docs in the presentation
+ @observable childrenDocs: Doc[] = [];
+ //variable to hold if presentation is started
+ @observable presStatus: boolean = false;
+ //Mapping of guids to presentations.
+ @observable presentationsMapping: Map<String, Doc> = new Map();
+ //Mapping of presentations to guid, so that select option values can be given.
+ @observable presentationsKeyMapping: Map<Doc, String> = new Map();
+ //Variable to keep track of guid of the current presentation
+ @observable currentSelectedPresValue: string | undefined;
+ //A flag to keep track if title input is open, which is used in rendering.
+ @observable PresTitleInputOpen: boolean = false;
+ //Variable that holds reference to title input, so that new presentations get titles assigned.
+ @observable titleInputElement: HTMLInputElement | undefined;
+ @observable PresTitleChangeOpen: boolean = false;
+
+ @observable opacity = 1;
+ @observable persistOpacity = true;
+ @observable labelOpacity = 0;
+ @observable presMode = false;
+
+ @observable public static CurrentPresentation: PresBox;
+
+ //initilize class variables
+ constructor(props: FieldViewProps) {
+ super(props);
+ runInAction(() => PresBox.CurrentPresentation = this);
+ }
+
+ @action
+ toggle = (forcedValue: boolean | undefined) => {
+ if (forcedValue !== undefined) {
+ this.curPresentation.width = forcedValue ? expandedWidth : 0;
+ } else {
+ this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
+ }
+ }
+
+ //Second lifecycle function that gets called when component mounts. It makes sure toS
+ //get the back-up information from previous session for the current presentation.
+ async componentDidMount() {
+ this.setPresentationBackUps();
+ }
+
+
+ /**
+ * The function that retrieves the backUps for the current Presentation if present,
+ * otherwise initializes.
+ */
+ setPresentationBackUps = async () => {
+ //storing the presentation status,ie. whether it was stopped or playing
+ let presStatusBackUp = BoolCast(this.curPresentation.presStatus);
+ runInAction(() => this.presStatus = presStatusBackUp);
+ }
+
+ //observable means render is re-called every time variable is changed
+ @observable
+ collapsed: boolean = false;
+ next = async () => {
+ const current = NumCast(this.curPresentation.selectedDoc);
+ //asking to get document at current index
+ let docAtCurrentNext = await this.getDocAtIndex(current + 1);
+ if (docAtCurrentNext === undefined) {
+ return;
+ }
+ let nextSelected = current + 1;
+
+ let presDocs = DocListCast(this.curPresentation.data);
+ for (; nextSelected < presDocs.length - 1; nextSelected++) {
+ if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) {
+ break;
+ }
+ }
+
+ this.gotoDocument(nextSelected, current);
+
+ }
+ back = async () => {
+ const current = NumCast(this.curPresentation.selectedDoc);
+ //requesting for the doc at current index
+ let docAtCurrent = await this.getDocAtIndex(current);
+ if (docAtCurrent === undefined) {
+ return;
+ }
+
+ //asking for its presentation id.
+ let curPresId = StrCast(docAtCurrent.presentId);
+ let prevSelected = current;
+ let zoomOut: boolean = false;
+
+ //checking if this presentation id is mapped to a group, if so chosing the first element in group
+ let presDocs = DocListCast(this.curPresentation.data);
+ let currentsArray: Doc[] = [];
+ for (; prevSelected > 0 && presDocs[prevSelected].groupButton; prevSelected--) {
+ currentsArray.push(presDocs[prevSelected]);
+ }
+ prevSelected = Math.max(0, prevSelected - 1);
+
+ //checking if any of the group members had used zooming in
+ currentsArray.forEach((doc: Doc) => {
+ //let presElem: PresentationElement | undefined = this.presElementsMappings.get(doc);
+ if (this.presElementsMappings.get(doc)!.props.document.showButton) {
+ zoomOut = true;
+ return;
+ }
+ });
+
+
+ // if a group set that flag to zero or a single element
+ //If so making sure to zoom out, which goes back to state before zooming action
+ if (current > 0) {
+ if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.showButton) {
+ let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null);
+ let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]);
+ if (prevScale !== undefined) {
+ if (prevScale !== curScale) {
+ DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
+ }
+ }
+ }
+ }
+ this.gotoDocument(prevSelected, current);
+
+ }
+
+ /**
+ * This is the method that checks for the actions that need to be performed
+ * after the document has been presented, which involves 3 button options:
+ * Hide Until Presented, Hide After Presented, Fade After Presented
+ */
+ showAfterPresented = (index: number) => {
+ this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => {
+ //the order of cases is aligned based on priority
+ if (presElem.props.document.hideTillShownButton) {
+ if (this.childrenDocs.indexOf(key) <= index) {
+ key.opacity = 1;
+ }
+ }
+ if (presElem.props.document.hideAfterButton) {
+ if (this.childrenDocs.indexOf(key) < index) {
+ key.opacity = 0;
+ }
+ }
+ if (presElem.props.document.fadeButton) {
+ if (this.childrenDocs.indexOf(key) < index) {
+ key.opacity = 0.5;
+ }
+ }
+ });
+ }
+
+ /**
+ * This is the method that checks for the actions that need to be performed
+ * before the document has been presented, which involves 3 button options:
+ * Hide Until Presented, Hide After Presented, Fade After Presented
+ */
+ hideIfNotPresented = (index: number) => {
+ this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => {
+ //the order of cases is aligned based on priority
+
+ if (presElem.props.document.hideAfterButton) {
+ if (this.childrenDocs.indexOf(key) >= index) {
+ key.opacity = 1;
+ }
+ }
+ if (presElem.props.document.fadeButton) {
+ if (this.childrenDocs.indexOf(key) >= index) {
+ key.opacity = 1;
+ }
+ }
+ if (presElem.props.document.hideTillShownButton) {
+ if (this.childrenDocs.indexOf(key) > index) {
+ key.opacity = 0;
+ }
+ }
+ });
+ }
+
+ /**
+ * This method makes sure that cursor navigates to the element that
+ * has the option open and last in the group. If not in the group, and it has
+ * te option open, navigates to that element.
+ */
+ navigateToElement = async (curDoc: Doc, fromDoc: number) => {
+ let docToJump: Doc = curDoc;
+ let willZoom: boolean = false;
+
+
+ let presDocs = DocListCast(this.curPresentation.data);
+ let nextSelected = presDocs.indexOf(curDoc);
+ let currentDocGroups: Doc[] = [];
+ for (; nextSelected < presDocs.length - 1; nextSelected++) {
+ if (!this.presElementsMappings.get(presDocs[nextSelected + 1])!.props.document.groupButton) {
+ break;
+ }
+ currentDocGroups.push(presDocs[nextSelected]);
+ }
+
+ currentDocGroups.forEach((doc: Doc, index: number) => {
+ if (this.presElementsMappings.get(doc)!.navButton) {
+ docToJump = doc;
+ willZoom = false;
+ }
+ if (this.presElementsMappings.get(doc)!.showButton) {
+ docToJump = doc;
+ willZoom = true;
+ }
+ });
+
+ //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
+ if (this.presElementsMappings.get(curDoc)!.navButton) {
+ DocumentManager.Instance.jumpToDocument(curDoc, false);
+ } else if (this.presElementsMappings.get(curDoc)!.showButton) {
+ let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]);
+ //awaiting jump so that new scale can be found, since jumping is async
+ await DocumentManager.Instance.jumpToDocument(curDoc, true);
+ let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
+ curDoc.viewScale = newScale;
+
+ //saving the scale user was on before zooming in
+ if (curScale !== 1) {
+ this.childrenDocs[fromDoc].viewScale = curScale;
+ }
+
+ }
+ return;
+ }
+ let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]);
+
+ //awaiting jump so that new scale can be found, since jumping is async
+ await DocumentManager.Instance.jumpToDocument(docToJump, willZoom);
+ let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
+ curDoc.viewScale = newScale;
+ //saving the scale that user was on
+ if (curScale !== 1) {
+ this.childrenDocs[fromDoc].viewScale = curScale;
+ }
+
+ }
+
+ /**
+ * Async function that supposedly return the doc that is located at given index.
+ */
+ getDocAtIndex = async (index: number) => {
+ const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
+ if (!list) {
+ return undefined;
+ }
+ if (index < 0 || index >= list.length) {
+ return undefined;
+ }
+
+ this.curPresentation.selectedDoc = index;
+ //awaiting async call to finish to get Doc instance
+ const doc = await list[index];
+ return doc;
+ }
+
+ /**
+ * The function that removes a doc from a presentation. It also makes sure to
+ * do necessary updates to backUps and mappings stored locally.
+ */
+ @action
+ public RemoveDoc = async (index: number) => {
+ const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
+ if (value) {
+ let removedDoc = await value.splice(index, 1)[0];
+
+ //removing the Presentation Element stored for it
+ this.presElementsMappings.delete(removedDoc);
+
+ }
+ }
+
+ public removeDocByRef = (doc: Doc) => {
+ let indexOfDoc = this.childrenDocs.indexOf(doc);
+ const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
+ if (value) {
+ value.splice(indexOfDoc, 1)[0];
+ }
+ //this.RemoveDoc(indexOfDoc, true);
+ if (indexOfDoc !== - 1) {
+ return true;
+ }
+ return false;
+ }
+
+ //The function that is called when a document is clicked or reached through next or back.
+ //it'll also execute the necessary actions if presentation is playing.
+ @action
+ public gotoDocument = async (index: number, fromDoc: number) => {
+ Doc.UnBrushAllDocs();
+ const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
+ if (!list) {
+ return;
+ }
+ if (index < 0 || index >= list.length) {
+ return;
+ }
+ this.curPresentation.selectedDoc = index;
+
+ if (!this.presStatus) {
+ this.presStatus = true;
+ this.startPresentation(index);
+ }
+
+ const doc = await list[index];
+ if (this.presStatus) {
+ this.navigateToElement(doc, fromDoc);
+ this.hideIfNotPresented(index);
+ this.showAfterPresented(index);
+ }
+ }
+ //Function that sets the store of the children docs.
+ @action
+ setChildrenDocs = (docList: Doc[]) => {
+ this.childrenDocs = docList;
+ }
+
+ //The function that is called to render the play or pause button depending on
+ //if presentation is running or not.
+ renderPlayPauseButton = () => {
+ if (this.presStatus) {
+ return <button title="Reset Presentation" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="stop" /></button>;
+ } else {
+ return <button title="Start Presentation From Start" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="play" /></button>;
+ }
+ }
+
+ //The function that starts or resets presentaton functionally, depending on status flag.
+ @action
+ startOrResetPres = () => {
+ if (this.presStatus) {
+ this.resetPresentation();
+ } else {
+ this.presStatus = true;
+ this.startPresentation(0);
+ const current = NumCast(this.curPresentation.selectedDoc);
+ this.gotoDocument(0, current);
+ }
+ this.curPresentation.presStatus = this.presStatus;
+ }
+
+ //The function that resets the presentation by removing every action done by it. It also
+ //stops the presentaton.
+ @action
+ resetPresentation = () => {
+ this.childrenDocs.forEach((doc: Doc) => {
+ doc.opacity = 1;
+ doc.viewScale = 1;
+ });
+ this.curPresentation.selectedDoc = 0;
+ this.presStatus = false;
+ this.curPresentation.presStatus = this.presStatus;
+ if (this.childrenDocs.length === 0) {
+ return;
+ }
+ DocumentManager.Instance.zoomIntoScale(this.childrenDocs[0], 1);
+ }
+
+
+ //The function that starts the presentation, also checking if actions should be applied
+ //directly at start.
+ startPresentation = (startIndex: number) => {
+ this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => {
+ if (component.props.document.hideTillShownButton) {
+ if (this.childrenDocs.indexOf(doc) > startIndex) {
+ doc.opacity = 0;
+ }
+
+ }
+ if (component.props.document.hideAfterButton) {
+ if (this.childrenDocs.indexOf(doc) < startIndex) {
+ doc.opacity = 0;
+ }
+ }
+ if (component.props.document.fadeButton) {
+ if (this.childrenDocs.indexOf(doc) < startIndex) {
+ doc.opacity = 0.5;
+ }
+ }
+
+ });
+
+ }
+
+
+ /**
+ * The function that is called to render either select for presentations, or title inputting.
+ */
+ renderSelectOrPresSelection = () => {
+ if (this.PresTitleInputOpen || this.PresTitleChangeOpen) {
+ return <input ref={(e) => this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />;
+ } else {
+ return (null);
+ }
+ }
+
+ /**
+ * The function that is called on enter press of title input. It gives the
+ * new presentation the title user entered. If nothing is entered, gives a default title.
+ */
+ @action
+ submitPresentationTitle = (e: React.KeyboardEvent) => {
+ if (e.keyCode === 13) {
+ let presTitle = this.titleInputElement!.value;
+ this.titleInputElement!.value = "";
+ if (this.PresTitleChangeOpen) {
+ this.PresTitleChangeOpen = false;
+ this.changePresentationTitle(presTitle);
+ }
+ }
+ }
+ /**
+ * The function that is called to change title of presentation to what user entered.
+ */
+ @undoBatch
+ changePresentationTitle = (newTitle: string) => {
+ if (newTitle === "") {
+ return;
+ }
+ this.curPresentation.title = newTitle;
+ }
+
+ addPressElem = (keyDoc: Doc, elem: PresentationElement) => {
+ this.presElementsMappings.set(keyDoc, elem);
+ }
+
+ minimize = undoBatch(action(() => {
+ this.presMode = true;
+ this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close");
+ }));
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" });
+ }
+
+ render() {
+
+ let width = "100%"; //NumCast(this.curPresentation.width)
+ return (
+ <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onContextMenu={this.specificContextMenu}
+ onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))}
+ style={{ width: width, opacity: this.opacity, }}>
+ <div className="presentation-buttons">
+ <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
+ {this.renderPlayPauseButton()}
+ <button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
+ <button title="Minimize" className="presentation-button" onClick={this.minimize}><FontAwesomeIcon icon={"eye"} /></button>
+ </div>
+ <input
+ type="checkbox"
+ onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
+ this.persistOpacity = e.target.checked;
+ this.opacity = this.persistOpacity ? 1 : 0.4;
+ })}
+ checked={this.persistOpacity}
+ style={{ position: "absolute", bottom: 5, left: 5 }}
+ onPointerEnter={action(() => this.labelOpacity = 1)}
+ onPointerLeave={action(() => this.labelOpacity = 0)}
+ />
+ <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p>
+ <PresentationViewList
+ mainDocument={this.curPresentation}
+ deleteDocument={this.RemoveDoc}
+ gotoDocument={this.gotoDocument}
+ PresElementsMappings={this.presElementsMappings}
+ setChildrenDocs={this.setChildrenDocs}
+ presStatus={this.presStatus}
+ removeDocByRef={this.removeDocByRef}
+ clearElemMap={() => this.presElementsMappings.clear()}
+ />
+ </div>
+ );
+ }
+
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a33155fff..1ac2fae39 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -4,7 +4,7 @@ import { observer } from "mobx-react";
import * as rp from 'request-promise';
import { InkTool } from "../../../new_fields/InkField";
import { makeInterface } from "../../../new_fields/Schema";
-import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
import { RouteStore } from "../../../server/RouteStore";
import { Utils } from "../../../Utils";
@@ -34,7 +34,7 @@ library.add(faVideo);
export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
private _reactionDisposer?: IReactionDisposer;
private _youtubeReactionDisposer?: IReactionDisposer;
- private _youtubePlayer: any = undefined;
+ private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null;
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
@@ -78,7 +78,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
@action public Pause = (update: boolean = true) => {
this.Playing = false;
update && this.player && this.player.pause();
- update && this._youtubePlayer && this._youtubePlayer.pauseVideo();
+ update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo();
this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
this._playTimer = undefined;
this.updateTimecode();
@@ -206,7 +206,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
}
specificContextMenu = (e: React.MouseEvent): void => {
- let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ let field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
if (field) {
let url = field.url.href;
let subitems: ContextMenuProps[] = [];
@@ -218,7 +218,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
@computed get content() {
- let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ 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;
return !field ? <div>Loading</div> :
@@ -230,7 +230,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
@computed get youtubeVideoId() {
- let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ let 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("/")) : "";
}
@@ -246,7 +246,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let onYoutubePlayerStateChange = (event: any) => runInAction(() => {
if (started && event.data === YT.PlayerState.PLAYING) {
started = false;
- this._youtubePlayer.unMute();
+ this._youtubePlayer && this._youtubePlayer.unMute();
this.Pause();
return;
}
@@ -271,6 +271,8 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
+ @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+
@computed get youtubeContent() {
this._youtubeIframeId = VideoBox._youtubeIframeCounter++;
this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true;
@@ -283,6 +285,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
render() {
+ Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
return <div style={{ pointerEvents: "all", width: "100%", height: "100%" }} onContextMenu={this.specificContextMenu}>
{this.youtubeVideoId ? this.youtubeContent : this.content}
</div>;
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 07774263c..fbe9bf063 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,3 +1,5 @@
+@import "../globalCssVariables.scss";
+
.webBox-cont,
.webBox-cont-interactive {
padding: 0vw;
@@ -28,6 +30,7 @@
width: 100%;
height: 100%;
position: absolute;
+ pointer-events: all;
}
.webBox-button {
@@ -61,6 +64,14 @@
width: 40px;
transform-origin: top left;
}
+
+ .switchToText {
+ color: $main-accent;
+ }
+
+ .switchToText:hover {
+ color: $dark-color;
+ }
}
button:hover {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 9b66b2431..29eef27a0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,18 +1,29 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { FieldResult } from "../../../new_fields/Doc";
+import { FieldResult, Doc, Field } from "../../../new_fields/Doc";
import { HtmlField } from "../../../new_fields/HtmlField";
-import { InkTool } from "../../../new_fields/InkField";
-import { Cast, NumCast } from "../../../new_fields/Types";
import { WebField } from "../../../new_fields/URLField";
-import { Utils } from "../../../Utils";
import { DocumentDecorations } from "../DocumentDecorations";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from './FieldView';
-import { KeyValueBox } from "./KeyValueBox";
import "./WebBox.scss";
import React = require("react");
+import { InkTool } from "../../../new_fields/InkField";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { Utils } from "../../../Utils";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { observable, action, computed } from "mobx";
+import { listSpec } from "../../../new_fields/Schema";
+import { RefField } from "../../../new_fields/RefField";
+import { ObjectField } from "../../../new_fields/ObjectField";
+import { updateSourceFile } from "typescript";
+import { KeyValueBox } from "./KeyValueBox";
+import { setReactionScheduler } from "mobx/lib/internal";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Docs } from "../../documents/Documents";
+
+library.add(faStickyNote);
@observer
export class WebBox extends React.Component<FieldViewProps> {
@@ -64,6 +75,29 @@ export class WebBox extends React.Component<FieldViewProps> {
}
}
+
+ switchToText = () => {
+ let url: string = "";
+ let field = Cast(this.props.Document[this.props.fieldKey], WebField);
+ if (field) url = field.url.href;
+
+ let newBox = Docs.Create.TextDocument({
+ x: NumCast(this.props.Document.x),
+ y: NumCast(this.props.Document.y),
+ title: url,
+ width: 200,
+ height: 70,
+ documentText: "@@@" + url
+ });
+
+ SelectionManager.SelectedDocuments().map(dv => {
+ dv.props.addDocument && dv.props.addDocument(newBox, false);
+ dv.props.removeDocument && dv.props.removeDocument(dv.props.Document);
+ });
+
+ Doc.BrushDoc(newBox);
+ }
+
urlEditor() {
return (
<div className="webView-urlEditor" style={{ top: this.collapsed ? -70 : 0 }}>
@@ -86,9 +120,19 @@ export class WebBox extends React.Component<FieldViewProps> {
onChange={this.onURLChange}
onKeyDown={this.onValueKeyDown}
/>
- <button className="submitUrl" onClick={this.submitURL}>
- SUBMIT URL
- </button>
+ <div style={{
+ display: "flex",
+ flexDirection: "row",
+ justifyContent: "space-between",
+ minWidth: "100px",
+ }}>
+ <button className="submitUrl" onClick={this.submitURL}>
+ SUBMIT
+ </button>
+ <div className="switchToText" title="Convert web to text doc" onClick={this.switchToText} style={{ display: "flex", alignItems: "center", justifyContent: "center" }} >
+ <FontAwesomeIcon icon={faStickyNote} size={"lg"} />
+ </div>
+ </div>
</div>
</div>
</div>
diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx
index 7ba7b6d14..eeb2531a2 100644
--- a/src/client/views/pdf/Annotation.tsx
+++ b/src/client/views/pdf/Annotation.tsx
@@ -1,23 +1,21 @@
import React = require("react");
-import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
import { DocumentManager } from "../../util/DocumentManager";
-import { PresentationView } from "../presentationview/PresentationView";
import PDFMenu from "./PDFMenu";
import "./Annotation.scss";
import { scale } from "./PDFViewer";
+import { PresBox } from "../nodes/PresBox";
interface IAnnotationProps {
anno: Doc;
- index: number;
- ParentIndex: () => number;
fieldExtensionDoc: Doc;
- scrollTo?: (n: number) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
}
export default class Annotation extends React.Component<IAnnotationProps> {
@@ -32,20 +30,20 @@ interface IRegionAnnotationProps {
y: number;
width: number;
height: number;
- index: number;
- ParentIndex: () => number;
fieldExtensionDoc: Doc;
- scrollTo?: (n: number) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
document: Doc;
}
@observer
class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
private _reactionDisposer?: IReactionDisposer;
- private _scrollDisposer?: IReactionDisposer;
+ private _brushDisposer?: IReactionDisposer;
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ @observable private _brushed: boolean = false;
+
componentDidMount() {
this._reactionDisposer = reaction(
() => this.props.document.delete,
@@ -53,15 +51,18 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
{ fireImmediately: true }
);
- this._scrollDisposer = reaction(
- () => this.props.ParentIndex(),
- (ind) => ind === this.props.index && this.props.scrollTo && this.props.scrollTo(this.props.y * scale)
- );
+ this._brushDisposer = reaction(
+ () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.IsBrushed(FieldValue(Cast(this.props.document.group, Doc))!),
+ (brushed) => {
+ if (brushed !== undefined) {
+ runInAction(() => this._brushed = brushed);
+ }
+ }
+ )
}
componentWillUnmount() {
this._reactionDisposer && this._reactionDisposer();
- this._scrollDisposer && this._scrollDisposer();
}
deleteAnnotation = () => {
@@ -81,7 +82,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
pinToPres = () => {
let group = FieldValue(Cast(this.props.document.group, Doc));
- group && PresentationView.Instance.PinDoc(group);
+ group && this.props.pinToPres(group);
}
@action
@@ -124,7 +125,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> {
left: this.props.x,
width: this.props.width,
height: this.props.height,
- backgroundColor: this.props.ParentIndex() === this.props.index ? "green" : StrCast(this.props.document.color)
+ backgroundColor: this._brushed ? "green" : StrCast(this.props.document.color)
}} />);
}
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 08674720d..7bc1d3507 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -36,6 +36,7 @@ interface IViewerProps {
active: () => boolean;
setPanY?: (n: number) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
+ pinToPres: (document: Doc) => void;
addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
}
@@ -75,7 +76,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
return Math.min(this.props.pdf.numPages - 1, this.getPageFromScroll(this.panY + (this._pageSizes[0] ? this._pageSizes[0].height : 0)) + this._pageBuffer);
}
- @computed get filteredAnnotations() {
+ @computed get allAnnotations() {
+ let annotations = DocListCast(this.props.fieldExtensionDoc.annotations);
+ return annotations.filter(anno => {
+ let run = this._script.run({ this: anno });
+ return run.success ? run.result : true;
+ })
+ }
+
+ @computed get nonDocAnnotations() {
return this._annotations.filter(anno => {
let run = this._script.run({ this: anno });
return run.success ? run.result : true;
@@ -100,12 +109,15 @@ export class PDFViewer extends React.Component<IViewerProps> {
this._filterReactionDisposer = reaction(
() => ({ scriptField: Cast(this.props.Document.filterScript, ScriptField), annos: this._annotations.slice() }),
action(({ scriptField, annos }: { scriptField: FieldResult<ScriptField>, annos: Doc[] }) => {
+ let oldScript = this._script.originalScript;
this._script = scriptField && scriptField.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript;
+ if (this._script.originalScript !== oldScript) {
+ this.Index = -1;
+ }
annos.forEach(d => {
let run = this._script.run(d);
d.opacity = !run.success || run.result ? 1 : 0;
});
- this.Index = -1;
}),
{ fireImmediately: true }
);
@@ -151,18 +163,25 @@ export class PDFViewer extends React.Component<IViewerProps> {
if (this._pageSizes.length === 0) {
this._isPage = Array<string>(this.props.pdf.numPages);
this._pageSizes = Array<{ width: number, height: number }>(this.props.pdf.numPages);
+ this._visibleElements = Array<JSX.Element>(this.props.pdf.numPages);
await Promise.all(this._pageSizes.map<Pdfjs.PDFPromise<any>>((val, i) =>
this.props.pdf.getPage(i + 1).then(action((page: Pdfjs.PDFPageProxy) => {
this._pageSizes.splice(i, 1, {
width: (page.view[page.rotate === 0 || page.rotate === 180 ? 2 : 3] - page.view[page.rotate === 0 || page.rotate === 180 ? 0 : 1]) * scale,
height: (page.view[page.rotate === 0 || page.rotate === 180 ? 3 : 2] - page.view[page.rotate === 0 || page.rotate === 180 ? 1 : 0]) * scale
});
+ this._visibleElements.splice(i, 1,
+ <div key={`${this.props.url}-placeholder-${i + 1}`} className="pdfviewer-placeholder"
+ style={{ width: this._pageSizes[i].width, height: this._pageSizes[i].height }}>
+ "PAGE IS LOADING... "
+ </div>);
this.getPlaceholderPage(i);
}))));
this.props.loaded(Math.max(...this._pageSizes.map(i => i.width)), this._pageSizes[0].height, this.props.pdf.numPages);
let startY = NumCast(this.props.Document.startY, NumCast(this.props.Document.panY));
this.props.setPanY && this.props.setPanY(startY);
+ this.props.scrollTo(startY);
}
}
@@ -288,12 +307,20 @@ export class PDFViewer extends React.Component<IViewerProps> {
prevAnnotation = (e: React.MouseEvent) => {
e.stopPropagation();
this.Index = Math.max(this.Index - 1, 0);
+ let scrollToAnnotation = this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index];
+ this.allAnnotations.forEach(d => Doc.UnBrushDoc(d));
+ Doc.BrushDoc(scrollToAnnotation);
+ this.props.scrollTo(NumCast(scrollToAnnotation.y));
}
@action
nextAnnotation = (e: React.MouseEvent) => {
e.stopPropagation();
- this.Index = Math.min(this.Index + 1, this.filteredAnnotations.length - 1);
+ this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1);
+ let scrollToAnnotation = this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index];
+ this.allAnnotations.forEach(d => Doc.UnBrushDoc(d));
+ Doc.BrushDoc(scrollToAnnotation);
+ this.props.scrollTo(NumCast(scrollToAnnotation.y));
}
sendAnnotations = (page: number) => {
@@ -406,8 +433,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
</div>
<div className="pdfViewer-text" ref={this._viewer} />
<div className="pdfViewer-annotationLayer" style={{ height: NumCast(this.props.Document.nativeHeight) }} ref={this._annotationLayer}>
- {this.filteredAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
- <Annotation {...this.props} ParentIndex={this.getIndex} anno={anno} index={index} key={`${anno[Id]}-annotation`} />)}
+ {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
+ <Annotation {...this.props} anno={anno} key={`${anno[Id]}-annotation`} />)}
</div>
<div className="pdfViewer-overlayCont" onPointerDown={(e) => e.stopPropagation()}
style={{ bottom: -this.props.panY, left: `${this._searching ? 0 : 100}%` }}>
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 7ca9d2d7d..856e883e7 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -19,8 +19,8 @@ interface IPageProps {
numPages: number;
page: number;
pageLoaded: (page: Pdfjs.PDFPageViewport) => void;
- fieldExtensionDoc: Doc,
- Document: Doc,
+ fieldExtensionDoc: Doc;
+ Document: Doc;
renderAnnotations: (annotations: Doc[], removeOld: boolean) => void;
sendAnnotations: (annotations: HTMLDivElement[], page: number) => void;
createAnnotation: (div: HTMLDivElement, page: number) => void;
@@ -70,7 +70,8 @@ export default class Page extends React.Component<IPageProps> {
this.props.pageLoaded(viewport);
let ctx = this._canvas.current.getContext("2d");
if (ctx) {
- page.render({ canvasContext: ctx, viewport: viewport }); // renders the page onto the canvas context
+ //@ts-ignore
+ page.render({ canvasContext: ctx, viewport: viewport, enableWebGL: true }); // renders the page onto the canvas context
page.getTextContent().then(res => // renders text onto the text container
//@ts-ignore
Pdfjs.renderTextLayer({
@@ -112,7 +113,7 @@ export default class Page extends React.Component<IPageProps> {
if (!BoolCast(annotationDoc.linkedToDoc)) {
let annotations = await DocListCastAsync(annotationDoc.annotations);
annotations && annotations.forEach(anno => anno.target = targetDoc);
- DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`)
+ DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`);
}
}
},
@@ -151,6 +152,9 @@ export default class Page extends React.Component<IPageProps> {
PDFMenu.Instance.fadeOut(true);
if (e.target && (e.target as any).parentElement === this._textLayer.current) {
e.stopPropagation();
+ if (!e.ctrlKey) {
+ this.props.sendAnnotations([], -1);
+ }
}
else {
// set marquee x and y positions to the spatially transformed position
@@ -161,14 +165,12 @@ export default class Page extends React.Component<IPageProps> {
}
this._marqueeing = true;
this._marquee.current && (this._marquee.current.style.opacity = "0.2");
+ this.props.sendAnnotations([], -1);
}
document.removeEventListener("pointermove", this.onSelectStart);
document.addEventListener("pointermove", this.onSelectStart);
document.removeEventListener("pointerup", this.onSelectEnd);
document.addEventListener("pointerup", this.onSelectEnd);
- if (!e.ctrlKey) {
- this.props.sendAnnotations([], -1);
- }
}
}
@@ -257,7 +259,7 @@ export default class Page extends React.Component<IPageProps> {
}
}
}
- let text = selRange.extractContents().textContent;
+ let text = selRange.cloneContents().textContent;
text && this.props.setSelectionText(text);
// clear selection
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index d98b66324..7be44faf6 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -1,21 +1,19 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons';
-import { faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch, faArrowRight } from '@fortawesome/free-solid-svg-icons';
+import { faArrowRight, faArrowUp, faFile as fileSolid, faFileDownload, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import { listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils, returnFalse, emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
+import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, returnOne } from "../../../Utils";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
-import { ContextMenu } from "../ContextMenu";
import { Transform } from "../../util/Transform";
+import { ContextMenu } from "../ContextMenu";
import { DocumentView } from "../nodes/DocumentView";
-import { DocumentType } from "../../documents/Documents";
import React = require("react");
@@ -33,26 +31,9 @@ interface PresentationElementProps {
deleteDocument(index: number): void;
gotoDocument(index: number, fromDoc: number): Promise<void>;
allListElements: Doc[];
- groupMappings: Map<String, Doc[]>;
presStatus: boolean;
- presButtonBackUp: Doc;
- presGroupBackUp: Doc;
removeDocByRef(doc: Doc): boolean;
PresElementsMappings: Map<Doc, PresentationElement>;
-
-
-}
-
-//enum for the all kinds of buttons a doc in presentation can have
-export enum buttonIndex {
- Show = 0,
- Navigate = 1,
- HideTillPressed = 2,
- FadeAfter = 3,
- HideAfter = 4,
- Group = 5,
- OpenRight = 6
-
}
/**
@@ -62,37 +43,33 @@ export enum buttonIndex {
@observer
export default class PresentationElement extends React.Component<PresentationElementProps> {
- @observable private selectedButtons: boolean[];
private header?: HTMLDivElement | undefined;
private listdropDisposer?: DragManager.DragDropDisposer;
- private presElRef: React.RefObject<HTMLDivElement>;
- private backUpDoc: Doc | undefined;
-
-
- constructor(props: PresentationElementProps) {
- super(props);
- this.selectedButtons = new Array(7);
-
- this.presElRef = React.createRef();
- }
-
+ private presElRef: React.RefObject<HTMLDivElement> = React.createRef();
componentWillUnmount() {
this.listdropDisposer && this.listdropDisposer();
}
+ @computed get currentIndex() { return NumCast(this.props.mainDocument.selectedDoc); }
- /**
- * Getter to get the status of the buttons.
- */
- @computed
- get selected() {
- return this.selectedButtons;
- }
+ @computed get showButton() { return BoolCast(this.props.document.showButton); }
+ @computed get navButton() { return BoolCast(this.props.document.navButton); }
+ @computed get hideTillShownButton() { return BoolCast(this.props.document.hideTillShownButton); }
+ @computed get fadeButton() { return BoolCast(this.props.document.fadeButton); }
+ @computed get hideAfterButton() { return BoolCast(this.props.document.hideAfterButton); }
+ @computed get groupButton() { return BoolCast(this.props.document.groupButton); }
+ @computed get openRightButton() { return BoolCast(this.props.document.openRightButton); }
+ set showButton(val: boolean) { this.props.document.showButton = val; }
+ set navButton(val: boolean) { this.props.document.navButton = val; }
+ set hideTillShownButton(val: boolean) { this.props.document.hideTillShownButton = val; }
+ set fadeButton(val: boolean) { this.props.document.fadeButton = val; }
+ set hideAfterButton(val: boolean) { this.props.document.hideAfterButton = val; }
+ set groupButton(val: boolean) { this.props.document.groupButton = val; }
+ set openRightButton(val: boolean) { this.props.document.openRightButton = val; }
//Lifecycle function that makes sure that button BackUp is received when mounted.
async componentDidMount() {
- this.receiveButtonBackUp();
if (this.presElRef.current) {
this.header = this.presElRef.current;
this.createListDropTarget(this.presElRef.current);
@@ -107,156 +84,9 @@ export default class PresentationElement extends React.Component<PresentationEle
}
}
- /**
- * Function that will be called to receive stored backUp for buttons
- */
- receiveButtonBackUp = async () => {
-
- //get the list that stores docs that keep track of buttons
- let castedList = Cast(this.props.presButtonBackUp.selectedButtonDocs, listSpec(Doc));
- if (!castedList) {
- this.props.presButtonBackUp.selectedButtonDocs = castedList = new List<Doc>();
- }
-
- let foundDoc: boolean = false;
-
- //if this is the first time this doc mounts, push a doc for it to store
-
- for (let doc of castedList) {
- let curDoc = await doc;
- let curDocId = StrCast(curDoc.docId);
- if (curDocId === this.props.document[Id]) {
- let selectedButtonOfDoc = Cast(curDoc.selectedButtons, listSpec("boolean"), null);
- if (selectedButtonOfDoc !== undefined) {
- runInAction(() => this.selectedButtons = selectedButtonOfDoc);
- foundDoc = true;
- this.backUpDoc = curDoc;
- break;
- }
- }
- }
-
- if (!foundDoc) {
- let newDoc = new Doc();
- let defaultBooleanArray: boolean[] = new Array(7);
- newDoc.selectedButtons = new List(defaultBooleanArray);
- newDoc.docId = this.props.document[Id];
- castedList.push(newDoc);
- this.backUpDoc = newDoc;
- }
-
- }
-
- /**
- * The function that is called to group docs together. It tries to group a doc
- * that turned grouping option with the above document. If that doc is grouped with
- * other documents. Those other documents will be grouped with doc's above document as well.
- */
- @action
- onGroupClick = (document: Doc, index: number, buttonStatus: boolean) => {
- let p = this.props;
- if (index >= 1) {
- //checking if options was turned true
- if (buttonStatus) {
- //getting the id of the above-doc and the doc
- let aboveGuid = StrCast(p.allListElements[index - 1].presentId, null);
- let docGuid = StrCast(document.presentId, null);
- //the case where above-doc is already in group
- if (p.groupMappings.has(aboveGuid)) {
- let aboveArray = p.groupMappings.get(aboveGuid)!;
- //case where doc is already in group
- if (p.groupMappings.has(docGuid)) {
- let docsArray = p.groupMappings.get(docGuid)!;
- docsArray.forEach((doc: Doc) => {
- if (!aboveArray.includes(doc)) {
- aboveArray.push(doc);
- }
- doc.presentId = aboveGuid;
- });
- p.groupMappings.delete(docGuid);
- //the case where doc was not in group
- } else {
- if (!aboveArray.includes(document)) {
- aboveArray.push(document);
-
- }
-
- }
- //the case where above-doc was not in group
- } else {
- let newAboveArray: Doc[] = [];
- newAboveArray.push(p.allListElements[index - 1]);
-
- //the case where doc is in group
- if (p.groupMappings.has(docGuid)) {
- let docsArray = p.groupMappings.get(docGuid)!;
- docsArray.forEach((doc: Doc) => {
- newAboveArray.push(doc);
- doc.presentId = aboveGuid;
- });
- p.groupMappings.delete(docGuid);
-
- //the case where doc is not in a group
- } else {
- newAboveArray.push(document);
-
- }
- p.groupMappings.set(aboveGuid, newAboveArray);
-
- }
- document.presentId = aboveGuid;
-
- //when grouping is turned off
- } else {
- let curArray = p.groupMappings.get(StrCast(document.presentId, Utils.GenerateGuid()))!;
- let targetIndex = curArray.indexOf(document);
- let firstPart = curArray.slice(0, targetIndex);
- let firstPartNewGuid = Utils.GenerateGuid();
- firstPart.forEach((doc: Doc) => doc.presentId = firstPartNewGuid);
- let secondPart = curArray.slice(targetIndex);
- p.groupMappings.set(StrCast(p.allListElements[index - 1].presentId, Utils.GenerateGuid()), firstPart);
- p.groupMappings.set(StrCast(document.presentId, Utils.GenerateGuid()), secondPart);
-
-
- }
-
- }
- this.autoSaveGroupChanges();
-
- }
-
-
- /**
- * This function is called at the end of each group update to update the group updates.
- */
- @action
- autoSaveGroupChanges = () => {
- let castedList: List<Doc> = new List<Doc>();
- this.props.presGroupBackUp.groupDocs = castedList;
- this.props.groupMappings.forEach((docArray: Doc[], id: String) => {
- //create a new doc for each group
- let newGroupDoc = new Doc();
- castedList.push(newGroupDoc);
- //store the id of the group in the doc
- newGroupDoc.presentIdStore = id.toString();
- //store the doc array which represents the group in the doc
- newGroupDoc.grouping = new List(docArray);
- });
-
- }
-
- /**
- * Function that is called on click to change the group status of a docus, by turning the option on/off.
- */
@action
- changeGroupStatus = () => {
- if (this.selectedButtons[buttonIndex.Group]) {
- this.selectedButtons[buttonIndex.Group] = false;
- } else {
- this.selectedButtons[buttonIndex.Group] = true;
- }
- this.autoSaveButtonChange(buttonIndex.Group);
-
+ onGroupClick = (e: React.MouseEvent) => {
+ this.groupButton = !this.groupButton;
}
/**
@@ -266,31 +96,18 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onHideDocumentUntilPressClick = (e: React.MouseEvent) => {
e.stopPropagation();
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (this.selectedButtons[buttonIndex.HideTillPressed]) {
- this.selectedButtons[buttonIndex.HideTillPressed] = false;
- if (this.props.index >= current) {
+ this.hideTillShownButton = !this.hideTillShownButton;
+ if (!this.hideTillShownButton) {
+ if (this.props.index >= this.currentIndex) {
this.props.document.opacity = 1;
}
} else {
- this.selectedButtons[buttonIndex.HideTillPressed] = true;
if (this.props.presStatus) {
- if (this.props.index > current) {
+ if (this.props.index > this.currentIndex) {
this.props.document.opacity = 0;
}
}
}
- this.autoSaveButtonChange(buttonIndex.HideTillPressed);
- }
-
- /**
- * This function is called to get the updates for the changed buttons.
- */
- @action
- autoSaveButtonChange = async (index: buttonIndex) => {
- if (this.backUpDoc) {
- this.backUpDoc.selectedButtons = new List(this.selectedButtons);
- }
}
/**
@@ -301,25 +118,19 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (this.selectedButtons[buttonIndex.HideAfter]) {
- this.selectedButtons[buttonIndex.HideAfter] = false;
- if (this.props.index <= current) {
+ this.hideAfterButton = !this.hideAfterButton;
+ if (!this.hideAfterButton) {
+ if (this.props.index <= this.currentIndex) {
this.props.document.opacity = 1;
}
} else {
- if (this.selectedButtons[buttonIndex.FadeAfter]) {
- this.selectedButtons[buttonIndex.FadeAfter] = false;
- }
- this.selectedButtons[buttonIndex.HideAfter] = true;
+ if (this.fadeButton) this.fadeButton = false;
if (this.props.presStatus) {
- if (this.props.index < current) {
+ if (this.props.index < this.currentIndex) {
this.props.document.opacity = 0;
}
}
}
- this.autoSaveButtonChange(buttonIndex.HideAfter);
-
}
/**
@@ -330,25 +141,19 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => {
e.stopPropagation();
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (this.selectedButtons[buttonIndex.FadeAfter]) {
- this.selectedButtons[buttonIndex.FadeAfter] = false;
- if (this.props.index <= current) {
+ this.fadeButton = !this.fadeButton;
+ if (!this.fadeButton) {
+ if (this.props.index <= this.currentIndex) {
this.props.document.opacity = 1;
}
} else {
- if (this.selectedButtons[buttonIndex.HideAfter]) {
- this.selectedButtons[buttonIndex.HideAfter] = false;
- }
- this.selectedButtons[buttonIndex.FadeAfter] = true;
+ this.hideAfterButton = false;
if (this.props.presStatus) {
- if (this.props.index < current) {
+ if (this.props.index < this.currentIndex) {
this.props.document.opacity = 0.5;
}
}
}
- this.autoSaveButtonChange(buttonIndex.FadeAfter);
-
}
/**
@@ -357,22 +162,13 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onNavigateDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- if (this.selectedButtons[buttonIndex.Navigate]) {
- this.selectedButtons[buttonIndex.Navigate] = false;
-
- } else {
- if (this.selectedButtons[buttonIndex.Show]) {
- this.selectedButtons[buttonIndex.Show] = false;
- }
- this.selectedButtons[buttonIndex.Navigate] = true;
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (current === this.props.index) {
+ this.navButton = !this.navButton;
+ if (this.navButton) {
+ this.showButton = false;
+ if (this.currentIndex === this.props.index) {
this.props.gotoDocument(this.props.index, this.props.index);
}
}
-
- this.autoSaveButtonChange(buttonIndex.Navigate);
-
}
/**
@@ -381,23 +177,16 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onZoomDocumentClick = (e: React.MouseEvent) => {
e.stopPropagation();
- if (this.selectedButtons[buttonIndex.Show]) {
- this.selectedButtons[buttonIndex.Show] = false;
- this.props.document.viewScale = 1;
+ this.showButton = !this.showButton;
+ if (!this.showButton) {
+ this.props.document.viewScale = 1;
} else {
- if (this.selectedButtons[buttonIndex.Navigate]) {
- this.selectedButtons[buttonIndex.Navigate] = false;
- }
- this.selectedButtons[buttonIndex.Show] = true;
- const current = NumCast(this.props.mainDocument.selectedDoc);
- if (current === this.props.index) {
+ this.navButton = false;
+ if (this.currentIndex === this.props.index) {
this.props.gotoDocument(this.props.index, this.props.index);
}
}
-
- this.autoSaveButtonChange(buttonIndex.Show);
-
}
/**
@@ -407,13 +196,8 @@ export default class PresentationElement extends React.Component<PresentationEle
@action
onRightTabClick = (e: React.MouseEvent) => {
e.stopPropagation();
- if (this.selectedButtons[buttonIndex.OpenRight]) {
- this.selectedButtons[buttonIndex.OpenRight] = false;
- // action maybe
- } else {
- this.selectedButtons[buttonIndex.OpenRight] = true;
- }
- this.autoSaveButtonChange(buttonIndex.OpenRight);
+
+ this.openRightButton = !this.openRightButton;
}
/**
@@ -449,8 +233,6 @@ export default class PresentationElement extends React.Component<PresentationEle
//where does treeViewId come from
let movedDocs = (de.data.options === this.props.mainDocument[Id] ? de.data.draggedDocuments : de.data.droppedDocuments);
//console.log("How is this causing an issue");
- let droppedDoc: Doc = de.data.droppedDocuments[0];
- await this.updateGroupsOnDrop(droppedDoc, de);
document.removeEventListener("pointermove", this.onDragMove, true);
return (de.data.dropAction || de.data.userDropAction) ?
de.data.droppedDocuments.reduce((added: boolean, d: Doc) => Doc.AddDocToList(this.props.mainDocument, "data", d, this.props.document, before) || added, false)
@@ -463,221 +245,13 @@ export default class PresentationElement extends React.Component<PresentationEle
return false;
}
- /**
- * This method is called to update groups when the user drags and drops an
- * element to a different place. It follows the default behaviour and reconstructs
- * the groups in the way they would appear if clicked by user.
- */
- updateGroupsOnDrop = async (droppedDoc: Doc, de: DragManager.DropEvent) => {
-
- let x = this.ScreenToLocalListTransform(de.x, de.y);
- let rect = this.header!.getBoundingClientRect();
- let bounds = this.ScreenToLocalListTransform(rect.left, rect.top + rect.height / 2);
- let before = x[1] < bounds[1];
-
- let droppedDocIndex = this.props.allListElements.indexOf(droppedDoc);
-
- let dropIndexDiff = droppedDocIndex - this.props.index;
-
- //checking if the position it's dropped corresponds to current location with 3 cases.
- if (droppedDocIndex === this.props.index) {
- return;
- }
-
- if (dropIndexDiff === 1 && !before) {
- return;
- }
- if (dropIndexDiff === -1 && before) {
- return;
- }
-
- let p = this.props;
- let droppedDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(droppedDoc);
- let curDocGuid = StrCast(droppedDoc.presentId, null);
-
- //Splicing the doc from its current group, since it's moved
- if (p.groupMappings.has(curDocGuid)) {
- let groupArray = this.props.groupMappings.get(curDocGuid)!;
-
- if (droppedDocSelectedButtons[buttonIndex.Group]) {
- let groupIndexOfDrop = groupArray.indexOf(droppedDoc);
- let firstPart = groupArray.splice(0, groupIndexOfDrop);
-
- if (firstPart.length > 1) {
- let newGroupGuid = Utils.GenerateGuid();
- firstPart.forEach((doc: Doc) => doc.presentId = newGroupGuid);
- this.props.groupMappings.set(newGroupGuid, firstPart);
- }
- }
-
- groupArray.splice(groupArray.indexOf(droppedDoc), 1);
- if (groupArray.length === 0) {
- this.props.groupMappings.delete(curDocGuid);
- }
- droppedDoc.presentId = Utils.GenerateGuid();
-
- //making sure to correct to groups after splicing, in case the dragged element
- //had the grouping on.
- let indexOfBelow = droppedDocIndex + 1;
- if (indexOfBelow < this.props.allListElements.length && indexOfBelow > 1) {
- let selectedButtonsOrigBelow: boolean[] = await this.getSelectedButtonsOfDoc(this.props.allListElements[indexOfBelow]);
- let aboveBelowDoc: Doc = this.props.allListElements[droppedDocIndex - 1];
- let aboveBelowDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(aboveBelowDoc);
- let belowDoc: Doc = this.props.allListElements[indexOfBelow];
- let belowDocPresId = StrCast(belowDoc.presentId);
-
- if (selectedButtonsOrigBelow[buttonIndex.Group]) {
- let belowDocGroup: Doc[] = this.props.groupMappings.get(belowDocPresId)!;
- if (aboveBelowDocSelectedButtons[buttonIndex.Group]) {
- let aboveBelowDocPresId = StrCast(aboveBelowDoc.presentId);
- if (this.props.groupMappings.has(aboveBelowDocPresId)) {
- let aboveBelowDocGroup: Doc[] = this.props.groupMappings.get(aboveBelowDocPresId)!;
- aboveBelowDocGroup.push(...belowDocGroup);
- this.props.groupMappings.delete(belowDocPresId);
- belowDocGroup.forEach((doc: Doc) => doc.presentId = aboveBelowDocPresId);
-
- }
- } else {
- belowDocGroup.unshift(aboveBelowDoc);
- aboveBelowDoc.presentId = belowDocPresId;
- }
-
-
- }
- }
-
- }
-
- //Case, when the dropped doc had the group button clicked.
- if (droppedDocSelectedButtons[buttonIndex.Group]) {
- if (before) {
- if (this.props.index > 0) {
- let aboveDoc = this.props.allListElements[this.props.index - 1];
- let aboveDocGuid = StrCast(aboveDoc.presentId);
- if (this.props.groupMappings.has(aboveDocGuid)) {
- this.protectOrderAndPush(aboveDocGuid, aboveDoc, droppedDoc);
- } else {
- this.createNewGroup(aboveDoc, droppedDoc, aboveDocGuid);
- }
- } else {
- let propsPresId = StrCast(this.props.document.presentId);
- if (this.selectedButtons[buttonIndex.Group]) {
- let propsArray = this.props.groupMappings.get(propsPresId)!;
- propsArray.unshift(droppedDoc);
- droppedDoc.presentId = propsPresId;
- }
- }
- } else {
- let propsDocGuid = StrCast(this.props.document.presentId);
- if (this.props.groupMappings.has(propsDocGuid)) {
- this.protectOrderAndPush(propsDocGuid, this.props.document, droppedDoc);
-
- } else {
- this.createNewGroup(this.props.document, droppedDoc, propsDocGuid);
- }
- }
-
-
- //if the group button of the element was not clicked.
- } else {
- if (before) {
- if (this.props.index > 0) {
-
- let aboveDoc = this.props.allListElements[this.props.index - 1];
- let aboveDocGuid = StrCast(aboveDoc.presentId);
- let aboveDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(aboveDoc);
-
-
- if (this.selectedButtons[buttonIndex.Group]) {
- if (aboveDocSelectedButtons[buttonIndex.Group]) {
- let aboveGroupArray = this.props.groupMappings.get(aboveDocGuid)!;
- let propsDocPresId = StrCast(this.props.document.presentId);
-
- this.halveGroupArray(aboveDoc, aboveGroupArray, droppedDoc, propsDocPresId);
-
- } else {
- let belowPresentId = StrCast(this.props.document.presentId);
- let belowGroup = this.props.groupMappings.get(belowPresentId)!;
- belowGroup.splice(belowGroup.indexOf(aboveDoc), 1);
- belowGroup.unshift(droppedDoc);
- droppedDoc.presentId = belowPresentId;
- aboveDoc.presentId = Utils.GenerateGuid();
- }
-
-
- }
- } else {
- let propsPresId = StrCast(this.props.document.presentId);
- if (this.selectedButtons[buttonIndex.Group]) {
- let propsArray = this.props.groupMappings.get(propsPresId)!;
- propsArray.unshift(droppedDoc);
- droppedDoc.presentId = propsPresId;
- }
- }
- } else {
- if (this.props.index < this.props.allListElements.length - 1) {
- let belowDoc = this.props.allListElements[this.props.index + 1];
- let belowDocGuid = StrCast(belowDoc.presentId);
- let belowDocSelectedButtons: boolean[] = await this.getSelectedButtonsOfDoc(belowDoc);
-
- let propsDocGuid = StrCast(this.props.document.presentId);
-
- if (belowDocSelectedButtons[buttonIndex.Group]) {
- let belowGroupArray = this.props.groupMappings.get(belowDocGuid)!;
- if (this.selectedButtons[buttonIndex.Group]) {
-
- let propsGroupArray = this.props.groupMappings.get(propsDocGuid)!;
-
- this.halveGroupArray(this.props.document, propsGroupArray, droppedDoc, belowDocGuid);
-
- } else {
- belowGroupArray.splice(belowGroupArray.indexOf(this.props.document), 1);
- this.props.document.presentId = Utils.GenerateGuid();
- belowGroupArray.unshift(droppedDoc);
- droppedDoc.presentId = belowDocGuid;
- }
- }
-
- }
- }
- }
- this.autoSaveGroupChanges();
-
- }
-
- /**
- * This method returns the selectedButtons boolean array of the passed in doc,
- * retrieving it from the back-up.
- */
- getSelectedButtonsOfDoc = async (paramDoc: Doc) => {
- let castedList = Cast(this.props.presButtonBackUp.selectedButtonDocs, listSpec(Doc));
- let foundSelectedButtons: boolean[] = new Array(7);
-
- //if this is the first time this doc mounts, push a doc for it to store
- for (let doc of castedList!) {
- let curDoc = await doc;
- let curDocId = StrCast(curDoc.docId);
- if (curDocId === paramDoc[Id]) {
- let selectedButtonOfDoc = Cast(curDoc.selectedButtons, listSpec("boolean"), null);
- if (selectedButtonOfDoc !== undefined) {
- return selectedButtonOfDoc;
- }
- }
- }
-
- return foundSelectedButtons;
-
- }
-
//This is used to add dragging as an event.
onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
- let selected = NumCast(this.props.mainDocument.selectedDoc, 0);
this.header!.className = "presentationView-item";
-
- if (selected === this.props.index) {
+ if (this.currentIndex === this.props.index) {
//this doc is selected
this.header!.className = "presentationView-item presentationView-selected";
}
@@ -687,13 +261,9 @@ export default class PresentationElement extends React.Component<PresentationEle
//This is used to remove the dragging when dropped.
onPointerLeave = (e: React.PointerEvent): void => {
- //to get currently selected presentation doc
- let selected = NumCast(this.props.mainDocument.selectedDoc, 0);
-
this.header!.className = "presentationView-item";
-
- if (selected === this.props.index) {
+ if (this.currentIndex === this.props.index) {
//this doc is selected
this.header!.className = "presentationView-item presentationView-selected";
@@ -729,62 +299,6 @@ export default class PresentationElement extends React.Component<PresentationEle
move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
return this.props.document !== target && this.props.removeDocByRef(doc) && addDoc(doc);
}
-
- /**
- * Helper method that gets called to divide a group array into two different groups
- * including the targetDoc in first part.
- * @param targetDoc document that is targeted as slicing point
- * @param propsGroupArray the array that gets divided into 2
- * @param droppedDoc the dropped document
- * @param belowDocGuid presentId of the belowGroup
- */
- private halveGroupArray(targetDoc: Doc, propsGroupArray: Doc[], droppedDoc: Doc, belowDocGuid: string) {
- let targetIndex = propsGroupArray.indexOf(targetDoc);
- let firstPart = propsGroupArray.slice(0, targetIndex + 1);
- let firstPartNewGuid = Utils.GenerateGuid();
- firstPart.forEach((doc: Doc) => doc.presentId = firstPartNewGuid);
- let secondPart = propsGroupArray.slice(targetIndex + 1);
- secondPart.unshift(droppedDoc);
- droppedDoc.presentId = belowDocGuid;
- this.props.groupMappings.set(firstPartNewGuid, firstPart);
- this.props.groupMappings.set(belowDocGuid, secondPart);
- }
-
- /**
- * Helper method that creates a new group, pushing above document first,
- * and dropped document second.
- * @param aboveDoc the document above dropped document
- * @param droppedDoc the dropped document itself
- * @param aboveDocGuid above document's presentId
- */
- private createNewGroup(aboveDoc: Doc, droppedDoc: Doc, aboveDocGuid: string) {
- let newGroup: Doc[] = [];
- newGroup.push(aboveDoc);
- newGroup.push(droppedDoc);
- droppedDoc.presentId = aboveDocGuid;
- this.props.groupMappings.set(aboveDocGuid, newGroup);
- }
-
- /**
- * Helper method that finds the above document's group, and pushes the
- * dropped document into that group, protecting the visual order of the
- * presentation elements.
- * @param aboveDoc the document above dropped document
- * @param droppedDoc the dropped document itself
- * @param aboveDocGuid above document's presentId
- */
- private protectOrderAndPush(aboveDocGuid: string, aboveDoc: Doc, droppedDoc: Doc) {
- let groupArray = this.props.groupMappings.get(aboveDocGuid)!;
- let tempStack: Doc[] = [];
- while (groupArray[groupArray.length - 1] !== aboveDoc) {
- tempStack.push(groupArray.pop()!);
- }
- groupArray.push(droppedDoc);
- droppedDoc.presentId = aboveDocGuid;
- while (tempStack.length !== 0) {
- groupArray.push(tempStack.pop()!);
- }
- }
/**
* This function is a getter to get if a document is in previewMode.
*/
@@ -837,14 +351,15 @@ export default class PresentationElement extends React.Component<PresentationEle
Document={this.props.document}
addDocument={returnFalse}
removeDocument={returnFalse}
+ ruleProvider={undefined}
ScreenToLocalTransform={Transform.Identity}
addDocTab={returnFalse}
+ pinToPres={returnFalse}
renderDepth={1}
PanelWidth={() => 350}
PanelHeight={() => 90}
focus={emptyFunction}
backgroundColor={returnEmptyString}
- selectOnLoad={false}
parentActive={returnFalse}
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
@@ -871,11 +386,8 @@ export default class PresentationElement extends React.Component<PresentationEle
let p = this.props;
let title = p.document.title;
- //to get currently selected presentation doc
- let selected = NumCast(p.mainDocument.selectedDoc, 0);
-
let className = " presentationView-item";
- if (selected === p.index) {
+ if (this.currentIndex === p.index) {
//this doc is selected
className += " presentationView-selected";
}
@@ -891,23 +403,19 @@ export default class PresentationElement extends React.Component<PresentationEle
outlineStyle: "dashed",
outlineWidth: Doc.IsBrushed(p.document) ? `1px` : "0px",
}}
- onClick={e => { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}>
+ onClick={e => { p.gotoDocument(p.index, this.currentIndex); e.stopPropagation(); }}>
<strong className="presentationView-name">
{`${p.index + 1}. ${title}`}
</strong>
<button className="presentation-icon" onPointerDown={(e) => e.stopPropagation()} onClick={e => { this.props.deleteDocument(p.index); e.stopPropagation(); }}>X</button>
<br></br>
- <button title="Zoom" className={this.selectedButtons[buttonIndex.Show] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
- <button title="Navigate" className={this.selectedButtons[buttonIndex.Navigate] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
- <button title="Hide Document Till Presented" className={this.selectedButtons[buttonIndex.HideTillPressed] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
- <button title="Fade Document After Presented" className={this.selectedButtons[buttonIndex.FadeAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} color={"gray"} /></button>
- <button title="Hide Document After Presented" className={this.selectedButtons[buttonIndex.HideAfter] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
- <button title="Group With Up" className={this.selectedButtons[buttonIndex.Group] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={(e) => {
- e.stopPropagation();
- this.changeGroupStatus();
- this.onGroupClick(p.document, p.index, this.selectedButtons[buttonIndex.Group]);
- }}> <FontAwesomeIcon icon={"arrow-up"} /> </button>
- <button title="Open Right" className={this.selectedButtons[buttonIndex.OpenRight] ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onRightTabClick}><FontAwesomeIcon icon={"arrow-right"} /></button>
+ <button title="Zoom" className={this.showButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onZoomDocumentClick}><FontAwesomeIcon icon={"search"} /></button>
+ <button title="Navigate" className={this.navButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onNavigateDocumentClick}><FontAwesomeIcon icon={"location-arrow"} /></button>
+ <button title="Hide Document Till Presented" className={this.hideTillShownButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentUntilPressClick}><FontAwesomeIcon icon={fileSolid} /></button>
+ <button title="Fade Document After Presented" className={this.fadeButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onFadeDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+ <button title="Hide Document After Presented" className={this.hideAfterButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onHideDocumentAfterPresentedClick}><FontAwesomeIcon icon={faFileDownload} /></button>
+ <button title="Group With Up" className={this.groupButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onGroupClick}> <FontAwesomeIcon icon={"arrow-up"} /> </button>
+ <button title="Open Right" className={this.openRightButton ? "presentation-interaction-selected" : "presentation-interaction"} onPointerDown={(e) => e.stopPropagation()} onClick={this.onRightTabClick}><FontAwesomeIcon icon={"arrow-right"} /></button>
<br />
{this.renderEmbeddedInline()}
diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx
index e853c4070..930ce202e 100644
--- a/src/client/views/presentationview/PresentationList.tsx
+++ b/src/client/views/presentationview/PresentationList.tsx
@@ -1,27 +1,20 @@
-import { observer } from "mobx-react";
-import React = require("react");
import { action } from "mobx";
-import "./PresentationView.scss";
-import { Utils } from "../../../Utils";
-import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { NumCast, StrCast } from "../../../new_fields/Types";
+import { observer } from "mobx-react";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
-import PresentationElement, { buttonIndex } from "./PresentationElement";
-import "../../../new_fields/Doc";
-
-
+import { NumCast } from "../../../new_fields/Types";
+import PresentationElement from "./PresentationElement";
+import "./PresentationView.scss";
+import React = require("react");
interface PresListProps {
mainDocument: Doc;
deleteDocument(index: number): void;
gotoDocument(index: number, fromDoc: number): Promise<void>;
- groupMappings: Map<String, Doc[]>;
PresElementsMappings: Map<Doc, PresentationElement>;
setChildrenDocs: (docList: Doc[]) => void;
presStatus: boolean;
- presButtonBackUp: Doc;
- presGroupBackUp: Doc;
removeDocByRef(doc: Doc): boolean;
clearElemMap(): void;
@@ -34,35 +27,6 @@ interface PresListProps {
*/
export default class PresentationViewList extends React.Component<PresListProps> {
- /**
- * Method that initializes presentation ids for the
- * docs that is in the presentation, when presentation list
- * gets re-rendered. It makes sure to not assign ids to the
- * docs that are in the group, so that mapping won't be disrupted.
- */
-
- @action
- initializeGroupIds = async (docList: Doc[]) => {
- docList.forEach(async (doc: Doc, index: number) => {
- let docGuid = StrCast(doc.presentId, null);
- //checking if part of group
- let storedGuids: string[] = [];
- let castedGroupDocs = await DocListCastAsync(this.props.presGroupBackUp.groupDocs);
- //making sure the docs that were in groups, which were stored, to not get new guids.
- if (castedGroupDocs !== undefined) {
- castedGroupDocs.forEach((doc: Doc) => {
- let storedGuid = StrCast(doc.presentIdStore, null);
- if (storedGuid) {
- storedGuids.push(storedGuid);
- }
-
- });
- }
- if (!this.props.groupMappings.has(docGuid) && !storedGuids.includes(docGuid)) {
- doc.presentId = Utils.GenerateGuid();
- }
- });
- }
/**
* Initially every document starts with a viewScale 1, which means
@@ -80,7 +44,6 @@ export default class PresentationViewList extends React.Component<PresListProps>
render() {
const children = DocListCast(this.props.mainDocument.data);
- this.initializeGroupIds(children);
this.initializeScaleViews(children);
this.props.setChildrenDocs(children);
this.props.clearElemMap();
@@ -99,11 +62,8 @@ export default class PresentationViewList extends React.Component<PresListProps>
index={index}
deleteDocument={this.props.deleteDocument}
gotoDocument={this.props.gotoDocument}
- groupMappings={this.props.groupMappings}
allListElements={children}
presStatus={this.props.presStatus}
- presButtonBackUp={this.props.presButtonBackUp}
- presGroupBackUp={this.props.presGroupBackUp}
removeDocByRef={this.props.removeDocByRef}
PresElementsMappings={this.props.PresElementsMappings}
/>
diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss
index 65b09c833..5c40a8808 100644
--- a/src/client/views/presentationview/PresentationView.scss
+++ b/src/client/views/presentationview/PresentationView.scss
@@ -1,13 +1,14 @@
.presentationView-cont {
position: absolute;
- background: white;
z-index: 2;
box-shadow: #AAAAAA .2vw .2vw .4vw;
right: 0;
top: 0;
bottom: 0;
letter-spacing: 2px;
-
+ overflow: hidden;
+ transition: 0.7s opacity ease;
+ pointer-events: all;
}
.presentationView-item {
@@ -22,14 +23,11 @@
user-select: none;
transition: all .1s;
-
-
.documentView-node {
position: absolute;
z-index: 1;
}
-
}
.presentationView-item-above {
@@ -47,12 +45,15 @@
.presentationView-item:hover {
transition: all .1s;
- background: #AAAAAA
+ background: #AAAAAA;
+ border-radius: 12px;
}
.presentationView-selected {
background: gray;
color: black;
+ border-radius: 12px;
+ box-shadow: black 2px 2px 5px;
}
.presentationView-heading {
@@ -69,7 +70,6 @@
display: inline-block;
width: calc(100% - 200px);
letter-spacing: 2px;
-
}
.presentation-icon {
@@ -77,11 +77,12 @@
}
.presentation-interaction {
+ color: gray;
float: left;
}
.presentation-interaction-selected {
- background: #505050;
+ color: white;
float: left;
}
@@ -93,7 +94,8 @@
.presentation-button {
margin-right: 2.5%;
margin-left: 2.5%;
- width: 25%;
+ width: 20%;
+ border-radius: 5px;
}
.presentation-buttons {
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
deleted file mode 100644
index bea70f00b..000000000
--- a/src/client/views/presentationview/PresentationView.tsx
+++ /dev/null
@@ -1,994 +0,0 @@
-import { observer } from "mobx-react";
-import React = require("react");
-import { observable, action, runInAction, reaction, autorun } from "mobx";
-import "./PresentationView.scss";
-import { DocumentManager } from "../../util/DocumentManager";
-import { Utils } from "../../../Utils";
-import { Doc, DocListCast, DocListCastAsync, WidthSym } from "../../../new_fields/Doc";
-import { listSpec } from "../../../new_fields/Schema";
-import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { List } from "../../../new_fields/List";
-import PresentationElement, { buttonIndex } from "./PresentationElement";
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit, faEye } from '@fortawesome/free-solid-svg-icons';
-import { Docs } from "../../documents/Documents";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import PresentationViewList from "./PresentationList";
-import PresModeMenu from "./PresentationModeMenu";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-
-library.add(faArrowLeft);
-library.add(faArrowRight);
-library.add(faPlay);
-library.add(faStop);
-library.add(faPlus);
-library.add(faTimes);
-library.add(faMinus);
-library.add(faEdit);
-library.add(faEye);
-
-
-export interface PresViewProps {
- Documents: List<Doc>;
-}
-
-const expandedWidth = 400;
-const presMinWidth = 300;
-
-@observer
-export class PresentationView extends React.Component<PresViewProps> {
- public static Instance: PresentationView;
-
- //Mapping from presentation ids to a list of doc that represent a group
- @observable groupMappings: Map<String, Doc[]> = new Map();
- //mapping from docs to their rendered component
- @observable presElementsMappings: Map<Doc, PresentationElement> = new Map();
- //variable that holds all the docs in the presentation
- @observable childrenDocs: Doc[] = [];
- //variable to hold if presentation is started
- @observable presStatus: boolean = false;
- //back-up so that presentation stays the way it's when refreshed
- @observable presGroupBackUp: Doc = new Doc();
- @observable presButtonBackUp: Doc = new Doc();
-
- //Keeping track of the doc for the current presentation
- @observable curPresentation: Doc = new Doc();
- //Mapping of guids to presentations.
- @observable presentationsMapping: Map<String, Doc> = new Map();
- //Mapping of presentations to guid, so that select option values can be given.
- @observable presentationsKeyMapping: Map<Doc, String> = new Map();
- //Variable to keep track of guid of the current presentation
- @observable currentSelectedPresValue: string | undefined;
- //A flag to keep track if title input is open, which is used in rendering.
- @observable PresTitleInputOpen: boolean = false;
- //Variable that holds reference to title input, so that new presentations get titles assigned.
- @observable titleInputElement: HTMLInputElement | undefined;
- @observable PresTitleChangeOpen: boolean = false;
- @observable presMode: boolean = false;
-
-
- @observable opacity = 1;
- @observable persistOpacity = true;
- @observable labelOpacity = 0;
-
- //initilize class variables
- constructor(props: PresViewProps) {
- super(props);
- PresentationView.Instance = this;
- }
-
- @action
- toggle = (forcedValue: boolean | undefined) => {
- if (forcedValue !== undefined) {
- this.curPresentation.width = forcedValue ? expandedWidth : 0;
- } else {
- this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
- }
- }
-
- //The first lifecycle function that gets called to set up the current presentation.
- async componentWillMount() {
-
- this.props.Documents.forEach(async (doc, index: number) => {
-
- //For each presentation received from mainContainer, a mapping is created.
- let curDoc: Doc = await doc;
- let newGuid = Utils.GenerateGuid();
- this.presentationsKeyMapping.set(curDoc, newGuid);
- this.presentationsMapping.set(newGuid, curDoc);
-
- //The Presentation at first index gets set as default start presentation
- if (index === 0) {
- runInAction(() => this.currentSelectedPresValue = newGuid);
- runInAction(() => this.curPresentation = curDoc);
- }
- });
- }
-
- //Second lifecycle function that gets called when component mounts. It makes sure to
- //get the back-up information from previous session for the current presentation.
- async componentDidMount() {
- let docAtZero = await this.props.Documents[0];
- runInAction(() => this.curPresentation = docAtZero);
-
- this.setPresentationBackUps();
-
- }
-
-
- /**
- * The function that retrieves the backUps for the current Presentation if present,
- * otherwise initializes.
- */
- setPresentationBackUps = async () => {
- //getting both backUp documents
-
- let castedGroupBackUp = Cast(this.curPresentation.presGroupBackUp, Doc);
- let castedButtonBackUp = Cast(this.curPresentation.presButtonBackUp, Doc);
- //if instantiated before
- if (castedGroupBackUp instanceof Promise) {
- castedGroupBackUp.then(doc => {
- let toAssign = doc ? doc : new Doc();
- this.curPresentation.presGroupBackUp = toAssign;
- runInAction(() => this.presGroupBackUp = toAssign);
- if (doc) {
- if (toAssign[Id] === doc[Id]) {
- this.retrieveGroupMappings();
- }
- }
- });
-
- //if never instantiated a store doc yet
- } else if (castedGroupBackUp instanceof Doc) {
- let castedDoc: Doc = await castedGroupBackUp;
- runInAction(() => this.presGroupBackUp = castedDoc);
- this.retrieveGroupMappings();
- } else {
- runInAction(() => {
- let toAssign = new Doc();
- this.presGroupBackUp = toAssign;
- this.curPresentation.presGroupBackUp = toAssign;
-
- });
-
- }
- //if instantiated before
- if (castedButtonBackUp instanceof Promise) {
- castedButtonBackUp.then(doc => {
- let toAssign = doc ? doc : new Doc();
- this.curPresentation.presButtonBackUp = toAssign;
- runInAction(() => this.presButtonBackUp = toAssign);
- });
-
- //if never instantiated a store doc yet
- } else if (castedButtonBackUp instanceof Doc) {
- let castedDoc: Doc = await castedButtonBackUp;
- runInAction(() => this.presButtonBackUp = castedDoc);
-
- } else {
- runInAction(() => {
- let toAssign = new Doc();
- this.presButtonBackUp = toAssign;
- this.curPresentation.presButtonBackUp = toAssign;
- });
-
- }
-
-
- //storing the presentation status,ie. whether it was stopped or playing
- let presStatusBackUp = BoolCast(this.curPresentation.presStatus);
- runInAction(() => this.presStatus = presStatusBackUp);
- }
-
- /**
- * This is the function that is called to retrieve the groups that have been stored and
- * push them to the groupMappings.
- */
- retrieveGroupMappings = async () => {
- let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs);
- if (castedGroupDocs !== undefined) {
- castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => {
- let castedGrouping = await DocListCastAsync(groupDoc.grouping);
- let castedKey = StrCast(groupDoc.presentIdStore, null);
- if (castedGrouping) {
- castedGrouping.forEach((doc: Doc) => {
- doc.presentId = castedKey;
- });
- }
- if (castedGrouping !== undefined && castedKey !== undefined) {
- this.groupMappings.set(castedKey, castedGrouping);
- }
- });
- }
- }
-
- //observable means render is re-called every time variable is changed
- @observable
- collapsed: boolean = false;
- closePresentation = action(() => this.curPresentation.width = 0);
- next = async () => {
- const current = NumCast(this.curPresentation.selectedDoc);
- //asking to get document at current index
- let docAtCurrentNext = await this.getDocAtIndex(current + 1);
- if (docAtCurrentNext === undefined) {
- return;
- }
- //asking for it's presentation id
- let curNextPresId = StrCast(docAtCurrentNext.presentId);
- let nextSelected = current + 1;
-
- //if curDoc is in a group, selection slides until last one, if not it's next one
- if (this.groupMappings.has(curNextPresId)) {
- let currentsArray = this.groupMappings.get(StrCast(docAtCurrentNext.presentId))!;
- nextSelected = current + currentsArray.length - currentsArray.indexOf(docAtCurrentNext);
-
- //end of grup so go beyond
- if (nextSelected === current) nextSelected = current + 1;
- }
-
- this.gotoDocument(nextSelected, current);
-
- }
- back = async () => {
- const current = NumCast(this.curPresentation.selectedDoc);
- //requesting for the doc at current index
- let docAtCurrent = await this.getDocAtIndex(current);
- if (docAtCurrent === undefined) {
- return;
- }
-
- //asking for its presentation id.
- let curPresId = StrCast(docAtCurrent.presentId);
- let prevSelected = current - 1;
- let zoomOut: boolean = false;
-
- //checking if this presentation id is mapped to a group, if so chosing the first element in group
- if (this.groupMappings.has(curPresId)) {
- let currentsArray = this.groupMappings.get(StrCast(docAtCurrent.presentId))!;
- prevSelected = current - currentsArray.length + (currentsArray.length - currentsArray.indexOf(docAtCurrent)) - 1;
- //end of grup so go beyond
- if (prevSelected === current) prevSelected = current - 1;
-
- //checking if any of the group members had used zooming in
- currentsArray.forEach((doc: Doc) => {
- //let presElem: PresentationElement | undefined = this.presElementsMappings.get(doc);
- if (this.presElementsMappings.get(doc)!.selected[buttonIndex.Show]) {
- zoomOut = true;
- return;
- }
- });
-
- }
-
- // if a group set that flag to zero or a single element
- //If so making sure to zoom out, which goes back to state before zooming action
- if (current > 0) {
- if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.selected[buttonIndex.Show]) {
- let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null);
- let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]);
- if (prevScale !== undefined) {
- if (prevScale !== curScale) {
- DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
- }
- }
- }
- }
- this.gotoDocument(prevSelected, current);
-
- }
-
- /**
- * This is the method that checks for the actions that need to be performed
- * after the document has been presented, which involves 3 button options:
- * Hide Until Presented, Hide After Presented, Fade After Presented
- */
- showAfterPresented = (index: number) => {
- this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => {
- let selectedButtons: boolean[] = presElem.selected;
- //the order of cases is aligned based on priority
- if (selectedButtons[buttonIndex.HideTillPressed]) {
- if (this.childrenDocs.indexOf(key) <= index) {
- key.opacity = 1;
- }
- }
- if (selectedButtons[buttonIndex.HideAfter]) {
- if (this.childrenDocs.indexOf(key) < index) {
- key.opacity = 0;
- }
- }
- if (selectedButtons[buttonIndex.FadeAfter]) {
- if (this.childrenDocs.indexOf(key) < index) {
- key.opacity = 0.5;
- }
- }
- });
- }
-
- /**
- * This is the method that checks for the actions that need to be performed
- * before the document has been presented, which involves 3 button options:
- * Hide Until Presented, Hide After Presented, Fade After Presented
- */
- hideIfNotPresented = (index: number) => {
- this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => {
- let selectedButtons: boolean[] = presElem.selected;
-
- //the order of cases is aligned based on priority
-
- if (selectedButtons[buttonIndex.HideAfter]) {
- if (this.childrenDocs.indexOf(key) >= index) {
- key.opacity = 1;
- }
- }
- if (selectedButtons[buttonIndex.FadeAfter]) {
- if (this.childrenDocs.indexOf(key) >= index) {
- key.opacity = 1;
- }
- }
- if (selectedButtons[buttonIndex.HideTillPressed]) {
- if (this.childrenDocs.indexOf(key) > index) {
- key.opacity = 0;
- }
- }
- });
- }
-
- /**
- * This method makes sure that cursor navigates to the element that
- * has the option open and last in the group. If not in the group, and it has
- * te option open, navigates to that element.
- */
- navigateToElement = async (curDoc: Doc, fromDoc: number) => {
- let docToJump: Doc = curDoc;
- let curDocPresId = StrCast(curDoc.presentId, null);
- let willZoom: boolean = false;
-
- //checking if in group
- if (curDocPresId !== undefined) {
- if (this.groupMappings.has(curDocPresId)) {
- let currentDocGroup = this.groupMappings.get(curDocPresId)!;
- currentDocGroup.forEach((doc: Doc, index: number) => {
- let selectedButtons: boolean[] = this.presElementsMappings.get(doc)!.selected;
- if (selectedButtons[buttonIndex.Navigate]) {
- docToJump = doc;
- willZoom = false;
- }
- if (selectedButtons[buttonIndex.Show]) {
- docToJump = doc;
- willZoom = true;
- }
- });
- }
-
- }
- //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 curDocButtons = this.presElementsMappings.get(curDoc)!.selected;
- if (curDocButtons[buttonIndex.Navigate]) {
- this.jumpToTabOrRight(curDocButtons, curDoc);
- } else if (curDocButtons[buttonIndex.Show]) {
- let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]);
- if (curDocButtons[buttonIndex.OpenRight]) {
- //awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(curDoc, true);
- } else {
- await DocumentManager.Instance.jumpToDocument(curDoc, true, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
- }
-
- let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
- curDoc.viewScale = newScale;
-
- //saving the scale user was on before zooming in
- if (curScale !== 1) {
- this.childrenDocs[fromDoc].viewScale = curScale;
- }
-
- }
- return;
- }
- let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]);
- let curDocButtons = this.presElementsMappings.get(docToJump)!.selected;
-
-
- if (curDocButtons[buttonIndex.OpenRight]) {
- //awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(docToJump, willZoom);
- } else {
- await DocumentManager.Instance.jumpToDocument(docToJump, willZoom, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
- }
- let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
- curDoc.viewScale = newScale;
- //saving the scale that user was on
- if (curScale !== 1) {
- this.childrenDocs[fromDoc].viewScale = curScale;
- }
-
- }
-
- /**
- * This function checks if right option is clicked on a presentation element, if not it does open it as a tab
- * with help of CollectionDockingView.
- */
- jumpToTabOrRight = (curDocButtons: boolean[], curDoc: Doc) => {
- if (curDocButtons[buttonIndex.OpenRight]) {
- DocumentManager.Instance.jumpToDocument(curDoc, false);
- } else {
- DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
- }
- }
-
- /**
- * Async function that supposedly return the doc that is located at given index.
- */
- getDocAtIndex = async (index: number) => {
- const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
- if (!list) {
- return undefined;
- }
- if (index < 0 || index >= list.length) {
- return undefined;
- }
-
- this.curPresentation.selectedDoc = index;
- //awaiting async call to finish to get Doc instance
- const doc = await list[index];
- return doc;
- }
-
- /**
- * The function that removes a doc from a presentation. It also makes sure to
- * do necessary updates to backUps and mappings stored locally.
- */
- @action
- public RemoveDoc = async (index: number) => {
- const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
- if (value) {
- let removedDoc = await value.splice(index, 1)[0];
-
- //removing the Presentation Element stored for it
- this.presElementsMappings.delete(removedDoc);
-
- let removedDocPresentId = StrCast(removedDoc.presentId);
-
- //Removing it from local mapping of the groups
- if (this.groupMappings.has(removedDocPresentId)) {
- let removedDocsGroup = this.groupMappings.get(removedDocPresentId);
- if (removedDocsGroup) {
- removedDocsGroup.splice(removedDocsGroup.indexOf(removedDoc), 1);
- if (removedDocsGroup.length === 0) {
- this.groupMappings.delete(removedDocPresentId);
- }
- }
- }
-
-
- let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc));
- if (castedList) {
- for (let doc of castedList) {
- let curDoc = await doc;
- let curDocId = StrCast(curDoc.docId);
- if (curDocId === removedDoc[Id]) {
- castedList.splice(castedList.indexOf(curDoc), 1);
- break;
-
- }
- }
- }
-
- //removing it from the backup of groups
- let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs);
- if (castedGroupDocs) {
- castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => {
- let castedKey = StrCast(groupDoc.presentIdStore, null);
- if (castedKey === removedDocPresentId) {
- let castedGrouping = await DocListCastAsync(groupDoc.grouping);
- if (castedGrouping) {
- castedGrouping.splice(castedGrouping.indexOf(removedDoc), 1);
- if (castedGrouping.length === 0) {
- castedGroupDocs!.splice(castedGroupDocs!.indexOf(groupDoc), 1);
- }
- }
- }
-
- });
-
- }
-
-
- }
- }
-
- /**
- * An alternative remove method that removes a doc from presentation by its actual
- * reference.
- */
- public removeDocByRef = (doc: Doc) => {
- let indexOfDoc = this.childrenDocs.indexOf(doc);
- const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
- if (value) {
- value.splice(indexOfDoc, 1)[0];
- }
- if (indexOfDoc !== - 1) {
- return true;
- }
- return false;
- }
-
- //The function that is called when a document is clicked or reached through next or back.
- //it'll also execute the necessary actions if presentation is playing.
- @action
- public gotoDocument = async (index: number, fromDoc: number) => {
- const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc)));
- if (!list) {
- return;
- }
- if (index < 0 || index >= list.length) {
- return;
- }
- this.curPresentation.selectedDoc = index;
-
- if (!this.presStatus) {
- this.presStatus = true;
- this.startPresentation(index);
- }
-
- const doc = await list[index];
- if (this.presStatus) {
- this.navigateToElement(doc, fromDoc);
- this.hideIfNotPresented(index);
- this.showAfterPresented(index);
- }
-
- }
-
- //Function that is called to resetGroupIds, so that documents get new groupIds at
- //first load, when presentation is changed.
- resetGroupIds = async () => {
- let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs);
- if (castedGroupDocs !== undefined) {
- castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => {
- let castedGrouping = await DocListCastAsync(groupDoc.grouping);
- if (castedGrouping) {
- castedGrouping.forEach((doc: Doc) => {
- doc.presentId = Utils.GenerateGuid();
- });
- }
- });
- }
- runInAction(() => this.groupMappings = new Map());
- }
-
- /**
- * Adds a document to the presentation view
- **/
- @undoBatch
- @action
- public PinDoc(doc: Doc) {
- //add this new doc to props.Document
- const data = Cast(this.curPresentation.data, listSpec(Doc));
- if (data) {
- data.push(doc);
- } else {
- this.curPresentation.data = new List([doc]);
- }
-
- this.toggle(true);
- }
-
- //Function that sets the store of the children docs.
- @action
- setChildrenDocs = (docList: Doc[]) => {
- this.childrenDocs = docList;
- }
-
- //The function that is called to render the play or pause button depending on
- //if presentation is running or not.
- renderPlayPauseButton = () => {
- if (this.presStatus) {
- return <button title="Reset Presentation" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="stop" /></button>;
- } else {
- return <button title="Start Presentation From Start" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="play" /></button>;
- }
- }
-
- //The function that starts or resets presentaton functionally, depending on status flag.
- @action
- startOrResetPres = async () => {
- if (this.presStatus) {
- this.resetPresentation();
- } else {
- this.presStatus = true;
- let startIndex = await this.findStartDocument();
- this.startPresentation(startIndex);
- const current = NumCast(this.curPresentation.selectedDoc);
- this.gotoDocument(startIndex, current);
- }
- this.curPresentation.presStatus = this.presStatus;
- }
-
- /**
- * This method is called to find the start document of presentation. So
- * that when user presses on play, the correct presentation element will be
- * selected.
- */
- findStartDocument = async () => {
- let docAtZero = await this.getDocAtIndex(0);
- if (docAtZero === undefined) {
- return 0;
- }
- let docAtZeroPresId = StrCast(docAtZero.presentId);
-
- if (this.groupMappings.has(docAtZeroPresId)) {
- let group = this.groupMappings.get(docAtZeroPresId)!;
- let lastDoc = group[group.length - 1];
- return this.childrenDocs.indexOf(lastDoc);
- } else {
- return 0;
- }
- }
-
- //The function that resets the presentation by removing every action done by it. It also
- //stops the presentaton.
- @action
- resetPresentation = () => {
- this.childrenDocs.forEach((doc: Doc) => {
- doc.opacity = 1;
- doc.viewScale = 1;
- });
- this.curPresentation.selectedDoc = 0;
- this.presStatus = false;
- this.curPresentation.presStatus = this.presStatus;
- if (this.childrenDocs.length === 0) {
- return;
- }
- DocumentManager.Instance.zoomIntoScale(this.childrenDocs[0], 1);
- }
-
-
- //The function that starts the presentation, also checking if actions should be applied
- //directly at start.
- startPresentation = (startIndex: number) => {
- let selectedButtons: boolean[];
- this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => {
- selectedButtons = component.selected;
- if (selectedButtons[buttonIndex.HideTillPressed]) {
- if (this.childrenDocs.indexOf(doc) > startIndex) {
- doc.opacity = 0;
- }
-
- }
- if (selectedButtons[buttonIndex.HideAfter]) {
- if (this.childrenDocs.indexOf(doc) < startIndex) {
- doc.opacity = 0;
- }
- }
- if (selectedButtons[buttonIndex.FadeAfter]) {
- if (this.childrenDocs.indexOf(doc) < startIndex) {
- doc.opacity = 0.5;
- }
- }
-
- });
-
- }
-
- /**
- * The function that is called to add a new presentation to the presentationView.
- * It sets up te mappings and local copies of it. Resets the groupings and presentation.
- * Makes the new presentation current selected, and retrieve the back-Ups if present.
- */
- @action
- addNewPresentation = (presTitle: string) => {
- //creating a new presentation doc
- let newPresentationDoc = Docs.Create.TreeDocument([], { title: presTitle });
- this.props.Documents.push(newPresentationDoc);
-
- //setting that new doc as current
- this.curPresentation = newPresentationDoc;
-
- //storing the doc in local copies for easier access
- let newGuid = Utils.GenerateGuid();
- this.presentationsMapping.set(newGuid, newPresentationDoc);
- this.presentationsKeyMapping.set(newPresentationDoc, newGuid);
-
- //resetting the previous presentation's actions so that new presentation can be loaded.
- this.resetGroupIds();
- this.resetPresentation();
- this.presElementsMappings = new Map();
- this.currentSelectedPresValue = newGuid;
- this.setPresentationBackUps();
-
- }
-
- /**
- * The function that is called to change the current selected presentation.
- * Changes the presentation, also resetting groupings and presentation in process.
- * Plus retrieving the backUps for the newly selected presentation.
- */
- @action
- getSelectedPresentation = (e: React.ChangeEvent<HTMLSelectElement>) => {
- //get the guid of the selected presentation
- let selectedGuid = e.target.value;
- //set that as current presentation
- this.curPresentation = this.presentationsMapping.get(selectedGuid)!;
-
- //reset current Presentations local things so that new one can be loaded
- this.resetGroupIds();
- this.resetPresentation();
- this.presElementsMappings = new Map();
- this.currentSelectedPresValue = selectedGuid;
- this.setPresentationBackUps();
-
-
- }
-
- /**
- * The function that is called to render either select for presentations, or title inputting.
- */
- renderSelectOrPresSelection = () => {
- let presentationList = DocListCast(this.props.Documents);
- if (this.PresTitleInputOpen || this.PresTitleChangeOpen) {
- return <input ref={(e) => this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />;
- } else {
- return <select value={this.currentSelectedPresValue} id="pres_selector" className="presentationView-title" onChange={this.getSelectedPresentation}>
- {presentationList.map((doc: Doc, index: number) => {
- let mappedGuid = this.presentationsKeyMapping.get(doc);
- let docGuid: string = mappedGuid ? mappedGuid.toString() : "";
- return <option key={docGuid} value={docGuid}>{StrCast(doc.title)}</option>;
- })}
- </select>;
- }
- }
-
- /**
- * The function that is called on enter press of title input. It gives the
- * new presentation the title user entered. If nothing is entered, gives a default title.
- */
- @action
- submitPresentationTitle = (e: React.KeyboardEvent) => {
- if (e.keyCode === 13) {
- let presTitle = this.titleInputElement!.value;
- this.titleInputElement!.value = "";
- if (this.PresTitleInputOpen) {
- if (presTitle === "") {
- presTitle = "Presentation";
- }
- this.PresTitleInputOpen = false;
- this.addNewPresentation(presTitle);
- } else if (this.PresTitleChangeOpen) {
- this.PresTitleChangeOpen = false;
- this.changePresentationTitle(presTitle);
- }
- }
- }
-
- /**
- * The function that is called to remove a presentation from all its copies, and the main Container's
- * list. Sets up the next presentation as current.
- */
- @action
- removePresentation = async () => {
- if (this.presentationsMapping.size !== 1) {
- let presentationList = Cast(this.props.Documents, listSpec(Doc));
- let batch = UndoManager.StartBatch("presRemoval");
-
- //getting the presentation that will be removed
- let removedDoc = this.presentationsMapping.get(this.currentSelectedPresValue!);
- //that presentation is removed
- presentationList!.splice(presentationList!.indexOf(removedDoc!), 1);
-
- //its mappings are removed from local copies
- this.presentationsKeyMapping.delete(removedDoc!);
- this.presentationsMapping.delete(this.currentSelectedPresValue!);
-
- //the next presentation is set as current
- let remainingPresentations = this.presentationsMapping.values();
- let nextDoc = remainingPresentations.next().value;
- this.curPresentation = nextDoc;
-
-
- //Storing these for being able to undo changes
- let curGuid = this.currentSelectedPresValue!;
- let curPresStatus = this.presStatus;
-
- //resetting the groups and presentation actions so that next presentation gets loaded
- this.resetGroupIds();
- this.resetPresentation();
- this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString();
- this.setPresentationBackUps();
-
- //Storing for undo
- let currentGroups = this.groupMappings;
- let curPresElemMapping = this.presElementsMappings;
-
- //Event to undo actions that are not related to doc directly, aka. local things
- UndoManager.AddEvent({
- undo: action(() => {
- this.curPresentation = removedDoc!;
- this.presentationsMapping.set(curGuid, removedDoc!);
- this.presentationsKeyMapping.set(removedDoc!, curGuid);
- this.currentSelectedPresValue = curGuid;
-
- this.presStatus = curPresStatus;
- this.groupMappings = currentGroups;
- this.presElementsMappings = curPresElemMapping;
- this.setPresentationBackUps();
-
- }),
- redo: action(() => {
- this.curPresentation = nextDoc;
- this.presStatus = false;
- this.presentationsKeyMapping.delete(removedDoc!);
- this.presentationsMapping.delete(curGuid);
- this.currentSelectedPresValue = this.presentationsKeyMapping.get(nextDoc)!.toString();
- this.setPresentationBackUps();
-
- }),
- });
-
- batch.end();
- }
- }
-
- /**
- * The function that is called to change title of presentation to what user entered.
- */
- @undoBatch
- changePresentationTitle = (newTitle: string) => {
- if (newTitle === "") {
- return;
- }
- this.curPresentation.title = newTitle;
- }
-
- /**
- * On pointer down element that is catched on resizer of te
- * presentation view. Sets up the event listeners to change the size with
- * mouse move.
- */
- _downsize = 0;
- onPointerDown = (e: React.PointerEvent) => {
- this._downsize = e.clientX;
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
- e.preventDefault();
- }
- /**
- * Changes the size of the presentation view, with mouse move.
- * Minimum size is set to 300, so that every button is visible.
- */
- @action
- onPointerMove = (e: PointerEvent) => {
-
- this.curPresentation.width = Math.max(window.innerWidth - e.clientX, presMinWidth);
- }
-
- /**
- * The method that is called on pointer up event. It checks if the button is just
- * clicked so that presentation view will be closed. The way it's done is to check
- * for minimal pixel change like 4, and accept it as it's just a click on top of the dragger.
- */
- @action
- onPointerUp = (e: PointerEvent) => {
- if (Math.abs(e.clientX - this._downsize) < 4) {
- let presWidth = NumCast(this.curPresentation.width);
- if (presWidth - presMinWidth !== 0) {
- this.curPresentation.width = 0;
- }
- if (presWidth === 0) {
- this.curPresentation.width = presMinWidth;
- }
- }
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
-
- /**
- * This function is a setter that opens up the
- * presentation mode, by setting it's render flag
- * to true. It also closes the presentation view.
- */
- @action
- openPresMode = () => {
- if (!this.presMode) {
- this.curPresentation.width = 0;
- this.presMode = true;
- }
- }
-
- /**
- * This function closes the presentation mode by setting its
- * render flag to false. It also opens up the presentation view.
- * By setting it to it's minimum size.
- */
- @action
- closePresMode = () => {
- if (this.presMode) {
- this.presMode = false;
- this.curPresentation.width = presMinWidth;
- }
-
- }
-
- /**
- * Function that is called to render the presentation mode, depending on its flag.
- */
- renderPresMode = () => {
- if (this.presMode) {
- return <PresModeMenu next={this.next} back={this.back} startOrResetPres={this.startOrResetPres} presStatus={this.presStatus} closePresMode={this.closePresMode} />;
- } else {
- return (null);
- }
-
- }
-
- render() {
-
- let width = NumCast(this.curPresentation.width);
-
- return (
- <div>
- <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflowY: "scroll", overflowX: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}>
- <div className="presentationView-heading">
- {this.renderSelectOrPresSelection()}
- <button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button>
- <button title="Open Presentation Mode" className="presentation-icon" style={{ marginRight: 10 }} onClick={this.openPresMode}><FontAwesomeIcon icon={"eye"} /></button>
- <button title="Add Presentation" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
- runInAction(() => { if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; } });
- runInAction(() => this.PresTitleInputOpen ? this.PresTitleInputOpen = false : this.PresTitleInputOpen = true);
- }}><FontAwesomeIcon icon={"plus"} /></button>
- <button title="Remove Presentation" className='presentation-icon' style={{ marginRight: 10 }} onClick={this.removePresentation}><FontAwesomeIcon icon={"minus"} /></button>
- <button title="Change Presentation Title" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => {
- runInAction(() => { if (this.PresTitleInputOpen) { this.PresTitleInputOpen = false; } });
- runInAction(() => this.PresTitleChangeOpen ? this.PresTitleChangeOpen = false : this.PresTitleChangeOpen = true);
- }}><FontAwesomeIcon icon={"edit"} /></button>
- </div>
- <div className="presentation-buttons">
- <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
- {this.renderPlayPauseButton()}
- <button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
- </div>
-
- <PresentationViewList
- mainDocument={this.curPresentation}
- deleteDocument={this.RemoveDoc}
- gotoDocument={this.gotoDocument}
- groupMappings={this.groupMappings}
- PresElementsMappings={this.presElementsMappings}
- setChildrenDocs={this.setChildrenDocs}
- presStatus={this.presStatus}
- presButtonBackUp={this.presButtonBackUp}
- presGroupBackUp={this.presGroupBackUp}
- removeDocByRef={this.removeDocByRef}
- clearElemMap={() => this.presElementsMappings.clear()}
- />
- <input
- type="checkbox"
- onChange={action((e: React.ChangeEvent<HTMLInputElement>) => {
- this.persistOpacity = e.target.checked;
- this.opacity = this.persistOpacity ? 1 : 0.4;
- })}
- checked={this.persistOpacity}
- style={{ position: "absolute", bottom: 5, left: 5 }}
- onPointerEnter={action(() => this.labelOpacity = 1)}
- onPointerLeave={action(() => this.labelOpacity = 0)}
- />
- <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p>
- </div>
- <div className="mainView-libraryHandle"
- style={{ cursor: "ew-resize", right: `${width - 10}px`, backgroundColor: "white", opacity: this.opacity, transition: "0.7s opacity ease" }}
- onPointerDown={this.onPointerDown}>
- <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} />
- </div>
- {this.renderPresMode()}
-
- </div>
- );
- }
-}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 3e8582d61..c13d1d276 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -6,7 +6,7 @@ import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-s
import { library } from '@fortawesome/fontawesome-svg-core';
import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { DocumentType } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
import { Cast, StrCast } from '../../../new_fields/Types';
import * as _ from "lodash";
import { IconBar } from './IconBar';
diff --git a/src/client/views/search/IconBar.tsx b/src/client/views/search/IconBar.tsx
index 4712b0abc..c9924222f 100644
--- a/src/client/views/search/IconBar.tsx
+++ b/src/client/views/search/IconBar.tsx
@@ -4,7 +4,6 @@ import { observable, action } from 'mobx';
// import "./SearchBox.scss";
import "./IconBar.scss";
import "./IconButton.scss";
-import { DocumentType } from '../../documents/Documents';
import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faTimesCircle, faCheckCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library } from '@fortawesome/fontawesome-svg-core';
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index 5d23f6eeb..d2cfe7fad 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -6,7 +6,7 @@ import "./IconButton.scss";
import { faSearch, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote, faMusic, faLink, faChartBar, faGlobeAsia, faBan, faVideo, faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { library, icon } from '@fortawesome/fontawesome-svg-core';
-import { DocumentType } from '../../documents/Documents';
+import { DocumentType } from "../../documents/DocumentTypes";
import '../globalCssVariables.scss';
import * as _ from "lodash";
import { IconBar } from './IconBar';
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 4dc409e77..0d50124dd 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -1,25 +1,23 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { observable, action, runInAction, flow, computed } from 'mobx';
-import "./SearchBox.scss";
-import "./FilterBox.scss";
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core';
-import { SetupDrag } from '../../util/DragManager';
-import { Docs } from '../../documents/Documents';
-import { NumCast, Cast } from '../../../new_fields/Types';
-import { Doc } from '../../../new_fields/Doc';
-import { SearchItem } from './SearchItem';
+import { faTimes } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import * as rp from 'request-promise';
+import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { SearchUtil } from '../../util/SearchUtil';
+import { Cast, NumCast } from '../../../new_fields/Types';
import { RouteStore } from '../../../server/RouteStore';
-import { FilterBox } from './FilterBox';
-import { ReadStream } from 'fs';
-import * as $ from 'jquery';
-import { MainView } from '../MainView';
import { Utils } from '../../../Utils';
+import { Docs } from '../../documents/Documents';
+import { SetupDrag } from '../../util/DragManager';
+import { SearchUtil } from '../../util/SearchUtil';
+import { MainView } from '../MainView';
+import { FilterBox } from './FilterBox';
+import "./FilterBox.scss";
+import "./SearchBox.scss";
+import { SearchItem } from './SearchItem';
library.add(faTimes);
@@ -141,7 +139,7 @@ export class SearchBox extends React.Component {
private get filterQuery() {
const types = FilterBox.Instance.filterTypes;
const includeDeleted = FilterBox.Instance.getDataStatus();
- return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
+ return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})` : "");
}
@@ -222,7 +220,6 @@ export class SearchBox extends React.Component {
doc.width = size;
doc.height = size;
}
- doc.zoomBasis = 1;
x += 250;
if (x > 1000) {
x = 0;
@@ -245,7 +242,6 @@ export class SearchBox extends React.Component {
@action.bound
closeSearch = () => {
- console.log("closing search")
FilterBox.Instance.closeFilter();
this.closeResults();
this._searchbarOpen = false;
@@ -305,14 +301,16 @@ export class SearchBox extends React.Component {
this.getResults(this._searchString);
if (i < this._results.length) result = this._results[i];
if (result) {
- this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />;
+ let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string");
+ this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} highlighting={highlights} />;
this._isSearch[i] = "search";
}
}
else {
result = this._results[i];
if (result) {
- this._visibleElements[i] = <SearchItem doc={result[0]} key={result[0][Id]} highlighting={result[1]} />;
+ let highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string");
+ this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} highlighting={highlights} />;
this._isSearch[i] = "search";
}
}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 8201aa374..cd3dd912c 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -11,7 +11,7 @@ import { RichTextField } from "../../../new_fields/RichTextField";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { DocumentType } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, SetupDrag } from "../../util/DragManager";
import { LinkManager } from "../../util/LinkManager";
@@ -28,7 +28,7 @@ import "./SelectorContextMenu.scss";
export interface SearchItemProps {
doc: Doc;
- query?: string;
+ query: string;
highlighting: string[];
}
@@ -63,6 +63,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
runInAction(() => {
this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc }));
this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+
});
}
@@ -70,8 +71,8 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
return () => {
col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
- const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2;
- const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2;
+ const newPanX = NumCast(target.x) + NumCast(target.width) / 2;
+ const newPanY = NumCast(target.y) + NumCast(target.height) / 2;
col.panX = newPanX;
col.panY = newPanY;
}
@@ -127,68 +128,31 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> {
export class SearchItem extends React.Component<SearchItemProps> {
@observable _selected: boolean = false;
- private _previewDoc?: Doc;
onClick = () => {
// I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like
DocumentManager.Instance.jumpToDocument(this.props.doc, false);
- if (this.props.doc.data instanceof RichTextField) {
- this.highlightTextBox(this.props.doc);
- }
- // CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
}
@observable _useIcons = true;
@observable _displayDim = 50;
- highlightTextBox = (doc: Doc) => {
- if (this.props.query) {
- const fieldkey = 'search_string';
- if (Object.keys(doc).indexOf(fieldkey) === -1) {
- doc.search_string = this.props.query;
- }
- else {
- doc.search_string = undefined;
- }
-
- }
+ componentDidMount() {
+ this.props.doc.search_string = this.props.query;
+ this.props.doc.search_fields = this.props.highlighting.join(", ");
}
-
- fitToBox = () => {
- let bounds = Doc.ComputeContentBounds([this.props.doc]);
- return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim];
- }
-
componentWillUnmount() {
- if (this._previewDoc) {
- DocServer.DeleteDocument(this._previewDoc[Id]);
- }
+ this.props.doc.search_string = undefined;
+ this.props.doc.search_fields = undefined;
}
-
//@computed
@action
public DocumentIcon() {
let layoutresult = StrCast(this.props.doc.type);
if (!this._useIcons) {
- let renderDoc = this.props.doc;
- //let box: number[] = [];
- if (layoutresult.indexOf(DocumentType.COL) !== -1) {
- renderDoc = Doc.MakeDelegate(renderDoc);
- let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => {
- var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
- let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
- return {
- x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
- r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
- };
- }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
- let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim];
- }
let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
let returnYDimension = () => this._displayDim;
- let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
- let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
- this._previewDoc = newRenderDoc;
+ let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension());
const docview = <div
onPointerDown={action(() => {
this._useIcons = !this._useIcons;
@@ -201,14 +165,15 @@ export class SearchItem extends React.Component<SearchItemProps> {
Document={this.props.doc}
addDocument={returnFalse}
removeDocument={returnFalse}
+ ruleProvider={undefined}
ScreenToLocalTransform={Transform.Identity}
addDocTab={returnFalse}
+ pinToPres={returnFalse}
renderDepth={1}
PanelWidth={returnXDimension}
PanelHeight={returnYDimension}
focus={emptyFunction}
backgroundColor={returnEmptyString}
- selectOnLoad={false}
parentActive={returnFalse}
whenActiveChanged={returnFalse}
bringToFront={emptyFunction}
@@ -218,15 +183,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
ContentScaling={scale}
/>
</div>;
- const data = renderDoc.data;
- if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
- newRenderDoc.preview = true;
- newRenderDoc.search_string = this.props.query;
return docview;
}
- if (this._previewDoc) {
- DocServer.DeleteDocument(this._previewDoc[Id]);
- }
let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
@@ -278,8 +236,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
Doc.BrushDoc(doc2);
}
} else {
- DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element =>
- Doc.BrushDoc(element.props.Document));
+ Doc.BrushDoc(this.props.doc);
}
}
@@ -293,8 +250,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
Doc.UnBrushDoc(doc2);
}
} else {
- DocumentManager.Instance.getAllDocumentViews(this.props.doc).
- forEach(element => Doc.UnBrushDoc(element.props.Document));
+ Doc.UnBrushDoc(this.props.doc);
}
}