aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts15
-rw-r--r--src/client/documents/Documents.ts3
-rw-r--r--src/client/goldenLayout.js2
-rw-r--r--src/client/util/DocumentManager.ts9
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts48
-rw-r--r--src/client/util/RichTextSchema.tsx9
-rw-r--r--src/client/util/SelectionManager.ts16
-rw-r--r--src/client/util/TooltipTextMenu.tsx42
-rw-r--r--src/client/util/prosemirrorPatches.js2
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx6
-rw-r--r--src/client/views/collections/CollectionView.tsx9
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx119
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx589
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx5
-rw-r--r--src/client/views/linking/LinkFollowBox.tsx10
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx4
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx24
-rw-r--r--src/client/views/nodes/DocumentView.tsx2
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss20
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx75
-rw-r--r--src/client/views/nodes/PDFBox.tsx30
-rw-r--r--src/client/views/nodes/PresBox.tsx6
-rw-r--r--src/client/views/pdf/PDFViewer.scss27
-rw-r--r--src/client/views/pdf/PDFViewer.tsx58
-rw-r--r--src/client/views/presentationview/PresentationModeMenu.tsx13
-rw-r--r--src/new_fields/Doc.ts13
27 files changed, 559 insertions, 600 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 65eb3cffd..4fac53c7d 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -235,6 +235,21 @@ export function timenow() {
return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm;
}
+export function aggregateBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
+ return boundsList.reduce((bounds, b) => {
+ var [sptX, sptY] = [b.x, b.y];
+ let [bptX, bptY] = [sptX + b.width, sptY + b.height];
+ 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.MAX_VALUE, b: -Number.MAX_VALUE });
+}
+export function intersectRect(r1: { left: number, top: number, width: number, height: number },
+ r2: { left: number, top: number, width: number, height: number }) {
+ return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+}
+
export function percent2frac(percent: string) {
return Number(percent.substr(0, percent.length - 1)) / 100;
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2fa0d2dcb..4ae770e25 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -638,7 +638,6 @@ export namespace DocUtils {
});
}
export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc, id?: string, anchored1?: boolean) {
- if (LinkManager.Instance.doesLinkExist(source, target)) return undefined;
let sv = DocumentManager.Instance.getDocumentView(source);
if (sv && sv.props.ContainingCollectionDoc === target) return;
if (target === CurrentUserUtils.UserDocument) return undefined;
@@ -651,7 +650,6 @@ export namespace DocUtils {
linkDocProto.sourceContext = sourceContext;
linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
linkDocProto.linkDescription = description;
- linkDocProto.type = DocumentType.LINK;
linkDocProto.anchor1 = source;
linkDocProto.anchor1Page = source.curPage;
@@ -665,6 +663,7 @@ export namespace DocUtils {
Doc.GetProto(source).links = ComputedField.MakeFunction("links(this)");
Doc.GetProto(target).links = ComputedField.MakeFunction("links(this)");
+
}, "make link");
return linkDocProto;
}
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index ad78139c1..29b750720 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -377,7 +377,7 @@
this._nOriginalY = coordinates.y;
this._oDocument.on('mousemove touchmove', this._fMove);
- this._oDocument.one('mouseup touchend', this._fUp);
+ this._oDocument.on('mouseup touchend', this._fUp);
this._timeout = setTimeout(lm.utils.fnBind(this._startDrag, this), this._nDelay);
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index eaed82918..e60ab09bb 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -186,13 +186,8 @@ export class DocumentManager {
@action
zoomIntoScale = (docDelegate: Doc, scale: number) => {
- let doc = Doc.GetProto(docDelegate);
-
- let docView: DocumentView | null;
- docView = DocumentManager.Instance.getDocumentView(doc);
- if (docView) {
- docView.props.zoomToScale(scale);
- }
+ let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
+ docView && docView.props.zoomToScale(scale);
}
getScaleOfDocView = (docDelegate: Doc) => {
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 3e3d3155c..aab437176 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -11,6 +11,20 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
export type KeyMap = { [key: string]: any };
+export let updateBullets = (tx2: Transaction, schema: Schema) => {
+ let fontSize: number | undefined = undefined;
+ tx2.doc.descendants((node: any, offset: any, index: any) => {
+ if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
+ let path = (tx2.doc.resolve(offset) as any).path;
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ if (node.type === schema.nodes.ordered_list) depth++;
+ fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
+ let fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
+ tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
+ }
+ });
+ return tx2;
+};
export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
let keys: { [key: string]: any } = {}, type;
@@ -93,16 +107,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
bind("Mod-s", TooltipTextMenu.insertStar);
- let updateBullets = (tx2: Transaction) => {
- tx2.doc.descendants((node: any, offset: any, index: any) => {
- if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
- let path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
- if (node.type === schema.nodes.ordered_list) depth++;
- tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks);
- }
- });
- };
bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
@@ -110,18 +114,18 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
var range = ref.$from.blockRange(ref.$to);
var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
- updateBullets(tx2);
- marks && tx2.ensureMarks([...marks]);
- marks && tx2.setStoredMarks([...marks]);
- dispatch(tx2);
+ let tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
})) { // couldn't sink into an existing list, so wrap in a new one
let newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
- updateBullets(tx2);
+ let tx3 = updateBullets(tx2, schema);
// when promoting to a list, assume list will format things so don't copy the stored marks.
- marks && tx2.ensureMarks([...marks]);
- marks && tx2.setStoredMarks([...marks]);
- dispatch(tx2);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
})) {
console.log("bullet promote fail");
}
@@ -132,10 +136,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
- updateBullets(tx2);
- marks && tx2.ensureMarks([...marks]);
- marks && tx2.setStoredMarks([...marks]);
- dispatch(tx2);
+ let tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
})) {
console.log("bullet demote fail");
}
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index ba4b92a25..710d55605 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -198,6 +198,8 @@ export const nodes: { [index: string]: NodeSpec } = {
attrs: {
bulletStyle: { default: 0 },
mapStyle: { default: "decimal" },
+ setFontSize: { default: undefined },
+ inheritedFontSize: { default: undefined },
visibility: { default: true }
},
toDOM(node: Node<any>) {
@@ -205,8 +207,9 @@ export const nodes: { [index: string]: NodeSpec } = {
const decMap = bs ? "decimal" + bs : "";
const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
- return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] :
- ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+ let fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
+ return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;font-size: ${fsize}` }, 0] :
+ ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}` }];
}
},
@@ -253,7 +256,7 @@ export const marks: { [index: string]: MarkSpec } = {
href: {},
location: { default: null },
title: { default: null },
- docref: { default: false }
+ docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
},
inclusive: false,
parseDOM: [{
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 4c97a1056..a02a270ee 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -88,20 +88,4 @@ export namespace SelectionManager {
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments.slice();
}
- export function ViewsSortedHorizontally(): DocumentView[] {
- let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
- if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
- if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
- return 0;
- });
- return sorted;
- }
- export function ViewsSortedVertically(): DocumentView[] {
- let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
- if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
- if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
- return 0;
- });
- return sorted;
- }
}
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index b6de048e4..a83a3949d 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -19,6 +19,7 @@ import { LinkManager } from "./LinkManager";
import { schema } from "./RichTextSchema";
import "./TooltipTextMenu.scss";
import { Cast, NumCast } from '../../new_fields/Types';
+import { updateBullets } from './ProsemirrorExampleTransfer';
const { toggleMark, setBlockType } = require("prosemirror-commands");
const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
@@ -302,12 +303,17 @@ export class TooltipTextMenu {
{
handlers: {
dragComplete: action(() => {
- let linkDoc = dragData.linkDocument;
- let proto = Doc.GetProto(linkDoc);
- if (proto && docView) {
- proto.sourceContext = docView.props.ContainingCollectionDoc;
+ if (dragData.linkDocument) {
+ let linkDoc = dragData.linkDocument;
+ let proto = Doc.GetProto(linkDoc);
+ if (proto && docView) {
+ proto.sourceContext = docView.props.ContainingCollectionDoc;
+ }
+ let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab");
+ if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) {
+ proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link
+ }
}
- linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
}),
},
hideSource: false
@@ -389,17 +395,24 @@ export class TooltipTextMenu {
}
}
- makeLinkWithState = (state: EditorState, target: string, location: string) => {
- let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
- }
+ // makeLinkWithState = (state: EditorState, target: string, location: string) => {
+ // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
+ // }
- makeLink = (target: string, location: string) => {
+ makeLink = (targetDoc: Doc, location: string): string => {
+ let target = Utils.prepend("/doc/" + targetDoc[Id]);
let node = this.view.state.selection.$from.nodeAfter;
- let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
+ let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] });
this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
node = this.view.state.selection.$from.nodeAfter;
link = node && node.marks.find(m => m.type.name === "link");
+ if (node) {
+ if (node.text) {
+ return node.text;
+ }
+ }
+ return "";
}
deleteLink = () => {
@@ -506,10 +519,11 @@ export class TooltipTextMenu {
}
}
//actually apply font
- return toggleMark(markType)(view.state, view.dispatch, view);
- }
- else {
- return;
+ if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
+ view.dispatch(updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
+ { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema));
+ }
+ else toggleMark(markType)(view.state, view.dispatch, view);
}
}
diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js
index 188e3e1c5..269423482 100644
--- a/src/client/util/prosemirrorPatches.js
+++ b/src/client/util/prosemirrorPatches.js
@@ -82,7 +82,7 @@ function sinkListItem(itemType) {
if (dispatch) {
var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null);
- let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create(parent.attrs, inner)))),
+ let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))),
nestedBefore ? 3 : 1, 0);
var before = range.start, after = range.end;
dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 56d12bd84..0168c466f 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -20,7 +20,8 @@ export enum CollectionViewType {
Docking,
Tree,
Stacking,
- Masonry
+ Masonry,
+ Pivot,
}
export namespace CollectionViewType {
@@ -32,7 +33,8 @@ export namespace CollectionViewType {
["docking", CollectionViewType.Docking],
["tree", CollectionViewType.Tree],
["stacking", CollectionViewType.Stacking],
- ["masonry", CollectionViewType.Masonry]
+ ["masonry", CollectionViewType.Masonry],
+ ["pivot", CollectionViewType.Pivot]
]);
export const valueOf = (value: string) => {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5f4742834..d3072ff1e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -18,6 +18,7 @@ import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionStackingView } from './CollectionStackingView';
import { CollectionTreeView } from "./CollectionTreeView";
import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
export const COLLECTION_BORDER_WIDTH = 2;
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
@@ -59,8 +60,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
case CollectionViewType.Tree: return (<CollectionTreeView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
+ case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />); }
case CollectionViewType.Freeform:
default:
+ this.props.Document.freeformLayoutEngine = undefined;
return (<CollectionFreeFormView chromeCollapsed={this._collapsed} key="collview" {...props} CollectionView={this} />);
}
return (null);
@@ -89,7 +92,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
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 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" });
+ subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" });
if (CollectionBaseView.InSafeMode()) {
ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" });
}
@@ -103,10 +106,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
}, icon: "ellipsis-v"
});
subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
+ subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
switch (this.props.Document.viewType) {
case CollectionViewType.Freeform: {
- subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) });
- subItems.push({ description: "Pivot", icon: "copy", event: () => this.props.Document.usePivotLayout = true });
+ subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
break;
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 7510b86a0..47b300efc 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -262,7 +262,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@observable private pivotKeyDisplay = this.pivotKey;
getPivotInput = () => {
- if (!this.document.usePivotLayout) {
+ if (StrCast(this.document.freeformLayoutEngine) !== "pivot") {
return (null);
}
return (<input className="collectionViewBaseChrome-viewSpecsInput"
@@ -398,6 +398,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
+ <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="7">Pivot View</option>
</select>
<div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
<input className="collectionViewBaseChrome-viewSpecsInput"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
new file mode 100644
index 000000000..21855b168
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -0,0 +1,119 @@
+import { Doc, Field, FieldResult } from "../../../../new_fields/Doc";
+import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
+import { ScriptBox } from "../../ScriptBox";
+import { CompileScript } from "../../../util/Scripting";
+import { ScriptField } from "../../../../new_fields/ScriptField";
+import { OverlayView, OverlayElementOptions } from "../../OverlayView";
+import { emptyFunction } from "../../../../Utils";
+import React = require("react");
+
+interface PivotData {
+ type: string;
+ text: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ fontSize: number;
+}
+
+export interface ViewDefBounds {
+ x: number;
+ y: number;
+ z?: number;
+ width: number;
+ height: number;
+ transition?: string;
+}
+
+export interface ViewDefResult {
+ ele: JSX.Element;
+ bounds?: ViewDefBounds;
+}
+
+export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
+ let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
+ const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
+ const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
+
+ for (const doc of childDocs) {
+ const val = doc[StrCast(pivotDoc.pivotField, "title")];
+ if (val) {
+ !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []);
+ pivotColumnGroups.get(val)!.push(doc);
+ }
+ }
+
+ const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity);
+ const numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
+ const docMap = new Map<Doc, ViewDefBounds>();
+ const groupNames: PivotData[] = [];
+
+ let x = 0;
+ pivotColumnGroups.forEach((val, key) => {
+ let y = 0;
+ let xCount = 0;
+ groupNames.push({
+ type: "text",
+ text: String(key),
+ x,
+ y: pivotAxisWidth + 50,
+ width: pivotAxisWidth * 1.25 * numCols,
+ height: 100,
+ fontSize: NumCast(pivotDoc.pivotFontSize, 10)
+ });
+ for (const doc of val) {
+ docMap.set(doc, {
+ x: x + xCount * pivotAxisWidth * 1.25,
+ y: -y,
+ width: pivotAxisWidth,
+ height: doc.nativeWidth ? (NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth
+ });
+ xCount++;
+ if (xCount >= numCols) {
+ xCount = 0;
+ y += pivotAxisWidth * 1.25;
+ }
+ }
+ x += pivotAxisWidth * 1.25 * (numCols + 1);
+ });
+
+ childPairs.map(pair => {
+ let defaultPosition = {
+ x: NumCast(pair.layout.x),
+ y: NumCast(pair.layout.y),
+ z: NumCast(pair.layout.z),
+ width: NumCast(pair.layout.width),
+ height: NumCast(pair.layout.height)
+ };
+ const pos = docMap.get(pair.layout) || defaultPosition;
+ layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
+ });
+ return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) };
+};
+
+
+
+export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
+ return () => {
+ let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+ let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
+ const scriptField = Cast(doc[key], ScriptField);
+ // tslint:disable-next-line: no-unnecessary-callback-wrapper
+ let scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
+ onCancel={() => overlayDisposer()} // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
+ onSave={(text, onError) => {
+ const script = CompileScript(text, { params, requiredType, typecheck: false });
+ if (!script.compiled) {
+ onError(script.errors.map(error => error.messageText).join("\n"));
+ } else {
+ doc[key] = new ScriptField(script);
+ overlayDisposer();
+ }
+ }} />;
+ overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
+ };
+ addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
+ addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+ };
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 36e62842c..af84a1d73 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,22 +1,23 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
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 { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } 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, PromiseValue, DateCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
+import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import { aggregateBounds, emptyFunction, intersectRect, 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";
-import { CompileScript } from "../../../util/Scripting";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
@@ -26,20 +27,17 @@ import { ContextMenuProps } from "../../ContextMenuItem";
import { InkingCanvas } from "../../InkingCanvas";
import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
-import { DocumentViewProps, documentSchema } from "../../nodes/DocumentView";
+import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView";
+import { FormattedTextBox } from "../../nodes/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
-import { OverlayElementOptions, OverlayView } from "../../OverlayView";
import PDFMenu from "../../pdf/PDFMenu";
-import { ScriptBox } from "../../ScriptBox";
import { CollectionSubView } from "../CollectionSubView";
+import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-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);
@@ -51,132 +49,10 @@ export const panZoomSchema = createSchema({
arrangeInit: ScriptField,
useClusters: "boolean",
isRuleProvider: "boolean",
- fitToBox: "boolean"
+ fitToBox: "boolean",
+ panTransformType: "string"
});
-export interface ViewDefBounds {
- x: number;
- y: number;
- z?: number;
- width: number;
- height: number;
-}
-
-export interface ViewDefResult {
- ele: JSX.Element;
- bounds?: ViewDefBounds;
-}
-
-export namespace PivotView {
-
- export interface PivotData {
- type: string;
- text: string;
- x: number;
- y: number;
- width: number;
- height: number;
- fontSize: number;
- }
-
- export const elements = (target: CollectionFreeFormView) => {
- let collection = target.Document;
- const field = StrCast(collection.pivotField) || "title";
- const width = NumCast(collection.pivotWidth) || 200;
- const groups = new Map<FieldResult<Field>, Doc[]>();
-
- for (const doc of target.childDocs) {
- const val = doc[field];
- if (val === undefined) continue;
-
- const l = groups.get(val);
- if (l) {
- l.push(doc);
- } else {
- groups.set(val, [doc]);
- }
- }
-
- let minSize = Infinity;
-
- groups.forEach((val, key) => minSize = Math.min(minSize, val.length));
-
- const numCols = NumCast(collection.pivotNumColumns) || Math.ceil(Math.sqrt(minSize));
- const fontSize = NumCast(collection.pivotFontSize);
-
- const docMap = new Map<Doc, ViewDefBounds>();
- const groupNames: PivotData[] = [];
-
- let x = 0;
- groups.forEach((val, key) => {
- let y = 0;
- let xCount = 0;
- groupNames.push({
- type: "text",
- text: String(key),
- x,
- y: width + 50,
- width: width * 1.25 * numCols,
- height: 100, fontSize: fontSize
- });
- for (const doc of val) {
- docMap.set(doc, {
- x: x + xCount * width * 1.25,
- y: -y,
- width,
- height: width
- });
- xCount++;
- if (xCount >= numCols) {
- xCount = 0;
- y += width * 1.25;
- }
- }
- x += width * 1.25 * (numCols + 1);
- });
-
- let elements = target.viewDefsToJSX(groupNames);
- let docViews = target.childDocs.reduce((prev, doc) => {
- let minim = BoolCast(doc.isMinimized);
- if (minim === undefined || !minim) {
- let defaultPosition = (): ViewDefBounds => {
- return {
- x: NumCast(doc.x),
- y: NumCast(doc.y),
- z: NumCast(doc.z),
- width: NumCast(doc.width),
- height: NumCast(doc.height)
- };
- };
- const pos = docMap.get(doc) || defaultPosition();
- prev.push({
- ele: <CollectionFreeFormDocumentView
- key={doc[Id]}
- x={pos.x}
- y={pos.y}
- width={pos.width}
- height={pos.height}
- transition={"transform 1s"}
- jitterRotation={NumCast(target.props.Document.jitterRotation)}
- {...target.getChildDocumentViewProps(doc)}
- />,
- bounds: {
- x: pos.x,
- y: pos.y,
- z: pos.z,
- width: NumCast(pos.width),
- height: NumCast(pos.height)
- }
- });
- }
- return prev;
- }, elements);
-
- return docViews;
- };
-
-}
-
type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
@@ -184,49 +60,25 @@ const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSch
export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _lastX: number = 0;
private _lastY: number = 0;
- private get _pwidth() { return this.props.PanelWidth(); }
- private get _pheight() { return this.props.PanelHeight(); }
- private get parentScaling() {
- return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1;
- }
-
- ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) {
- let bounds = boundsList.reduce((bounds, b) => {
- var [sptX, sptY] = [b.x, b.y];
- let [bptX, bptY] = [sptX + NumCast(b.width, 1), sptY + NumCast(b.height, 1)];
- 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.MAX_VALUE, b: -Number.MAX_VALUE });
- 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.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,
- scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.parentScaling
- };
- if (res.scale === 0) res.scale = 1;
- return res;
- }
-
- @computed get fitToBox() { return this.props.fitToBox || this.Document.fitToBox; }
- @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; }
- @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; }
- public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
+ private _clusterDistance: number = 75;
+ @observable _clusterSets: (Doc[])[] = [];
+
+ @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; }
+ @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
+ @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
+ @computed get nativeWidth() { return this.fitToContent ? 0 : this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
+ private get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt')
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
- private panX = () => this.contentBounds.panX;
- private panY = () => this.contentBounds.panY;
- private zoomScaling = () => this.contentBounds.scale;
- private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
- private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
+ private easing = () => this.props.Document.panTransformType === "Ease";
+ private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0;
+ private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document.panY || 0;
+ private zoomScaling = () => (this.fitToContent ?
+ Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
+ this.Document.scale || 1)
+ / this.parentScaling;
+ private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
+ private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
@@ -244,47 +96,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.addDocument(newBox, false);
}
private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
- this.props.addDocument(newBox, false);
- this.bringToFront(newBox);
- this.updateCluster(newBox);
- return true;
+ let added = this.props.addDocument(newBox, false);
+ added && this.bringToFront(newBox);
+ added && this.updateCluster(newBox);
+ return added;
}
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
- SelectionManager.SelectDoc(dv!, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
+ public isCurrent(doc: Doc) { return !this.props.Document.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); }
+
public getActiveDocuments = () => {
- const curPage = FieldValue(this.Document.curPage, -1);
- return this.childLayoutPairs.filter(pair => {
- var page = NumCast(pair.layout!.page, -1);
- return page === curPage || page === -1;
- }).map(pair => pair.layout);
+ return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
}
@computed get fieldExtensionDoc() {
return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
}
- intersectRect(r1: { left: number, top: number, width: number, height: number },
- r2: { left: number, top: number, width: number, height: number }) {
- return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
- }
- _clusterDistance = 75;
- boundsOverlap(doc: Doc, doc2: Doc) {
- var x2 = NumCast(doc2.x) - this._clusterDistance;
- var y2 = NumCast(doc2.y) - this._clusterDistance;
- var w2 = NumCast(doc2.width) + this._clusterDistance;
- var h2 = NumCast(doc2.height) + this._clusterDistance;
- var x = NumCast(doc.x) - this._clusterDistance;
- var y = NumCast(doc.y) - this._clusterDistance;
- var w = NumCast(doc.width) + this._clusterDistance;
- var h = NumCast(doc.height) + this._clusterDistance;
- if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
- return true;
- }
- return false;
+ @action
+ onDrop = (e: React.DragEvent): void => {
+ var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
+ super.onDrop(e, { x: pt[0], y: pt[1] });
}
+
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
@@ -300,7 +136,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let y = (z ? ypo : yp) - de.data.offset[1];
let dropX = NumCast(de.data.droppedDocuments[0].x);
let dropY = NumCast(de.data.droppedDocuments[0].y);
- de.data.droppedDocuments.forEach(d => {
+ de.data.droppedDocuments.forEach(action((d: Doc) => {
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
if (!NumCast(d.width)) {
@@ -312,7 +148,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
}
this.bringToFront(d);
- });
+ }));
de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]);
}
@@ -335,18 +171,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return false;
}
- tryDragCluster(e: PointerEvent) {
- let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
- let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
+ pickCluster(probe: number[]) {
+ return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => {
let cx = NumCast(cd.x) - this._clusterDistance;
let cy = NumCast(cd.y) - this._clusterDistance;
let cw = NumCast(cd.width) + 2 * this._clusterDistance;
let ch = NumCast(cd.height) + 2 * this._clusterDistance;
- if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) {
- return NumCast(cd.cluster);
- }
- return cluster;
+ return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ?
+ NumCast(cd.cluster) : cluster;
}, -1);
+ }
+ tryDragCluster(e: PointerEvent) {
+ let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
if (cluster !== -1) {
let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster);
@@ -371,36 +207,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return false;
}
- @observable sets: (Doc[])[] = [];
@undoBatch
- @action
updateClusters(useClusters: boolean) {
- this.Document.useClusters = useClusters;
- this.sets.length = 0;
- this.childLayoutPairs.map(pair => pair.layout).map(c => {
- let included = [];
- for (let i = 0; i < this.sets.length; i++) {
- for (let member of this.sets[i]) {
- if (this.boundsOverlap(c, member)) {
- included.push(i);
- break;
- }
- }
- }
- if (included.length === 0) {
- this.sets.push([c]);
- } else if (included.length === 1) {
- this.sets[included[0]].push(c);
- } else {
- this.sets[included[0]].push(c);
- for (let s = 1; s < included.length; s++) {
- this.sets[included[0]].push(...this.sets[included[s]]);
- this.sets[included[s]].length = 0;
- }
- }
- });
- this.sets.map((set, i) => set.map(member => member.cluster = i));
+ this.props.Document.useClusters = useClusters;
+ this._clusterSets.length = 0;
+ this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
@undoBatch
@@ -408,28 +220,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
updateCluster(doc: Doc) {
let childLayouts = this.childLayoutPairs.map(pair => pair.layout);
if (this.props.Document.useClusters) {
- this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
+ this._clusterSets.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, childLayouts) !== -1 && this.boundsOverlap(doc, member)) {
+ this._clusterSets.map((set, i) => set.map(member => {
+ if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
doc.cluster = i;
}
}));
- if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ if (doc.cluster === -1 && preferredInd !== -1 && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
doc.cluster = preferredInd;
}
- this.sets.map((set, i) => {
+ this._clusterSets.map((set, i) => {
if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
doc.cluster = i;
}
});
if (doc.cluster === -1) {
- doc.cluster = this.sets.length;
- this.sets.push([doc]);
+ doc.cluster = this._clusterSets.length;
+ this._clusterSets.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);
+ for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ this._clusterSets[doc.cluster].push(doc);
}
}
}
@@ -438,13 +250,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let clusterColor = "";
let cluster = NumCast(doc.cluster);
if (this.Document.useClusters) {
- if (this.sets.length <= cluster) {
+ if (this._clusterSets.length <= cluster) {
setTimeout(() => this.updateCluster(doc), 0);
} else {
// choose a cluster color from a palette
let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
clusterColor = colors[cluster % colors.length];
- let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)) : undefined;
+ let set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor));
// override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
@@ -453,8 +265,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return clusterColor;
}
+ _hitCluster = false;
@action
onPointerDown = (e: React.PointerEvent): void => {
+ this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false;
if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -473,7 +287,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble) {
- if (this.props.Document.useClusters && this.tryDragCluster(e)) {
+ if (this._hitCluster && this.tryDragCluster(e)) {
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
document.removeEventListener("pointermove", this.onPointerMove);
@@ -508,8 +322,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
- let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling() * cscale,
- this._pheight / this.zoomScaling() * cscale);
+ let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
+ this.props.PanelHeight() / this.zoomScaling() * cscale);
if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
@@ -525,7 +339,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (BoolCast(this.props.Document.lockedPosition)) return;
+ if (this.props.Document.lockedPosition) return;
if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
e.stopPropagation();
}
@@ -548,7 +362,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
setPan(panX: number, panY: number) {
- if (!BoolCast(this.props.Document.lockedPosition)) {
+ if (!this.props.Document.lockedPosition) {
this.props.Document.panTransformType = "None";
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -558,12 +372,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- @action
- onDrop = (e: React.DragEvent): void => {
- var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
- super.onDrop(e, { x: pt[0], y: pt[1] });
- }
-
bringToFront = (doc: Doc, sendToBack?: boolean) => {
if (sendToBack || doc.isBackground) {
doc.zIndex = 0;
@@ -601,48 +409,32 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY };
HistoryUtil.pushState(newState);
- let px = this.Document.panX;
- let py = this.Document.panY;
- let s = this.Document.scale;
- this.setPan(newPanX, newPanY);
+ let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
- this.props.Document.panTransformType = "Ease";
+ this.setPan(newPanX, newPanY);
+ this.Document.panTransformType = "Ease";
this.props.focus(this.props.Document);
- if (willZoom) {
- this.setScaleToZoom(doc, scale);
- }
- console.log("Focused " + this.Document.title + " " + s);
+ willZoom && this.setScaleToZoom(doc, scale);
+
afterFocus && setTimeout(() => {
if (afterFocus && afterFocus()) {
- console.log("UnFocused " + this.Document.title + " " + s);
- this.Document.panX = px;
- this.Document.panY = py;
- this.Document.scale = s;
+ this.Document.panX = savedState.px;
+ this.Document.panY = savedState.py;
+ this.Document.scale = savedState.s;
+ this.Document.panTransformType = savedState.pt;
}
}, 1000);
}
setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
- let p = this.props;
- let PanelHeight = p.PanelHeight();
- let panelWidth = p.PanelWidth();
-
- let docHeight = NumCast(doc.height);
- let docWidth = NumCast(doc.width);
- let targetHeight = scale * PanelHeight;
- let targetWidth = scale * panelWidth;
-
- let maxScaleX: number = targetWidth / docWidth;
- let maxScaleY: number = targetHeight / docHeight;
- let maxApplicableScale = Math.min(maxScaleX, maxScaleY);
- this.Document.scale = maxApplicableScale;
+ this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc.width), this.props.PanelHeight() / NumCast(doc.height));
}
zoomToScale = (scale: number) => {
this.Document.scale = scale;
}
- getScale = () => this.Document.scale ? this.Document.scale : 1;
+ getScale = () => this.Document.scale || 1;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
@@ -709,109 +501,109 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
viewDefsToJSX = (views: any[]) => {
- let elements: ViewDefResult[] = [];
- if (Array.isArray(views)) {
- elements = views.reduce<typeof elements>((prev, ele) => {
- const jsx = this.viewDefToJSX(ele);
- jsx && prev.push(jsx);
- return prev;
- }, elements);
- }
- return elements;
+ return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!);
}
private viewDefToJSX(viewDef: any): Opt<ViewDefResult> {
if (viewDef.type === "text") {
- const text = Cast(viewDef.text, "string");
+ const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
const x = Cast(viewDef.x, "number");
const y = Cast(viewDef.y, "number");
const z = Cast(viewDef.z, "number");
const width = Cast(viewDef.width, "number");
const height = Cast(viewDef.height, "number");
const fontSize = Cast(viewDef.fontSize, "number");
- if ([text, x, y, width, height].some(val => val === undefined)) {
- return undefined;
- }
-
- return {
- ele: <div className="collectionFreeform-customText" style={{
- transform: `translate(${x}px, ${y}px)`,
- width, height, fontSize
- }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
- };
+ return [text, x, y, width, height].some(val => val === undefined) ? undefined :
+ {
+ ele: <div className="collectionFreeform-customText" style={{ width, height, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+ {text}
+ </div>,
+ bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
+ };
}
}
- @computed.struct
- get elements() {
- if (this.Document.usePivotLayout) return PivotView.elements(this);
- let curPage = FieldValue(this.Document.curPage, -1);
- const initScript = this.Document.arrangeInit;
- let state: any = undefined;
- let pairs = this.childLayoutPairs;
- let elements: ViewDefResult[] = [];
- if (initScript) {
- const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log);
- if (initResult.success) {
- const result = initResult.result;
- const { state: scriptState, views } = result;
- state = scriptState;
- elements = this.viewDefsToJSX(views);
- }
+ lookupLayout = (doc: Doc, dataDoc?: Doc) => {
+ let data: any = undefined;
+ let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+ switch (this.Document.freeformLayoutEngine) {
+ case "pivot": computedElementData = this.doPivotLayout; break;
+ default: computedElementData = this.doFreeformLayout; break;
}
- let docviews = pairs.reduce((prev, pair) => {
- var page = NumCast(pair.layout.page, -1);
- if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) {
- const pos = this.getCalculatedPositions({ doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state });
- state = pos.state === undefined ? state : pos.state;
- prev.push({
- ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}
- ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
- jitterRotation={NumCast(this.props.Document.jitterRotation)}
- transition={pos.transition} x={pos.x} y={pos.y} width={pos.width} height={pos.height}
- {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
- bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 }
- });
+ computedElementData.map.forEach((value: any, key: { layout: Doc, data?: Doc }) => {
+ if (key.layout === doc && key.data === dataDoc) {
+ data = value;
}
- return prev;
- }, elements);
+ });
+ return data && { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition };
+ }
- return docviews;
+ @computed
+ get doPivotLayout() {
+ return computePivotLayout(this.props.Document, this.childDocs,
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
}
- @computed.struct
- get views() {
- return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+ @computed
+ get doFreeformLayout() {
+ let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
+ let layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
+ const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
+ let state = initResult && initResult.success ? initResult.result.scriptState : undefined;
+ let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
+
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
+ const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+ state = pos.state === undefined ? state : pos.state;
+ layoutPoolData.set(pair, pos);
+ });
+ return { map: layoutPoolData, elements: elements };
}
- @computed.struct
- get overlayViews() {
- return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+
+ @computed
+ get doLayoutComputation() {
+ let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+ switch (this.Document.freeformLayoutEngine) {
+ case "pivot": computedElementData = this.doPivotLayout; break;
+ default: computedElementData = this.doFreeformLayout; break;
+ }
+ this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
+ computedElementData.elements.push({
+ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.lookupLayout}
+ ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider}
+ jitterRotation={NumCast(this.props.Document.jitterRotation)} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />,
+ bounds: this.lookupLayout(pair.layout, pair.data)
+ }));
+
+ return computedElementData;
}
+ @computed.struct get elements() { return this.doLayoutComputation.elements; }
+ @computed.struct get views() { return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
+ @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); }
+
@action
onCursorMove = (e: React.PointerEvent) => {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
- arrangeContents = async () => {
- const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
+ layoutDocsInGrid = () => {
UndoManager.RunInBatch(() => {
- if (docs) {
- let startX = this.Document.panX || 0;
- let x = startX;
- let y = this.Document.panY || 0;
- let i = 0;
- const width = Math.max(...docs.map(doc => NumCast(doc.width)));
- const height = Math.max(...docs.map(doc => NumCast(doc.height)));
- for (const doc of docs) {
- doc.x = x;
- doc.y = y;
- x += width + 20;
- if (++i === 6) {
- i = 0;
- x = startX;
- y += height + 20;
- }
+ const docs = DocListCast(this.Document[this.props.fieldKey]);
+ let startX = this.Document.panX || 0;
+ let x = startX;
+ let y = this.Document.panY || 0;
+ let i = 0;
+ const width = Math.max(...docs.map(doc => NumCast(doc.width)));
+ const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+ for (const doc of docs) {
+ doc.x = x;
+ doc.y = y;
+ x += width + 20;
+ if (++i === 6) {
+ i = 0;
+ x = startX;
+ y += height + 20;
}
}
}, "arrange contents");
@@ -846,10 +638,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
layoutItems.push({ description: "Template Layout Instance", event: () => 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: async () => this.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
+ layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" });
- layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
+ layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, 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({
@@ -897,55 +689,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
]
-
- public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
- return () => {
- let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- let overlayDisposer: () => void = emptyFunction;
- const script = Cast(doc[key], ScriptField);
- let originalText: string | undefined = undefined;
- if (script) originalText = script.script.originalScript;
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
- const script = CompileScript(text, {
- params,
- requiredType,
- typecheck: false
- });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- return;
- }
- doc[key] = new ScriptField(script);
- overlayDisposer();
- }} />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
- };
- addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
- addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
- };
- }
-
render() {
// update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
- 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);
+ this.props.Document.fitX = this.contentBounds && this.contentBounds.x;
+ this.props.Document.fitY = this.contentBounds && this.contentBounds.y;
+ this.props.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
+ this.props.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
// if fieldExt is set, then children will be stored in the extension document for the fieldKey.
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey);
- const easing = () => this.props.Document.panTransformType === "Ease";
return (
<div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+ getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
- easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
<CollectionFreeFormLinksView {...this.props} key="freeformLinks">
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={this._inkKey} >
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} >
{this.childViews}
</InkingCanvas>
</CollectionFreeFormLinksView>
@@ -961,23 +723,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@observer
class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
- @computed get overlayView() {
- return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
- renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
- }
render() {
- return this.overlayView;
+ return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
+ renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />
}
}
@observer
class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
- @computed get backgroundView() {
- return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
- renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
- }
render() {
- return this.props.Document.backgroundLayout ? this.backgroundView : (null);
+ return !this.props.Document.backgroundLayout ? (null) :
+ (<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 bbea4a555..c85c3e55b 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -30,6 +30,7 @@ interface MarqueeViewProps {
removeDocument: (doc: Doc) => boolean;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
+ isAnnotationOverlay: boolean;
}
@observer
@@ -297,8 +298,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
y: bounds.top,
panX: 0,
panY: 0,
- backgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
- defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor,
+ backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
+ defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
width: bounds.width,
height: bounds.height,
title: "a nested collection",
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
index 81b0249dd..cad404d1f 100644
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ b/src/client/views/linking/LinkFollowBox.tsx
@@ -18,6 +18,7 @@ import { DocServer } from "../../DocServer";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { docs_v1 } from "googleapis";
+import { Utils } from "../../../Utils";
enum FollowModes {
OPENTAB = "Open in Tab",
@@ -242,6 +243,7 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
let proto = Doc.GetProto(LinkFollowBox.linkDoc);
let targetContext = await Cast(proto.targetContext, Doc);
let sourceContext = await Cast(proto.sourceContext, Doc);
+ let guid = StrCast(LinkFollowBox.linkDoc[Id]);
const shouldZoom = options ? options.shouldZoom : false;
let dockingFunc = (document: Doc) => { (this._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); };
@@ -251,6 +253,14 @@ export class LinkFollowBox extends React.Component<FieldViewProps> {
}
else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) {
DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!));
+ if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) {
+ if (guid) {
+ let views = DocumentManager.Instance.getDocumentViews(jumpToDoc);
+ views.length && (views[0].props.Document.scrollToLinkID = guid);
+ } else {
+ jumpToDoc.linkHref = Utils.prepend("/doc/" + StrCast(LinkFollowBox.linkDoc[Id]));
+ }
+ }
}
else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, undefined, undefined,
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 82fe3df23..835554ac0 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -28,9 +28,7 @@ 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;
- }
+ @action toggleShowMore() { this._showMore = !this._showMore; }
onEdit = (e: React.PointerEvent): void => {
e.stopPropagation();
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 91037df7f..bcb26b4c4 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -12,6 +12,7 @@ import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { random } from "animejs";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
+ dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined
x?: number;
y?: number;
width?: number;
@@ -32,14 +33,14 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema);
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
_disposer: IReactionDisposer | undefined = undefined;
- @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._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; }
- @computed get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; }
- @computed get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.Document[WidthSym](); }
- @computed get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.Document[HeightSym](); }
+ get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
+ get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : this.Document.x || 0; }
+ get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : this.Document.y || 0; }
+ get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.props.Document[WidthSym](); }
+ get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.props.Document[HeightSym](); }
+ @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
@computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
@computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
- @computed get scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); }
@computed get renderScriptDim() {
if (this.Document.renderScript) {
@@ -72,7 +73,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
panelHeight = () => this.props.PanelHeight();
getTransform = (): Transform => this.props.ScreenToLocalTransform()
.translate(-this.X, -this.Y)
- .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth)
+ .scale(1 / this.contentScaling())
borderRounding = () => {
let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
@@ -98,6 +99,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@observable _animPos: number[] | undefined = undefined;
+ finalPanelWidh = () => { return this.dataProvider ? this.dataProvider.width : this.panelWidth(); }
+ finalPanelHeight = () => { return this.dataProvider ? this.dataProvider.height : this.panelHeight(); }
+
render() {
trace();
return (
@@ -111,7 +115,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
StrCast(this.layoutDoc.boxShadow, ""),
borderRadius: this.borderRounding(),
transform: this.transform,
- transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : StrCast(this.layoutDoc.transition),
+ transition: this.Document.isAnimating !== undefined ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
width: this.width,
height: this.height,
zIndex: this.Document.zIndex || 0,
@@ -120,8 +124,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
backgroundColor={this.clusterColorFunc}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
+ PanelWidth={this.finalPanelWidh}
+ PanelHeight={this.finalPanelHeight}
/>
</div>
);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d8cfff973..e89fddd25 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -350,7 +350,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
// const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
de.data.linkSourceDocument !== this.props.Document &&
- (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc));
+ (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc, undefined, "in-text link being created")); // TODODO this is where in text links get passed
}
}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 0d7277cbe..45e516015 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -164,13 +164,13 @@ ol { counter-reset: deci1 0;}
.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}
+.decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;}
+.decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35}
+.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35}
+.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ") "; counter-increment: deci4; display:inline-block; min-width: 40}
+.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ") "; counter-increment: deci5; display:inline-block; min-width: 40}
+.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ") "; counter-increment: deci6; display:inline-block; min-width: 45}
+.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ") "; counter-increment: deci7; display:inline-block; min-width: 50}
+.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 }
+.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 }
+.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index eb4718581..47b64e260 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -80,6 +80,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
private _nodeClicked: any;
private _undoTyping?: UndoManager.Batch;
private _searchReactionDisposer?: Lambda;
+ private _scroolToRegionReactionDisposer: Opt<IReactionDisposer>;
private _reactionDisposer: Opt<IReactionDisposer>;
private _textReactionDisposer: Opt<IReactionDisposer>;
private _heightReactionDisposer: Opt<IReactionDisposer>;
@@ -138,6 +139,53 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this.props.isOverlay) {
DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
}
+
+ this._scroolToRegionReactionDisposer = reaction(
+ () => StrCast(this.props.Document.scrollToLinkID),
+ async (scrollToLinkID) => {
+ let findLinkFrag = (frag: Fragment, editor: EditorView) => {
+ const nodes: Node[] = [];
+ frag.forEach((node, index) => {
+ let examinedNode = findLinkNode(node, editor);
+ if (examinedNode && examinedNode.textContent) {
+ nodes.push(examinedNode);
+ start += index;
+ }
+ });
+ return { frag: Fragment.fromArray(nodes), start: start };
+ }
+ let findLinkNode = (node: Node, editor: EditorView) => {
+ if (!node.isText) {
+ const content = findLinkFrag(node.content, editor);
+ return node.copy(content.frag);
+ }
+ const marks = [...node.marks];
+ const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link);
+ return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined;
+ }
+
+ let start = -1;
+
+ if (this._editorView && scrollToLinkID) {
+ let editor = this._editorView;
+ let ret = findLinkFrag(editor.state.doc.content, editor);
+
+ if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) {
+ let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start
+ if (ret.frag.firstChild) {
+ selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected
+ }
+ editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView());
+ const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+ setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0);
+ setTimeout(() => this.unhighlightSearchTerms(), 2000);
+
+ this.props.Document.scrollToLinkID = undefined;
+ }
+ }
+
+ }
+ );
}
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
@@ -696,6 +744,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
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),
+ handleScrollToSelection: (editorView) => {
+ let ref = editorView.domAtPos(editorView.state.selection.from);
+ let refNode = ref.node as any;
+ while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
+ let r1 = refNode && (refNode as any).getBoundingClientRect();
+ let r3 = self._ref.current!.getBoundingClientRect();
+ r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
+ return true;
+ },
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); },
@@ -736,6 +793,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
componentWillUnmount() {
+ this._scroolToRegionReactionDisposer && this._scroolToRegionReactionDisposer();
this._rulesReactionDisposer && this._rulesReactionDisposer();
this._reactionDisposer && this._reactionDisposer();
this._proxyReactionDisposer && this._proxyReactionDisposer();
@@ -783,9 +841,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
let proto = Doc.GetProto(linkDoc);
let targetContext = await Cast(proto.targetContext, Doc);
let jumpToDoc = await Cast(linkDoc.anchor2, Doc);
+
if (jumpToDoc) {
if (DocumentManager.Instance.getDocumentView(jumpToDoc)) {
-
DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page)));
return;
}
@@ -852,7 +910,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
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 }));
+ let hit = this._editorView!.domAtPos(pos.pos).node as any;
+ let beforeEle = document.querySelector("." + hit.className) as Element;
+ let before = beforeEle ? window.getComputedStyle(beforeEle, ':before') : undefined;
+ let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined;
+ if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) {
+ let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined;
+ if (ol && ol.type === schema.nodes.ordered_list && !e.shiftKey) {
+ this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2))));
+ } else {
+ this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility }));
+ }
+ }
}
}
}
@@ -903,7 +972,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
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() }));
+ this._editorView!.dispatch(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");
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index a582ef2cc..12a5bc492 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -26,11 +26,12 @@ const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema);
@observer
export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
public static LayoutString() { return FieldView.LayoutString(PDFBox); }
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _reactionDisposer?: IReactionDisposer;
private _keyValue: string = "";
private _valueValue: string = "";
private _scriptValue: string = "";
+ @observable private _searching: boolean = false;
+ private _pdfViewer: PDFViewer | undefined;
private _keyRef: React.RefObject<HTMLInputElement> = React.createRef();
private _valueRef: React.RefObject<HTMLInputElement> = React.createRef();
private _scriptRef: React.RefObject<HTMLInputElement> = React.createRef();
@@ -46,7 +47,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
componentDidMount() {
this.props.setPdfBox && this.props.setPdfBox(this);
- this.props.Document.curPage = ComputedField.MakeFunction("Math.floor(Number(this.panY) / Number(this.nativeHeight) + 1)");
+ this.props.Document.curPage = 1; // ComputedField.MakeFunction("Math.floor(Number(this.panY) / Number(this.nativeHeight) + 1)");
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
if (pdfUrl instanceof PdfField) {
@@ -58,6 +59,14 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
this._reactionDisposer && this._reactionDisposer();
}
+ public search(string: string) {
+ this._pdfViewer && this._pdfViewer.search(string);
+ }
+
+ setPdfViewer = (pdfViewer: PDFViewer) => {
+ this._pdfViewer = pdfViewer;
+ }
+
public GetPage() {
return Math.floor((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1;
}
@@ -99,7 +108,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
scrollTo = (y: number) => {
- this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
+
}
private resetFilters = () => {
@@ -116,8 +125,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
settingsPanel() {
return !this.props.active() ? (null) :
(<div className="pdfBox-settingsCont" onPointerDown={(e) => e.stopPropagation()}>
- <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings"
- style={{ marginTop: `${this.Document.panY || 0}px` }}>
+ <button className="pdfBox-settingsButton" onClick={action(() => this._flyout = !this._flyout)} title="Open Annotation Settings" >
<div className="pdfBox-settingsButton-arrow"
style={{
borderTop: `25px solid ${this._flyout ? "#121721" : "transparent"}`,
@@ -157,6 +165,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
loaded = (nw: number, nh: number, np: number) => {
+ // nh *= .33.33333;
+ // nw *= 1.33333;
this.dataDoc.numPages = np;
if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) {
let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1);
@@ -166,11 +176,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
}
- onScroll = (e: React.UIEvent<HTMLDivElement>) => {
- e.stopPropagation();
- }
-
-
render() {
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
@@ -182,9 +187,10 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
e.button === 0 && e.stopPropagation();
}
}}>
- <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={this.Document.panY || 0}
+ <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded}
+ setPdfViewer={this.setPdfViewer}
Document={this.props.Document} DataDoc={this.dataDoc}
- addDocTab={this.props.addDocTab} setPanY={this.setPanY} GoToPage={this.GotoPage}
+ addDocTab={this.props.addDocTab} GoToPage={this.GotoPage}
pinToPres={this.props.pinToPres} addDocument={this.props.addDocument}
fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} />
{this.settingsPanel()}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index e376fbddb..5afd85430 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -161,10 +161,8 @@ export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps?
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);
- }
+ if (prevScale !== undefined && prevScale !== curScale) {
+ DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale);
}
}
}
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index 247df4e90..4388bc64c 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -14,6 +14,12 @@
transform-origin: top left;
}
+ .pdfViewer-dragAnnotationBox {
+ position:absolute;
+ background-color: transparent;
+ opacity: 0.1;
+ }
+
.pdfViewer-annotationLayer {
position: absolute;
top: 0;
@@ -27,27 +33,6 @@
}
}
- .pdfViewer-overlayCont {
- position: absolute;
- width: 100%;
- height: 100px;
- background: #121721;
- bottom: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 20px;
- overflow: hidden;
- transition: left .5s;
-
- .pdfViewer-overlaySearchBar {
- width: 20%;
- height: 100%;
- font-size: 30px;
- padding: 5px;
- }
- }
-
.pdfViewer-overlayButton {
border-bottom-left-radius: 50%;
display: flex;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index f8205a566..899a0f5aa 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -1,26 +1,25 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import * as Pdfjs from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";
-import * as rp from "request-promise";
import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, FieldResult, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
+import { Doc, DocListCast, FieldResult, WidthSym } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { Utils, numberRange } from "../../../Utils";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { numberRange } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
import { KeyCodes } from "../../northstar/utils/KeyCodes";
-import { CompileScript, CompiledScript } from "../../util/Scripting";
+import { DragManager } from "../../util/DragManager";
+import { CompiledScript, CompileScript } from "../../util/Scripting";
import Annotation from "./Annotation";
+import PDFMenu from "./PDFMenu";
import "./PDFViewer.scss";
import React = require("react");
import requestPromise = require("request-promise");
-import PDFMenu from "./PDFMenu";
-import { DragManager } from "../../util/DragManager";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
interface IViewerProps {
@@ -33,11 +32,11 @@ interface IViewerProps {
loaded: (nw: number, nh: number, np: number) => void;
scrollTo: (y: number) => void;
active: () => boolean;
- setPanY?: (n: number) => void;
GoToPage?: (n: number) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean;
+ setPdfViewer: (view: PDFViewer) => void;
}
/**
@@ -49,7 +48,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
@observable private _annotations: Doc[] = [];
@observable private _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
@observable private _script: CompiledScript = CompileScript("return true") as CompiledScript;
- @observable private _searching: boolean = false;
@observable private Index: number = -1;
@observable private _marqueeX: number = 0;
@observable private _marqueeY: number = 0;
@@ -64,14 +62,12 @@ export class PDFViewer extends React.Component<IViewerProps> {
private _viewer: React.RefObject<HTMLDivElement> = React.createRef();
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
public _pdfViewer: any;
- private _searchString: string = "";
private _selectionText: string = "";
private _marquee: React.RefObject<HTMLDivElement> = React.createRef();
private _marqueeing: boolean = false;
private _startY: number = 0;
private _startX: number = 0;
-
@computed get allAnnotations() {
return DocListCast(this.props.fieldExtensionDoc.annotations).filter(
anno => this._script.run({ this: anno }, console.log, true).result);
@@ -82,6 +78,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
componentDidMount = async () => {
+ this.props.setPdfViewer(this);
await this.initialLoad();
this._annotationReactionDisposer = reaction(
@@ -108,7 +105,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
document.removeEventListener("copy", this.copy);
document.addEventListener("copy", this.copy);
- setTimeout(this.setupPdfJsViewer, 1000);
+ this.setupPdfJsViewer();
}
componentWillUnmount = () => {
@@ -136,8 +133,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
}
- searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
-
setSelectionText = (text: string) => this._selectionText = text;
@action
@@ -334,13 +329,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
}
}
-
- @action
- toggleSearch = (e: React.MouseEvent) => {
- e.stopPropagation();
- this._searching = !this._searching;
- }
-
@action
onPointerDown = (e: React.PointerEvent): void => {
// if alt+left click, drag and annotate
@@ -499,9 +487,9 @@ export class PDFViewer extends React.Component<IViewerProps> {
let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc);
DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, {
handlers: {
- dragComplete: async () => {
- if (!BoolCast(annotationDoc.linkedToDoc)) {
- let annotations = await DocListCastAsync(annotationDoc.annotations);
+ dragComplete: () => {
+ if (!annotationDoc.linkedToDoc) {
+ let annotations = DocListCast(annotationDoc.annotations);
annotations && annotations.forEach(anno => anno.target = targetDoc);
DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`);
}
@@ -526,11 +514,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
render() {
let scaling = this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width / this.props.Document[WidthSym]() : 1;
- return (<div className="pdfViewer-viewer" ref={this._mainCont}>
+ return (<div className="pdfViewer-viewer" onPointerDown={this.onPointerDown} ref={this._mainCont}>
<div className="pdfViewer-text" style={{ transform: `scale(${scaling})` }}>
<div key="viewerReal" ref={this._viewer} />
</div>
- <div className="pdfPage-dragAnnotationBox" ref={this._marquee}
+ <div className="pdfViewer-dragAnnotationBox" ref={this._marquee}
style={{
left: `${this._marqueeX}px`, top: `${this._marqueeY}px`,
width: `${this._marqueeWidth}px`, height: `${this._marqueeHeight}px`,
@@ -541,14 +529,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
{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: 0, left: `${this._searching ? 0 : 100}%` }}>
- <button className="pdfViewer-overlayButton" title="Open Search Bar" />
- <input className="pdfViewer-overlaySearchBar" placeholder="Search" onChange={this.searchStringChanged}
- onKeyDown={(e: React.KeyboardEvent) => e.keyCode === KeyCodes.ENTER ? this.search(this._searchString) : e.keyCode === KeyCodes.BACKSPACE ? e.stopPropagation() : true} />
- <button title="Search" onClick={() => this.search(this._searchString)}>
- <FontAwesomeIcon icon="search" size="3x" color="white" /></button>
- </div>
<button className="pdfViewer-overlayButton" onClick={this.prevAnnotation} title="Previous Annotation"
style={{ bottom: 280, right: 10, display: this.props.active() ? "flex" : "none" }}>
<div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
@@ -559,14 +539,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
<div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="3x" /></div>
</button>
- <button className="pdfViewer-overlayButton" onClick={this.toggleSearch} title="Open Search Bar"
- style={{ bottom: 10, right: 0, display: this.props.active() ? "flex" : "none" }}>
- <div className="pdfViewer-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
- <div className="pdfViewer-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
- <FontAwesomeIcon style={{ color: "white" }} icon={this._searching ? "times" : "search"} size="3x" /></div>
- </button>
</div >);
}
}
-export enum AnnotationTypes { Region } \ No newline at end of file
+export enum AnnotationTypes { Region }
diff --git a/src/client/views/presentationview/PresentationModeMenu.tsx b/src/client/views/presentationview/PresentationModeMenu.tsx
index 4de8da587..0dd2b7731 100644
--- a/src/client/views/presentationview/PresentationModeMenu.tsx
+++ b/src/client/views/presentationview/PresentationModeMenu.tsx
@@ -21,10 +21,12 @@ export interface PresModeMenuProps {
export default class PresModeMenu extends React.Component<PresModeMenuProps> {
@observable private _top: number = 20;
- @observable private _right: number = 0;
+ @observable private _left: number = window.innerWidth - 160;
@observable private _opacity: number = 1;
@observable private _transition: string = "opacity 0.5s";
@observable private _transitionDelay: string = "";
+ private _offsetY: number = 0;
+ private _offsetX: number = 0;
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
@@ -35,8 +37,8 @@ export default class PresModeMenu extends React.Component<PresModeMenuProps> {
*/
@action
dragging = (e: PointerEvent) => {
- this._right -= e.movementX;
- this._top += e.movementY;
+ this._left = e.pageX - this._offsetX;
+ this._top = e.pageY - this._offsetY;
e.stopPropagation();
e.preventDefault();
@@ -63,6 +65,9 @@ export default class PresModeMenu extends React.Component<PresModeMenuProps> {
document.removeEventListener("pointerup", this.dragEnd);
document.addEventListener("pointerup", this.dragEnd);
+ this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX;
+ this._offsetY = e.nativeEvent.offsetY;
+
e.stopPropagation();
e.preventDefault();
}
@@ -82,7 +87,7 @@ export default class PresModeMenu extends React.Component<PresModeMenuProps> {
render() {
return (
<div className="presMenu-cont" ref={this._mainCont}
- style={{ right: this._right, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
+ style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
<button title="Back" className="presMenu-button" onClick={this.props.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
{this.renderPlayPauseButton()}
<button title="Next" className="presMenu-button" onClick={this.props.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b875e7896..1b3c8b0b0 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -13,6 +13,7 @@ import { listSpec } from "./Schema";
import { ComputedField } from "./ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types";
import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
+import { intersectRect } from "../Utils";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -614,6 +615,18 @@ export namespace Doc {
}), 0);
}
+ export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) {
+ var x2 = NumCast(doc2.x) - clusterDistance;
+ var y2 = NumCast(doc2.y) - clusterDistance;
+ var w2 = NumCast(doc2.width) + clusterDistance;
+ var h2 = NumCast(doc2.height) + clusterDistance;
+ var x = NumCast(doc.x) - clusterDistance;
+ var y = NumCast(doc.y) - clusterDistance;
+ var w = NumCast(doc.width) + clusterDistance;
+ var h = NumCast(doc.height) + clusterDistance;
+ return doc.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 });
+ }
+
export function isBrushedHighlightedDegree(doc: Doc) {
if (Doc.IsHighlighted(doc)) {
return 3;