aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts59
-rw-r--r--src/client/documents/DocumentTypes.ts3
-rw-r--r--src/client/documents/Documents.ts22
-rw-r--r--src/client/util/InteractionUtils.ts111
-rw-r--r--src/client/util/RichTextSchema.tsx38
-rw-r--r--src/client/util/SearchUtil.ts7
-rw-r--r--src/client/util/SelectionManager.ts42
-rw-r--r--src/client/util/SettingsManager.tsx2
-rw-r--r--src/client/util/TooltipTextMenu.scss571
-rw-r--r--src/client/util/TooltipTextMenu.tsx1151
-rw-r--r--src/client/views/AntimodeMenu.scss29
-rw-r--r--src/client/views/AntimodeMenu.tsx128
-rw-r--r--src/client/views/CollectionLinearView.scss6
-rw-r--r--src/client/views/CollectionLinearView.tsx29
-rw-r--r--src/client/views/DocComponent.tsx23
-rw-r--r--src/client/views/DocumentButtonBar.scss106
-rw-r--r--src/client/views/DocumentButtonBar.tsx366
-rw-r--r--src/client/views/DocumentDecorations.tsx26
-rw-r--r--src/client/views/InkSelectDecorations.scss5
-rw-r--r--src/client/views/InkSelectDecorations.tsx55
-rw-r--r--src/client/views/InkingCanvas.scss51
-rw-r--r--src/client/views/InkingCanvas.tsx196
-rw-r--r--src/client/views/InkingStroke.tsx104
-rw-r--r--src/client/views/Main.scss10
-rw-r--r--src/client/views/MainView.scss6
-rw-r--r--src/client/views/MainView.tsx34
-rw-r--r--src/client/views/PreviewCursor.scss1
-rw-r--r--src/client/views/PreviewCursor.tsx30
-rw-r--r--src/client/views/TemplateMenu.scss50
-rw-r--r--src/client/views/TemplateMenu.tsx51
-rw-r--r--src/client/views/Touchable.tsx94
-rw-r--r--src/client/views/collections/CollectionDockingView.scss70
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx24
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx15
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx1
-rw-r--r--src/client/views/collections/CollectionStaffView.scss13
-rw-r--r--src/client/views/collections/CollectionStaffView.tsx59
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx19
-rw-r--r--src/client/views/collections/CollectionView.tsx15
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss6
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx30
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx34
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx55
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss16
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx454
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx46
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx309
-rw-r--r--src/client/views/nodes/ButtonBox.scss13
-rw-r--r--src/client/views/nodes/ButtonBox.tsx2
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.scss5
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx23
-rw-r--r--src/client/views/nodes/ColorBox.tsx4
-rw-r--r--src/client/views/nodes/ContentFittingDocumentView.tsx5
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx6
-rw-r--r--src/client/views/nodes/DocumentView.scss47
-rw-r--r--src/client/views/nodes/DocumentView.tsx152
-rw-r--r--src/client/views/nodes/FieldView.tsx4
-rw-r--r--src/client/views/nodes/FontIconBox.scss6
-rw-r--r--src/client/views/nodes/FontIconBox.tsx7
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss347
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx107
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.scss1
-rw-r--r--src/client/views/nodes/FormattedTextBoxComment.tsx102
-rw-r--r--src/client/views/nodes/ImageBox.scss2
-rw-r--r--src/client/views/nodes/ImageBox.tsx12
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.scss54
-rw-r--r--src/client/views/nodes/PDFBox.tsx35
-rw-r--r--src/client/views/nodes/VideoBox.scss1
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
-rw-r--r--src/client/views/pdf/PDFMenu.scss40
-rw-r--r--src/client/views/pdf/PDFMenu.tsx125
-rw-r--r--src/client/views/pdf/PDFViewer.scss30
-rw-r--r--src/client/views/pdf/PDFViewer.tsx103
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx1
-rw-r--r--src/client/views/search/FilterBox.tsx6
-rw-r--r--src/new_fields/Doc.ts21
-rw-r--r--src/new_fields/InkField.ts20
-rw-r--r--src/new_fields/RichTextField.ts10
-rw-r--r--src/new_fields/RichTextUtils.ts2
-rw-r--r--src/new_fields/SchemaHeaderField.ts11
-rw-r--r--src/new_fields/documentSchemas.ts7
-rw-r--r--src/new_fields/util.ts6
-rw-r--r--src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloudbin172 -> 0 bytes
-rw-r--r--src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloudbin172 -> 0 bytes
-rw-r--r--src/server/authentication/models/current_user_utils.ts24
-rw-r--r--src/server/index.ts1
88 files changed, 3703 insertions, 2224 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 7bb025e49..37b509370 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -153,6 +153,63 @@ export namespace Utils {
return Math.max(lower, Math.min(upper, n));
}
+ export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] {
+ if ((xs2 < xs && xe2 > xs) || (xs2 < xe && xe2 > xe) || (xs2 > xs && xe2 < xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]];
+ if (xe2 < xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]];
+ //if (xs2 > xe)
+ return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]];
+ }
+ export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] {
+ if ((ys2 < ys && ye2 > ys) || (ys2 < ye && ye2 > ye) || (ys2 > ys && ye2 < ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]];
+ if (ye2 < ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]];
+ //if (ys2 > ye)
+ return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
+ }
+
+ function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) {
+
+ if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 };
+ var atob = { x: bx - ax, y: by - ay };
+ var atop = { x: px - ax, y: py - ay };
+ var len = atob.x * atob.x + atob.y * atob.y;
+ var dot = atop.x * atob.x + atop.y * atob.y;
+ var t = Math.min(1, Math.max(0, dot / len));
+
+ dot = (bx - ax) * (py - ay) - (by - ay) * (px - ax);
+
+ return {
+ point: {
+ x: ax + atob.x * t,
+ y: ay + atob.y * t
+ },
+ left: dot < 1,
+ dot: dot,
+ t: t
+ };
+ }
+
+ export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number,
+ l1: number, t1: number, w1: number, h1: number,
+ x: number, y: number) {
+ var r = l + w,
+ b = t + h;
+ var r1 = l1 + w1,
+ b1 = t1 + h1;
+ let hsegs = [[l, r, t, l1, r1, t1], [l, r, b, l1, r1, t1], [l, r, t, l1, r1, b1], [l, r, b, l1, r1, b1]];
+ let vsegs = [[l, t, b, l1, t1, b1], [r, t, b, l1, t1, b1], [l, t, b, r1, t1, b1], [r, t, b, r1, t1, b1]];
+ let res = hsegs.reduce((closest, seg) => {
+ let res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
+ return (res[0] < closest[0]) ? res : closest;
+ }, [Number.MAX_VALUE, []] as [number, number[]]);
+ let fres = vsegs.reduce((closest, seg) => {
+ let res = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]);
+ return (res[0] < closest[0]) ? res : closest;
+ }, res);
+
+ let near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
+ return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
+ }
+
export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) {
var r = l + w,
b = t + h;
@@ -303,6 +360,8 @@ export function returnEmptyString() { return ""; }
export function emptyFunction() { }
+export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); }
+
export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type Predicate<K, V> = (entry: [K, V]) => boolean;
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 12501065a..f6dd0c346 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -24,5 +24,6 @@ export enum DocumentType {
QUERY = "search",
COLOR = "color",
DOCULINK = "doculink",
- PDFANNO = "pdfanno"
+ PDFANNO = "pdfanno",
+ INK = "ink"
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ba9f87025..c5bf109a1 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -48,6 +48,8 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
import { QueryBox } from "../views/nodes/QueryBox";
import { ColorBox } from "../views/nodes/ColorBox";
import { DocuLinkBox } from "../views/nodes/DocuLinkBox";
+import { InkingStroke } from "../views/InkingStroke";
+import { InkField } from "../../new_fields/InkField";
var requestImageSize = require('../util/request-image-size');
var path = require('path');
@@ -76,7 +78,8 @@ export interface DocumentOptions {
viewType?: number;
backgroundColor?: string;
ignoreClick?: boolean;
- lockedPosition?: boolean;
+ lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged
+ lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed
opacity?: number;
defaultBackgroundColor?: string;
dropAction?: dropActionType;
@@ -95,7 +98,7 @@ export interface DocumentOptions {
autoHeight?: boolean;
removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document
dbDoc?: Doc;
- unchecked?: ScriptField; // returns whether a check box is unchecked
+ ischecked?: ScriptField; // returns whether a font icon box is checked
activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
onClick?: ScriptField;
dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
@@ -107,6 +110,8 @@ export interface DocumentOptions {
sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
+ strokeWidth?: number;
+ color?: string;
// [key: string]: Opt<Field>;
}
@@ -209,6 +214,9 @@ export namespace Docs {
[DocumentType.PRESELEMENT, {
layout: { view: PresElementBox, dataField: data }
}],
+ [DocumentType.INK, {
+ layout: { view: InkingStroke, dataField: data }
+ }]
]);
// All document prototypes are initialized with at least these values
@@ -414,6 +422,14 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options);
}
+ export function InkDocument(color: string, tool: number, strokeWidth: number, points: { x: number, y: number }[], options: DocumentOptions = {}) {
+ let doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
+ doc.color = color;
+ doc.strokeWidth = strokeWidth;
+ doc.tool = tool;
+ return doc;
+ }
+
export function IconDocument(icon: string, options: DocumentOptions = {}) {
return InstanceFromProto(Prototypes.get(DocumentType.ICON), new IconField(icon), options);
}
@@ -631,7 +647,7 @@ export namespace Docs {
}
if (type.indexOf("pdf") !== -1) {
ctor = Docs.Create.PdfDocument;
- options.nativeWidth = 1200;
+ options.nativeWidth = 927;
options.nativeHeight = 1200;
}
if (type.indexOf("excel") !== -1) {
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
new file mode 100644
index 000000000..e58635a6f
--- /dev/null
+++ b/src/client/util/InteractionUtils.ts
@@ -0,0 +1,111 @@
+export namespace InteractionUtils {
+ export const MOUSE = "mouse";
+ export const TOUCH = "touch";
+
+ export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
+ return e.pointerType === type;
+ }
+
+ export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
+ return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+ }
+
+ /**
+ * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point)
+ * @param pts - n-arbitrary long list of points
+ */
+ export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } {
+ let centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
+ let centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
+ return { X: centerX, Y: centerY };
+ }
+
+ /**
+ * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in
+ * @param pt1 - new point that corresponds to oldPoint1
+ * @param pt2 - new point that corresponds to oldPoint2
+ * @param oldPoint1 - previous point 1
+ * @param oldPoint2 - previous point 2
+ */
+ export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
+ let threshold = 4;
+ let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
+ let newDist = TwoPointEuclidist(pt1, pt2);
+
+ /** if they have the same sign, then we are either pinching in or out.
+ * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch)
+ * so that it can still pan without freaking out
+ */
+ if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) {
+ return Math.sign(oldDist - newDist);
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the type of Touch Interaction from a list of points.
+ * Also returns any data that is associated with a Touch Interaction
+ * @param pts - List of points
+ */
+ // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
+ // const leniency = 200;
+ // switch (pts.length) {
+ // case 1:
+ // return { type: OneFinger };
+ // case 2:
+ // return { type: TwoSeperateFingers };
+ // case 3:
+ // let pt1 = pts[0];
+ // let pt2 = pts[1];
+ // let pt3 = pts[2];
+ // if (pt1 && pt2 && pt3) {
+ // let dist12 = TwoPointEuclidist(pt1, pt2);
+ // let dist23 = TwoPointEuclidist(pt2, pt3);
+ // let dist13 = TwoPointEuclidist(pt1, pt3);
+ // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`);
+ // let dist12close = dist12 < leniency;
+ // let dist23close = dist23 < leniency;
+ // let dist13close = dist13 < leniency;
+ // let xor2313 = dist23close ? !dist13close : dist13close;
+ // let xor = dist12close ? !xor2313 : xor2313;
+ // // three input xor because javascript doesn't have logical xor's
+ // if (xor) {
+ // let points: number[] = [];
+ // let min = Math.min(dist12, dist23, dist13);
+ // switch (min) {
+ // case dist12:
+ // points = [0, 1, 2];
+ // break;
+ // case dist23:
+ // points = [1, 2, 0];
+ // break;
+ // case dist13:
+ // points = [0, 2, 1];
+ // break;
+ // }
+ // return { type: TwoToOneFingers, data: points };
+ // }
+ // else {
+ // return { type: ThreeSeperateFingers, data: null };
+ // }
+ // }
+ // default:
+ // return { type: undefined };
+ // }
+ // }
+
+ export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: TouchList, leniency: number): boolean {
+ for (let i = 0; i < newTouches.length; i++) {
+ let touch = newTouches.item(i);
+ if (touch) {
+ let oldTouch = oldTouches.get(touch.identifier);
+ if (oldTouch) {
+ if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 76b8aeaa1..0a717dff1 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -18,7 +18,6 @@ import { Transform } from "./Transform";
import React = require("react");
import { BoolCast, NumCast } from "../../new_fields/Types";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -130,6 +129,7 @@ export const nodes: { [index: string]: NodeSpec } = {
// }
// }]
},
+
// :: NodeSpec An inline image (`<img>`) node. Supports `src`,
// `alt`, and `href` attributes. The latter two default to the empty
// string.
@@ -310,6 +310,37 @@ export const marks: { [index: string]: MarkSpec } = {
}
},
+ // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text.
+ color: {
+ attrs: {
+ color: { default: "#000" }
+ },
+ inclusive: false,
+ parseDOM: [{
+ tag: "span", getAttrs(dom: any) {
+ return { color: dom.getAttribute("color") };
+ }
+ }],
+ toDOM(node: any) {
+ return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }];
+ }
+ },
+
+ marker: {
+ attrs: {
+ highlight: { default: "transparent" }
+ },
+ inclusive: false,
+ parseDOM: [{
+ tag: "span", getAttrs(dom: any) {
+ return { highlight: dom.getAttribute("backgroundColor") };
+ }
+ }],
+ toDOM(node: any) {
+ return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }];
+ }
+ },
+
// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
// Has parse rules that also match `<i>` and `font-style: italic`.
em: {
@@ -576,8 +607,8 @@ export class ImageResizeView {
this._handle.onpointerdown = function (e: any) {
e.preventDefault();
e.stopPropagation();
- let wid = Number(getComputedStyle(self._img).width!.replace(/px/, ""));
- let hgt = Number(getComputedStyle(self._img).height!.replace(/px/, ""));
+ let wid = Number(getComputedStyle(self._img).width.replace(/px/, ""));
+ let hgt = Number(getComputedStyle(self._img).height.replace(/px/, ""));
const startX = e.pageX;
const startWidth = parseFloat(node.attrs.width);
const onpointermove = (e: any) => {
@@ -680,6 +711,7 @@ export class DashDocView {
bringToFront={emptyFunction}
zoomToScale={emptyFunction}
getScale={returnOne}
+ dontRegisterView={true}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
ContentScaling={this.contentScaling}
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 6706dcb89..2cf13680a 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -28,6 +28,7 @@ export namespace SearchUtil {
start?: number;
rows?: number;
fq?: string;
+ allowAliases?: boolean;
}
export function Search(query: string, returnDocs: true, options?: SearchParams): Promise<DocSearchResult>;
export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
@@ -73,7 +74,7 @@ export namespace SearchUtil {
const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc);
for (let i = 0; i < ids.length; i++) {
let testDoc = docs[i];
- if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) {
+ if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) {
theDocs.push(testDoc);
theLines.push([]);
}
@@ -88,9 +89,9 @@ export namespace SearchUtil {
const proto = Doc.GetProto(doc);
const protoId = proto[Id];
if (returnDocs) {
- return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).docs;
+ return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).docs;
} else {
- return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).ids;
+ return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).ids;
}
// return Search(`{!join from=id to=proto_i}id:${protoId}`, true);
}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 2d717ca57..e01216e0f 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,46 +1,44 @@
-import { observable, action, runInAction, IReactionDisposer, reaction, autorun } from "mobx";
-import { Doc, Opt } from "../../new_fields/Doc";
+import { observable, action, runInAction, ObservableMap } from "mobx";
+import { Doc } from "../../new_fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
-import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
-import { NumCast, StrCast } from "../../new_fields/Types";
-import { InkingControl } from "../views/InkingControl";
+import { computedFn } from "mobx-utils";
export namespace SelectionManager {
class Manager {
@observable IsDragging: boolean = false;
- @observable SelectedDocuments: Array<DocumentView> = [];
+ SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap();
@action
SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
- if (manager.SelectedDocuments.indexOf(docView) === -1) {
+ if (!manager.SelectedDocuments.get(docView)) {
if (!ctrlPressed) {
this.DeselectAll();
}
- manager.SelectedDocuments.push(docView);
+ manager.SelectedDocuments.set(docView, true);
// console.log(manager.SelectedDocuments);
docView.props.whenActiveChanged(true);
- } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) {
- manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false));
- manager.SelectedDocuments = [docView];
+ } else if (!ctrlPressed && Array.from(manager.SelectedDocuments.entries()).length > 1) {
+ Array.from(manager.SelectedDocuments.keys()).map(dv => dv !== docView && dv.props.whenActiveChanged(false));
+ manager.SelectedDocuments.clear();
+ manager.SelectedDocuments.set(docView, true);
}
}
@action
DeselectDoc(docView: DocumentView): void {
- let ind = manager.SelectedDocuments.indexOf(docView);
- if (ind !== -1) {
- manager.SelectedDocuments.splice(ind, 1);
+ if (manager.SelectedDocuments.get(docView)) {
+ manager.SelectedDocuments.delete(docView);
docView.props.whenActiveChanged(false);
}
}
@action
DeselectAll(): void {
- manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
- manager.SelectedDocuments = [];
+ Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false));
+ manager.SelectedDocuments.clear();
}
}
@@ -53,14 +51,18 @@ export namespace SelectionManager {
manager.SelectDoc(docView, ctrlPressed);
}
- export function IsSelected(doc: DocumentView): boolean {
- return manager.SelectedDocuments.indexOf(doc) !== -1;
+ export function IsSelected(doc: DocumentView, outsideReaction?: boolean): boolean {
+ return outsideReaction ?
+ manager.SelectedDocuments.get(doc) ? true : false :
+ computedFn(function isSelected(doc: DocumentView) {
+ return manager.SelectedDocuments.get(doc) ? true : false;
+ })(doc);
}
export function DeselectAll(except?: Doc): void {
let found: DocumentView | undefined = undefined;
if (except) {
- for (const view of manager.SelectedDocuments) {
+ for (const view of Array.from(manager.SelectedDocuments.keys())) {
if (view.props.Document === except) found = view;
}
}
@@ -73,6 +75,6 @@ export namespace SelectionManager {
export function GetIsDragging() { return manager.IsDragging; }
export function SelectedDocuments(): Array<DocumentView> {
- return manager.SelectedDocuments.slice();
+ return Array.from(manager.SelectedDocuments.keys());
}
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index c7d4d1786..76f4bb964 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -38,7 +38,9 @@ export default class SettingsManager extends React.Component<{}> {
const { error: resultError, ...others } = await Identified.PostToServer('/internalResetPassword', { curr_pass });
if (resultError) {
// we failed
+ console.log(resultError);
}
+ console.log(others);
// do stuff with response
}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index ebf833dbe..8310a0da6 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -1,100 +1,62 @@
@import "../views/globalCssVariables";
-
-.ProseMirror-textblock-dropdown {
- min-width: 3em;
- }
-
- .ProseMirror-menu {
- margin: 0 -4px;
- line-height: 1;
- }
-
- .ProseMirror-tooltip .ProseMirror-menu {
- width: -webkit-fit-content;
- width: fit-content;
- white-space: pre;
- }
-
- .ProseMirror-menuitem {
- margin-right: 3px;
+.ProseMirror-menu-dropdown-wrap {
display: inline-block;
- z-index: 50000;
position: relative;
- }
-
- .ProseMirror-menuseparator {
- // border-right: 1px solid #ddd;
- margin-right: 3px;
- }
-
- .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu {
- font-size: 90%;
- white-space: nowrap;
- }
+}
- .ProseMirror-menu-dropdown {
+.ProseMirror-menu-dropdown {
vertical-align: 1px;
cursor: pointer;
position: relative;
- padding-right: 15px;
- margin: 3px;
+ padding: 0 15px 0 4px;
background: white;
- border-radius: 3px;
- text-align: center;
- }
-
- .ProseMirror-menu-dropdown-wrap {
- padding: 1px 0 1px 4px;
- display: inline-block;
+ border-radius: 2px;
+ text-align: left;
+ font-size: 12px;
+ white-space: nowrap;
+ margin-right: 4px;
+
+ &:after {
+ content: "";
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 4px solid currentColor;
+ opacity: .6;
+ position: absolute;
+ right: 4px;
+ top: calc(50% - 2px);
+ }
+}
+
+.ProseMirror-menu-submenu-wrap {
position: relative;
- }
-
- .ProseMirror-menu-dropdown:after {
- content: "";
- border-left: 4px solid transparent;
- border-right: 4px solid transparent;
- border-top: 4px solid currentColor;
- opacity: .6;
- position: absolute;
- right: 4px;
- top: calc(50% - 2px);
- }
-
- .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
- background: $dark-color;
- color:white;
+ margin-right: -4px;
+}
+
+.ProseMirror-menu-dropdown-menu,
+.ProseMirror-menu-submenu {
+ font-size: 12px;
+ background: white;
border: 1px solid rgb(223, 223, 223);
- padding: 2px;
- }
-
- .ProseMirror-menu-dropdown-menu {
+ min-width: 40px;
z-index: 50000;
- min-width: 6em;
- background: white;
position: absolute;
- }
-
- .linking {
- text-align: center;
- }
+ box-sizing: content-box;
- .ProseMirror-menu-dropdown-item {
- cursor: pointer;
- padding: 2px 8px 2px 4px;
- width: auto;
- z-index: 100000;
- }
-
- .ProseMirror-menu-dropdown-item:hover {
- background: white;
- }
-
- .ProseMirror-menu-submenu-wrap {
- position: relative;
- margin-right: -4px;
- }
-
- .ProseMirror-menu-submenu-label:after {
+ .ProseMirror-menu-dropdown-item {
+ cursor: pointer;
+ z-index: 100000;
+ text-align: left;
+ padding: 3px;
+
+ &:hover {
+ background-color: $light-color-secondary;
+ }
+ }
+}
+
+
+.ProseMirror-menu-submenu-label:after {
content: "";
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
@@ -103,153 +65,51 @@
position: absolute;
right: 4px;
top: calc(50% - 4px);
- }
-
- .ProseMirror-menu-submenu {
- display: none;
- min-width: 4em;
- left: 100%;
- top: -3px;
- }
-
- .ProseMirror-menu-active {
- background: #eee;
- border-radius: 4px;
- }
-
- .ProseMirror-menu-active {
- background: #eee;
- border-radius: 4px;
- }
-
- .ProseMirror-menu-disabled {
- opacity: .3;
- }
-
- .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
- display: block;
- }
-
- .ProseMirror-menubar {
- border-top-left-radius: inherit;
- border-top-right-radius: inherit;
- position: relative;
- min-height: 1em;
- color: white;
- padding: 10px 10px;
- top: 0; left: 0; right: 0;
- border-bottom: 1px solid silver;
- background:$dark-color;
- z-index: 10;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- overflow: visible;
- }
+}
.ProseMirror-icon {
display: inline-block;
- line-height: .8;
- vertical-align: -2px; /* Compensate for padding */
- padding: 2px 8px;
+ // line-height: .8;
+ // vertical-align: -2px; /* Compensate for padding */
+ // padding: 2px 8px;
cursor: pointer;
- }
-
- .ProseMirror-menu-disabled.ProseMirror-icon {
- cursor: default;
- }
-
- .ProseMirror-icon svg {
- fill:white;
- height: 1em;
- }
-
- .ProseMirror-icon span {
- vertical-align: text-top;
- }
-
- .ProseMirror ul, .ProseMirror ol {
- padding-left: 30px;
- }
-
- .ProseMirror blockquote {
- padding-left: 1em;
- border-left: 3px solid #eee;
- margin-left: 0; margin-right: 0;
- }
-
- .ProseMirror-example-setup-style img {
- cursor: default;
- }
-
- .ProseMirror-prompt {
- background: white;
- padding: 5px 10px 5px 15px;
- border: 1px solid silver;
- position: fixed;
- border-radius: 3px;
- z-index: 11;
- box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2);
- }
-
- .ProseMirror-prompt h5 {
- margin: 0;
- font-weight: normal;
- font-size: 100%;
- color: #444;
- }
-
- .ProseMirror-prompt input[type="text"],
- .ProseMirror-prompt textarea {
- background: white;
- border: none;
- outline: none;
- }
-
- .ProseMirror-prompt input[type="text"] {
- padding: 0 4px;
- }
-
- .ProseMirror-prompt-close {
- position: absolute;
- left: 2px; top: 1px;
- color: #666;
- border: none; background: transparent; padding: 0;
- }
-
- .ProseMirror-prompt-close:after {
- content: "✕";
- font-size: 12px;
- }
-
- .ProseMirror-invalid {
- background: #ffc;
- border: 1px solid #cc7;
- border-radius: 4px;
- padding: 5px 10px;
- position: absolute;
- min-width: 10em;
- }
-
- .ProseMirror-prompt-buttons {
- margin-top: 5px;
- display: none;
+
+ &.ProseMirror-menu-disabled {
+ cursor: default;
+ }
+
+ svg {
+ fill:white;
+ height: 1em;
+ }
+
+ span {
+ vertical-align: text-top;
+ }
}
-.tooltipMenu {
+.wrapper {
position: absolute;
- z-index: 20000;
- background: #121721;
- border: 1px solid silver;
- border-radius: 15px;
- //height: 60px;
- //padding: 2px 10px;
- //margin-top: 100px;
- //-webkit-transform: translateX(-50%);
- //transform: translateX(-50%);
+ pointer-events: all;
+ display: flex;
+ align-items: center;
transform: translateY(-85px);
+
+ height: 35px;
+ background: #323232;
+ border-radius: 6px;
+ box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+
+}
+
+.tooltipMenu, .basic-tools {
+ z-index: 20000;
pointer-events: all;
- height: fit-content;
- width:550px;
+ padding: 3px;
+ padding-bottom: 5px;
+ display: flex;
+ align-items: center;
+
.ProseMirror-example-setup-style hr {
padding: 2px 10px;
border: none;
@@ -265,60 +125,89 @@
}
}
-.tooltipExtras {
- position: absolute;
- z-index: 20000;
- background: #121721;
- border: 1px solid silver;
- border-radius: 15px;
- //height: 60px;
- //padding: 2px 10px;
- //margin-top: 100px;
- //-webkit-transform: translateX(-50%);
- //transform: translateX(-50%);
- transform: translateY(-115px);
- pointer-events: all;
+.menuicon {
+ width: 25px;
height: 25px;
- width:fit-content;
- .ProseMirror-example-setup-style hr {
- padding: 2px 10px;
- border: none;
- margin: 1em 0;
+ cursor: pointer;
+ text-align: center;
+ line-height: 25px;
+ margin: 0 2px;
+ border-radius: 3px;
+
+ &:hover {
+ background-color: black;
+
+ #link-drag {
+ background-color: black;
+ }
}
-
- .ProseMirror-example-setup-style hr:after {
- content: "";
- display: block;
- height: 1px;
- background-color: silver;
- line-height: 2px;
+
+ &> * {
+ margin-top: 50%;
+ margin-left: 50%;
+ transform: translate(-50%, -50%);
}
-}
-.wrapper {
- position: absolute;
- pointer-events: all;
+ svg {
+ fill: white;
+ width: 18px;
+ height: 18px;
+ }
}
- .menuicon {
- display: inline-block;
- border-right: 1px solid white(0, 0, 0, 0.2);
- //color: rgb(19, 18, 18);
- color: rgb(226, 21, 21);
- line-height: 1;
- padding: 0px 2px;
- margin: 1px;
+.menuicon-active {
+ width: 25px;
+ height: 25px;
cursor: pointer;
text-align: center;
- min-width: 10px;
-
- }
- .strong, .heading { font-weight: bold; }
- .em { font-style: italic; }
- .underline {text-decoration: underline}
- .superscript {vertical-align:super}
- .subscript { vertical-align:sub }
- .strikethrough {text-decoration-line:line-through}
+ line-height: 25px;
+ margin: 0 2px;
+ border-radius: 3px;
+
+ &:hover {
+ background-color: black;
+ }
+
+ &> * {
+ margin-top: 50%;
+ margin-left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ svg {
+ fill: greenyellow;
+ width: 18px;
+ height: 18px;
+ }
+}
+
+#colorPicker {
+ position: relative;
+
+ svg {
+ width: 18px;
+ height: 18px;
+ // margin-top: 11px;
+ }
+
+ .buttonColor {
+ position: absolute;
+ top: 24px;
+ left: 1px;
+ width: 24px;
+ height: 4px;
+ margin-top: 0;
+ }
+}
+
+#link-drag {
+ background-color: #323232;
+}
+
+.underline svg {
+ margin-top: 13px;
+}
+
.font-size-indicator {
font-size: 12px;
padding-right: 0px;
@@ -328,8 +217,9 @@
height: 20px;
text-align: center;
}
+
- .brush{
+.brush{
display: inline-block;
width: 1em;
height: 1em;
@@ -337,19 +227,146 @@
stroke: currentColor;
fill: currentColor;
margin-right: 15px;
- }
+}
- .brush-active{
+.brush-active{
display: inline-block;
width: 1em;
height: 1em;
stroke-width: 3;
- stroke: greenyellow;
fill: greenyellow;
margin-right: 15px;
- }
+}
+
+.dragger-wrapper {
+ color: #eee;
+ height: 22px;
+ padding: 0 5px;
+ box-sizing: content-box;
+ cursor: grab;
- .dragger{
- color: #eee;
- margin-left: 5px;
- } \ No newline at end of file
+ .dragger {
+ width: 18px;
+ height: 100%;
+ display: flex;
+ justify-content: space-evenly;
+ }
+
+ .dragger-line {
+ width: 2px;
+ height: 100%;
+ background-color: black;
+ }
+}
+
+.button-dropdown-wrapper {
+ display: flex;
+ align-content: center;
+
+ &:hover {
+ background-color: black;
+ }
+}
+
+.buttonSettings-dropdown {
+
+ &.ProseMirror-menu-dropdown {
+ width: 10px;
+ height: 25px;
+ margin: 0;
+ padding: 0 2px;
+ background-color: #323232;
+ text-align: center;
+
+ &:after {
+ border-top: 4px solid white;
+ right: 2px;
+ }
+
+ &:hover {
+ background-color: black;
+ }
+ }
+
+ &.ProseMirror-menu-dropdown-menu {
+ min-width: 150px;
+ left: -27px;
+ top: 31px;
+ background-color: #323232;
+ border: 1px solid #4d4d4d;
+ color: $light-color-secondary;
+ // border: none;
+ // border: 1px solid $light-color-secondary;
+ border-radius: 0 6px 6px 6px;
+ padding: 3px;
+ box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+
+ .ProseMirror-menu-dropdown-item{
+ cursor: default;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &:hover {
+ background-color: #323232;
+ }
+
+ .button-setting, .button-setting-disabled {
+ padding: 2px;
+ border-radius: 2px;
+ }
+
+ .button-setting:hover {
+ cursor: pointer;
+ background-color: black;
+ }
+
+ .separated-button {
+ border-top: 1px solid $light-color-secondary;
+ padding-top: 6px;
+ }
+
+ input {
+ color: black;
+ border: none;
+ border-radius: 1px;
+ padding: 3px;
+ }
+
+ button {
+ padding: 6px;
+ background-color: #323232;
+ border: 1px solid black;
+ border-radius: 1px;
+
+ &:hover {
+ background-color: black;
+ }
+ }
+ }
+
+
+ }
+}
+
+.colorPicker-wrapper {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ margin-top: 3px;
+ margin-left: -3px;
+ width: calc(100% + 6px);
+}
+
+button.colorPicker {
+ width: 20px;
+ height: 20px;
+ border-radius: 15px !important;
+ margin: 3px;
+ border: none !important;
+
+ &.active {
+ border: 2px solid white !important;
+ }
+} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 38471a955..5136089b3 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,6 +1,4 @@
-import { library } from '@fortawesome/fontawesome-svg-core';
-import { faListUl } from '@fortawesome/free-solid-svg-icons';
-import { action, observable } from "mobx";
+import { action } from "mobx";
import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css
import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
import { wrapInList } from 'prosemirror-schema-list';
@@ -20,97 +18,100 @@ import "./TooltipTextMenu.scss";
import { Cast, NumCast, StrCast } from '../../new_fields/Types';
import { updateBullets } from './ProsemirrorExampleTransfer';
import { DocumentDecorations } from '../views/DocumentDecorations';
+import { SelectionManager } from './SelectionManager';
+import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
const { toggleMark, setBlockType } = require("prosemirror-commands");
const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
- public tooltip: HTMLElement;
+ public static Toolbar: HTMLDivElement | undefined;
+
+ // editor state properties
private view: EditorView;
private editorProps: FieldViewProps & FormattedTextBoxProps | undefined;
- private listTypes: (NodeType | any)[];
- private fontSizes: Mark[] = [];
+
private fontStyles: Mark[] = [];
- private listTypeToIcon: Map<NodeType | any, string>;
- //private link: HTMLAnchorElement;
- private wrapper: HTMLDivElement;
- private extras: HTMLDivElement;
+ private fontSizes: Mark[] = [];
+ private listTypes: (NodeType | any)[] = [];
+ private listTypeToIcon: Map<NodeType | any, string> = new Map();
+ private _activeMarks: Mark[] = [];
+ private _marksToDoms: Map<Mark, HTMLSpanElement> = new Map();
+ private _collapsed: boolean = false;
+ // editor doms
+ public tooltip: HTMLElement = document.createElement("div");
+ private wrapper: HTMLDivElement = document.createElement("div");
+
+ // editor button doms
+ private colorDom?: Node;
+ private colorDropdownDom?: Node;
+ private highlightDom?: Node;
+ private highlightDropdownDom?: Node;
private linkEditor?: HTMLDivElement;
private linkText?: HTMLDivElement;
private linkDrag?: HTMLImageElement;
- //dropdown doms
+ private _linkDropdownDom?: Node;
+ private _brushdom?: Node;
+ private _brushDropdownDom?: Node;
private fontSizeDom?: Node;
private fontStyleDom?: Node;
private listTypeBtnDom?: Node;
+ private basicTools?: HTMLElement;
- private _activeMarks: Mark[] = [];
-
- private _collapseBtn?: MenuItem;
-
- private _brushMarks?: Set<Mark>;
- private _brushIsEmpty: boolean = true;
- private _brushdom?: Node;
-
- private _marksToDoms: Map<Mark, HTMLSpanElement> = new Map();
-
- private _collapsed: boolean = false;
constructor(view: EditorView) {
this.view = view;
- this.wrapper = document.createElement("div");
- this.tooltip = document.createElement("div");
- this.extras = document.createElement("div");
-
- this.wrapper.appendChild(this.extras);
- this.wrapper.appendChild(this.tooltip);
+ // initialize the tooltip -- sets this.tooltip
+ this.initTooltip(view);
- this.tooltip.className = "tooltipMenu";
- this.extras.className = "tooltipExtras";
+ // initialize the wrapper
+ this.wrapper = document.createElement("div");
this.wrapper.className = "wrapper";
+ this.wrapper.appendChild(this.tooltip);
- const dragger = document.createElement("span");
- dragger.className = "dragger";
- dragger.textContent = ">>>";
- this.extras.appendChild(dragger);
+ // initialize the dragger -- appends it to the wrapper
+ this.createDragger();
- this.dragElement(dragger);
+ TooltipTextMenu.Toolbar = this.wrapper;
+ }
- // this.createCollapse();
- // if (this._collapseBtn) {
- // this.tooltip.appendChild(this._collapseBtn.render(this.view).dom);
- // }
- //add the div which is the tooltip
- //view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
+ private async initTooltip(view: EditorView) {
+ // initialize tooltip dom
+ this.tooltip = document.createElement("div");
+ this.tooltip.className = "tooltipMenu";
+ this.basicTools = document.createElement("div");
+ this.basicTools.className = "basic-tools";
- //add additional icons
- library.add(faListUl);
- //add the buttons to the tooltip
+ // init buttons to the tooltip -- paths to svgs are obtained from fontawesome
let items = [
- { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") },
- { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") },
- { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") },
- { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") },
- { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") },
- { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") },
- { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') }
+ { command: toggleMark(schema.marks.strong), dom: this.svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") },
+ { command: toggleMark(schema.marks.em), dom: this.svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") },
+ { command: toggleMark(schema.marks.underline), dom: this.svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") },
+ { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") },
+ { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
+ { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") },
+ // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') }
];
+ // add menu items
this._marksToDoms = new Map();
- //add menu items
items.forEach(({ dom, command }) => {
this.tooltip.appendChild(dom);
switch (dom.title) {
case "Bold":
this._marksToDoms.set(schema.mark(schema.marks.strong), dom);
+ this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
break;
case "Italic":
this._marksToDoms.set(schema.mark(schema.marks.em), dom);
+ this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
break;
case "Underline":
this._marksToDoms.set(schema.mark(schema.marks.underline), dom);
+ this.basicTools && this.basicTools.appendChild(dom.cloneNode(true));
break;
}
@@ -129,10 +130,50 @@ export class TooltipTextMenu {
});
});
+
+ // highlight menu
+ this.highlightDom = this.createHighlightTool().render(this.view).dom;
+ this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom;
+ this.tooltip.appendChild(this.highlightDom);
+ this.tooltip.appendChild(this.highlightDropdownDom);
+
+ // color menu
+ this.colorDom = this.createColorTool().render(this.view).dom;
+ this.colorDropdownDom = this.createColorDropdown().render(this.view).dom;
+ this.tooltip.appendChild(this.colorDom);
+ this.tooltip.appendChild(this.colorDropdownDom);
+
+ // link menu
this.updateLinkMenu();
+ let dropdown = await this.createLinkDropdown();
+ this._linkDropdownDom = dropdown.render(this.view).dom;
+ this.tooltip.appendChild(this._linkDropdownDom);
+
+ // list of font styles
+ this.initFontStyles();
+
+ // font sizes
+ this.initFontSizes();
+
+ // list types
+ this.initListTypes();
+
+ // init brush tool
+ this._brushdom = this.createBrush().render(this.view).dom;
+ this.tooltip.appendChild(this._brushdom);
+ this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom;
+ this.tooltip.appendChild(this._brushDropdownDom);
+
+ // star
+ this.tooltip.appendChild(this.createStar().render(this.view).dom);
+
+ // list types dropdown
+ this.updateListItemDropdown(":", this.listTypeBtnDom);
+ await this.updateFromDash(view, undefined, undefined);
+ }
- //list of font styles
+ initFontStyles() {
this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" }));
this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" }));
this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" }));
@@ -140,8 +181,9 @@ export class TooltipTextMenu {
this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" }));
this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" }));
this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" }));
+ }
- //font sizes
+ initFontSizes() {
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 10 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 12 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 14 }));
@@ -152,8 +194,9 @@ export class TooltipTextMenu {
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 }));
this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 }));
+ }
- //list types
+ initListTypes() {
this.listTypeToIcon = new Map();
//this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":");
@@ -161,21 +204,91 @@ export class TooltipTextMenu {
this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A)");
// this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜");
this.listTypes = Array.from(this.listTypeToIcon.keys());
+ }
- //custom tools
- // this.tooltip.appendChild(this.createLink().render(this.view).dom);
+ // creates dragger element that allows dragging and collapsing (on double click)
+ // of editor and appends it to the wrapper
+ createDragger() {
+ let draggerWrapper = document.createElement("div");
+ draggerWrapper.className = "dragger-wrapper";
- this._brushdom = this.createBrush().render(this.view).dom;
- this.tooltip.appendChild(this._brushdom);
- this.tooltip.appendChild(this.createLink().render(this.view).dom);
- this.tooltip.appendChild(this.createStar().render(this.view).dom);
+ let dragger = document.createElement("div");
+ dragger.className = "dragger";
- this.updateListItemDropdown(":", this.listTypeBtnDom);
+ let line1 = document.createElement("span");
+ line1.className = "dragger-line";
+ let line2 = document.createElement("span");
+ line2.className = "dragger-line";
+ let line3 = document.createElement("span");
+ line3.className = "dragger-line";
- this.updateFromDash(view, undefined, undefined);
- TooltipTextMenu.Toolbar = this.wrapper;
+ dragger.appendChild(line1);
+ dragger.appendChild(line2);
+ dragger.appendChild(line3);
+
+ draggerWrapper.appendChild(dragger);
+
+ this.wrapper.appendChild(draggerWrapper);
+ this.dragElement(draggerWrapper);
+ }
+
+ dragElement(elmnt: HTMLElement) {
+ var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
+ if (elmnt) {
+ // if present, the header is where you move the DIV from:
+ elmnt.onpointerdown = dragMouseDown;
+ elmnt.ondblclick = onClick;
+ }
+ const self = this;
+
+ function dragMouseDown(e: PointerEvent) {
+ e = e || window.event;
+ //e.preventDefault();
+ // get the mouse cursor position at startup:
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ document.onpointerup = closeDragElement;
+ // call a function whenever the cursor moves:
+ document.onpointermove = elementDrag;
+ }
+
+ function onClick(e: MouseEvent) {
+ self._collapsed = !self._collapsed;
+ const children = self.wrapper.childNodes;
+ if (self._collapsed && children.length > 0) {
+ self.wrapper.removeChild(self.tooltip);
+ self.basicTools && self.wrapper.prepend(self.basicTools);
+ }
+ else {
+ self.wrapper.prepend(self.tooltip);
+ self.basicTools && self.wrapper.removeChild(self.basicTools);
+ }
+ }
+
+ function elementDrag(e: PointerEvent) {
+ e = e || window.event;
+ //e.preventDefault();
+ // calculate the new cursor position:
+ pos1 = pos3 - e.clientX;
+ pos2 = pos4 - e.clientY;
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ // set the element's new position:
+ // elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
+ // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
+
+ self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px";
+ self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px";
+ }
+
+ function closeDragElement() {
+ // stop moving when mouse button is released:
+ document.onpointerup = null;
+ document.onpointermove = null;
+ //self.highlightSearchTerms(self.state, ["hello"]);
+ //FormattedTextBox.Instance.unhighlightSearchTerms();
+ }
}
- public static Toolbar: HTMLDivElement | undefined;
//label of dropdown will change to given label
updateFontSizeDropdown(label: string) {
@@ -187,7 +300,7 @@ export class TooltipTextMenu {
let newfontSizeDom = (new Dropdown(fontSizeBtns, {
label: label,
- css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;"
+ css: "color:black; min-width: 60px;"
}) as MenuItem).render(this.view).dom;
if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); }
else {
@@ -206,25 +319,20 @@ export class TooltipTextMenu {
let newfontStyleDom = (new Dropdown(fontBtns, {
label: label,
- css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;"
+ css: "color:black; width: 125px;"
}) as MenuItem).render(this.view).dom;
if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); }
else {
this.tooltip.appendChild(newfontStyleDom);
}
this.fontStyleDom = newfontStyleDom;
-
}
updateLinkMenu() {
if (!this.linkEditor || !this.linkText) {
this.linkEditor = document.createElement("div");
this.linkEditor.className = "ProseMirror-icon menuicon";
- this.linkEditor.style.color = "black";
this.linkText = document.createElement("div");
- this.linkText.style.cssFloat = "left";
- this.linkText.style.marginRight = "5px";
- this.linkText.style.marginLeft = "5px";
this.linkText.setAttribute("contenteditable", "true");
this.linkText.style.whiteSpace = "nowrap";
this.linkText.style.width = "150px";
@@ -263,9 +371,7 @@ export class TooltipTextMenu {
this.linkDrag.style.width = "15px";
this.linkDrag.style.height = "15px";
this.linkDrag.title = "Drag to create link";
- this.linkDrag.style.color = "black";
- this.linkDrag.style.background = "black";
- this.linkDrag.style.cssFloat = "left";
+ this.linkDrag.id = "link-drag";
this.linkDrag.onpointerdown = (e: PointerEvent) => {
if (!this.editorProps) return;
let dragData = new DragManager.LinkDragData(this.editorProps.Document);
@@ -311,63 +417,124 @@ export class TooltipTextMenu {
e.preventDefault();
}
};
- // this.tooltip.appendChild(this.linkEditor);
}
- dragElement(elmnt: HTMLElement) {
- var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
- if (elmnt) {
- // if present, the header is where you move the DIV from:
- elmnt.onpointerdown = dragMouseDown;
- elmnt.ondblclick = onClick;
+ async getTextLinkTargetTitle() {
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = node && node.marks.find(m => m.type.name === "link");
+ if (link) {
+ let href = link.attrs.href;
+ if (href) {
+ if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+ const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ if (linkclicked) {
+ let linkDoc = await DocServer.GetRefField(linkclicked);
+ if (linkDoc instanceof Doc) {
+ let anchor1 = await Cast(linkDoc.anchor1, Doc);
+ let anchor2 = await Cast(linkDoc.anchor2, Doc);
+ let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document;
+ if (currentDoc && anchor1 && anchor2) {
+ if (Doc.AreProtosEqual(currentDoc, anchor1)) {
+ return StrCast(anchor2.title);
+ }
+ if (Doc.AreProtosEqual(currentDoc, anchor2)) {
+ return StrCast(anchor1.title);
+ }
+ }
+ }
+ }
+ } else {
+ return href;
+ }
+ } else {
+ return link.attrs.title;
+ }
}
- const self = this;
+ }
- function dragMouseDown(e: PointerEvent) {
- e = e || window.event;
- //e.preventDefault();
- // get the mouse cursor position at startup:
- pos3 = e.clientX;
- pos4 = e.clientY;
- document.onpointerup = closeDragElement;
- // call a function whenever the cursor moves:
- document.onpointermove = elementDrag;
- }
+ async createLinkDropdown() {
+ let targetTitle = await this.getTextLinkTargetTitle();
+ let input = document.createElement("input");
- function onClick(e: MouseEvent) {
- self._collapsed = !self._collapsed;
- const children = self.wrapper.childNodes;
- if (self._collapsed && children.length > 1) {
- self.wrapper.removeChild(self.tooltip);
+ // menu item for input for hyperlink url
+ // TODO: integrate search to allow users to search for a doc to link to
+ let linkInfo = new MenuItem({
+ title: "",
+ execEvent: "",
+ class: "button-setting-disabled",
+ css: "",
+ render() {
+ let p = document.createElement("p");
+ p.textContent = "Linked to:";
+
+ input.type = "text";
+ input.placeholder = "Enter URL";
+ if (targetTitle) input.value = targetTitle;
+ input.onclick = (e: MouseEvent) => {
+ input.select();
+ input.focus();
+ };
+
+ let div = document.createElement("div");
+ div.appendChild(p);
+ div.appendChild(input);
+ return div;
+ },
+ enable() { return false; },
+ run(p1, p2, p3, event) {
+ event.stopPropagation();
}
- else {
- self.wrapper.appendChild(self.tooltip);
+ });
+
+ // menu item to update/apply the hyperlink to the selected text
+ let linkApply = new MenuItem({
+ title: "",
+ execEvent: "",
+ class: "",
+ css: "",
+ render() {
+ let button = document.createElement("button");
+ button.className = "link-url-button";
+ button.textContent = "Apply hyperlink";
+ return button;
+ },
+ enable() { return false; },
+ run: (state, dispatch, view, event) => {
+ event.stopPropagation();
+ this.makeLinkToURL(input.value, "onRight");
}
- }
+ });
- function elementDrag(e: PointerEvent) {
- e = e || window.event;
- //e.preventDefault();
- // calculate the new cursor position:
- pos1 = pos3 - e.clientX;
- pos2 = pos4 - e.clientY;
- pos3 = e.clientX;
- pos4 = e.clientY;
- // set the element's new position:
- // elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
- // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
+ // menu item to remove the link
+ // TODO: allow this to be undoable
+ let self = this;
+ let deleteLink = new MenuItem({
+ title: "Delete link",
+ execEvent: "",
+ class: "separated-button",
+ css: "",
+ render() {
+ let button = document.createElement("button");
+ button.textContent = "Remove link";
+
+ let wrapper = document.createElement("div");
+ wrapper.appendChild(button);
+ return wrapper;
+ },
+ enable() { return true; },
+ async run() {
+ self.deleteLink();
+ // update link dropdown
+ let dropdown = await self.createLinkDropdown();
+ let newLinkDropdowndom = dropdown.render(self.view).dom;
+ self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom);
+ self._linkDropdownDom = newLinkDropdowndom;
+ }
+ });
- self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px";
- self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px";
- }
- function closeDragElement() {
- // stop moving when mouse button is released:
- document.onpointerup = null;
- document.onpointermove = null;
- //self.highlightSearchTerms(self.state, ["hello"]);
- //FormattedTextBox.Instance.unhighlightSearchTerms();
- }
+ let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem;
+ return linkDropdown;
}
// makeLinkWithState = (state: EditorState, target: string, location: string) => {
@@ -385,6 +552,15 @@ export class TooltipTextMenu {
return "";
}
+ makeLinkToURL = (target: String, lcoation: string) => {
+ 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 });
+ 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");
+ }
+
deleteLink = () => {
let node = this.view.state.selection.$from.nodeAfter;
let link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link);
@@ -402,19 +578,69 @@ export class TooltipTextMenu {
}
}
}
+ }
-
+ deleteLinkItem() {
+ const icon = {
+ height: 16, width: 16,
+ path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z"
+ };
+ return new MenuItem({
+ title: "Delete Link",
+ label: "X",
+ icon: icon,
+ css: "color: red",
+ class: "summarize",
+ execEvent: "",
+ run: (state, dispatch) => {
+ this.deleteLink();
+ }
+ });
}
- public static insertStar(state: EditorState<any>, dispatch: any) {
- if (state.selection.empty) return false;
- let mark = state.schema.marks.highlight.create();
- let tr = state.tr;
- tr.addMark(state.selection.from, state.selection.to, mark);
- let content = tr.selection.content();
- let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
- dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
- return true;
+ createLink() {
+ let markType = schema.marks.link;
+ return new MenuItem({
+ title: "Add or remove link",
+ label: "Add or remove link",
+ execEvent: "",
+ icon: icons.link,
+ css: "color:white;",
+ class: "menuicon",
+ enable(state) { return !state.selection.empty; },
+ run: (state, dispatch, view) => {
+ // to remove link
+ let curLink = "";
+ if (this.markActive(state, markType)) {
+
+ let { from, $from, to, empty } = state.selection;
+ let node = state.doc.nodeAt(from);
+ node && node.marks.map(m => {
+ m.type === markType && (curLink = m.attrs.href);
+ });
+ //toggleMark(markType)(state, dispatch);
+ //return true;
+ }
+ // to create link
+ openPrompt({
+ title: "Create a link",
+ fields: {
+ href: new TextField({
+ value: curLink,
+ label: "Link Target",
+ required: true
+ }),
+ title: new TextField({ label: "Title" })
+ },
+ callback(attrs: any) {
+ toggleMark(markType, attrs)(view.state, view.dispatch);
+ view.focus();
+ },
+ flyout_top: 0,
+ flyout_left: 0
+ });
+ }
+ });
}
//will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
@@ -439,6 +665,369 @@ export class TooltipTextMenu {
this.tooltip.appendChild(listTypeBtn);
return listTypeBtn;
}
+
+ createStar() {
+ return new MenuItem({
+ title: "Summarize",
+ label: "Summarize",
+ icon: icons.join,
+ css: "color:white;",
+ class: "menuicon",
+ execEvent: "",
+ run: (state, dispatch) => {
+ TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
+ }
+
+ });
+ }
+
+ public static insertStar(state: EditorState<any>, dispatch: any) {
+ if (state.selection.empty) return false;
+ let mark = state.schema.marks.highlight.create();
+ let tr = state.tr;
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ let content = tr.selection.content();
+ let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+ dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+ return true;
+ }
+
+ public static insertComment(state: EditorState<any>, dispatch: any) {
+ if (state.selection.empty) return false;
+ let mark = state.schema.marks.highlight.create();
+ let tr = state.tr;
+ tr.addMark(state.selection.from, state.selection.to, mark);
+ let content = tr.selection.content();
+ let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() });
+ dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark));
+ return true;
+ }
+
+ createHighlightTool() {
+ return new MenuItem({
+ title: "Highlight",
+ css: "color:white;",
+ class: "menuicon",
+ execEvent: "",
+ render() {
+ let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("viewBox", "-100 -100 650 650");
+ let path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+ path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z");
+ svg.appendChild(path);
+
+ let color = document.createElement("div");
+ color.className = "buttonColor";
+ color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString();
+
+ let wrapper = document.createElement("div");
+ wrapper.id = "colorPicker";
+ wrapper.appendChild(svg);
+ wrapper.appendChild(color);
+ return wrapper;
+ },
+ run: (state, dispatch) => {
+ TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch);
+ }
+ });
+ }
+
+ public static insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
+ if (state.selection.empty) return false;
+
+ let highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color });
+ dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark));
+ }
+
+ createHighlightDropdown() {
+ // menu item for color picker
+ let self = this;
+ let colors = new MenuItem({
+ title: "",
+ execEvent: "",
+ class: "button-setting-disabled",
+ css: "",
+ render() {
+ let p = document.createElement("p");
+ p.textContent = "Change highlight:";
+
+ let colorsWrapper = document.createElement("div");
+ colorsWrapper.className = "colorPicker-wrapper";
+
+ let colors = [
+ PastelSchemaPalette.get("pink2"),
+ PastelSchemaPalette.get("purple4"),
+ PastelSchemaPalette.get("bluegreen1"),
+ PastelSchemaPalette.get("yellow4"),
+ PastelSchemaPalette.get("red2"),
+ PastelSchemaPalette.get("bluegreen7"),
+ PastelSchemaPalette.get("bluegreen5"),
+ PastelSchemaPalette.get("orange1"),
+ "white",
+ "transparent"
+ ];
+
+ colors.forEach(color => {
+ let button = document.createElement("button");
+ button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker";
+ if (color) {
+ button.style.backgroundColor = color;
+ button.textContent = color === "transparent" ? "X" : "";
+ button.onclick = e => {
+ TooltipTextMenuManager.Instance.highlight = color;
+
+ TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch);
+
+ // update color menu
+ let highlightDom = self.createHighlightTool().render(self.view).dom;
+ let highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom;
+ self.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom);
+ self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom);
+ self.highlightDom = highlightDom;
+ self.highlightDropdownDom = highlightDropdownDom;
+ };
+ }
+ colorsWrapper.appendChild(button);
+ });
+
+ let div = document.createElement("div");
+ div.appendChild(p);
+ div.appendChild(colorsWrapper);
+ return div;
+ },
+ enable() { return false; },
+ run(p1, p2, p3, event) {
+ event.stopPropagation();
+ }
+ });
+
+ let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
+ return colorDropdown;
+ }
+
+ createColorTool() {
+ return new MenuItem({
+ title: "Color",
+ css: "color:white;",
+ class: "menuicon",
+ execEvent: "",
+ render() {
+ let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("viewBox", "-100 -100 650 650");
+ let path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+ path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z");
+ svg.appendChild(path);
+
+ let color = document.createElement("div");
+ color.className = "buttonColor";
+ color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString();
+
+ let wrapper = document.createElement("div");
+ wrapper.id = "colorPicker";
+ wrapper.appendChild(svg);
+ wrapper.appendChild(color);
+ return wrapper;
+ },
+ run: (state, dispatch) => {
+ TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch);
+ }
+ });
+ }
+
+ public static insertColor(color: String, state: EditorState<any>, dispatch: any) {
+ if (state.selection.empty) return false;
+
+ let colorMark = state.schema.mark(state.schema.marks.color, { color: color });
+ dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark));
+ }
+
+ createColorDropdown() {
+ // menu item for color picker
+ let self = this;
+ let colors = new MenuItem({
+ title: "",
+ execEvent: "",
+ class: "button-setting-disabled",
+ css: "",
+ render() {
+ let p = document.createElement("p");
+ p.textContent = "Change color:";
+
+ let colorsWrapper = document.createElement("div");
+ colorsWrapper.className = "colorPicker-wrapper";
+
+ let colors = [
+ DarkPastelSchemaPalette.get("pink2"),
+ DarkPastelSchemaPalette.get("purple4"),
+ DarkPastelSchemaPalette.get("bluegreen1"),
+ DarkPastelSchemaPalette.get("yellow4"),
+ DarkPastelSchemaPalette.get("red2"),
+ DarkPastelSchemaPalette.get("bluegreen7"),
+ DarkPastelSchemaPalette.get("bluegreen5"),
+ DarkPastelSchemaPalette.get("orange1"),
+ "#757472",
+ "#000"
+ ];
+
+ colors.forEach(color => {
+ let button = document.createElement("button");
+ button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker";
+ if (color) {
+ button.style.backgroundColor = color;
+ button.onclick = e => {
+ TooltipTextMenuManager.Instance.color = color;
+
+ TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch);
+
+ // update color menu
+ let colorDom = self.createColorTool().render(self.view).dom;
+ let colorDropdownDom = self.createColorDropdown().render(self.view).dom;
+ self.colorDom && self.tooltip.replaceChild(colorDom, self.colorDom);
+ self.colorDropdownDom && self.tooltip.replaceChild(colorDropdownDom, self.colorDropdownDom);
+ self.colorDom = colorDom;
+ self.colorDropdownDom = colorDropdownDom;
+ };
+ }
+ colorsWrapper.appendChild(button);
+ });
+
+ let div = document.createElement("div");
+ div.appendChild(p);
+ div.appendChild(colorsWrapper);
+ return div;
+ },
+ enable() { return false; },
+ run(p1, p2, p3, event) {
+ event.stopPropagation();
+ }
+ });
+
+ let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem;
+ return colorDropdown;
+ }
+
+ createBrush(active: boolean = false) {
+ const icon = {
+ height: 32, width: 32,
+ path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z"
+ };
+ let self = this;
+ return new MenuItem({
+ title: "Brush tool",
+ label: "Brush tool",
+ icon: icon,
+ css: "color:white;",
+ class: active ? "menuicon-active" : "menuicon",
+ execEvent: "",
+ run: (state, dispatch) => {
+ this.brush_function(state, dispatch);
+
+ // update dropdown with marks
+ let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
+ self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
+ self._brushDropdownDom = newBrushDropdowndom;
+ },
+ active: (state) => {
+ return true;
+ }
+ });
+ }
+
+ brush_function(state: EditorState<any>, dispatch: any) {
+ if (TooltipTextMenuManager.Instance._brushIsEmpty) {
+ const selected_marks = this.getMarksInSelection(this.view.state);
+ if (this._brushdom) {
+ if (selected_marks.size >= 0) {
+ TooltipTextMenuManager.Instance._brushMarks = selected_marks;
+ const newbrush = this.createBrush(true).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
+ TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
+ }
+ }
+ }
+ else {
+ let { from, to, $from } = this.view.state.selection;
+ if (this._brushdom) {
+ if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
+ if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) {
+ this.view.dispatch(this.view.state.tr.removeMark(from, to));
+ Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
+ const markType = mark.type;
+ this.changeToMarkInGroup(markType, this.view, []);
+ });
+ }
+ }
+ else {
+ const newbrush = this.createBrush(false).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
+ TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
+ }
+ }
+ }
+ }
+
+ createBrushDropdown(active: boolean = false) {
+ let label = "Stored marks: ";
+ if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) {
+ TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => {
+ const markType = mark.type;
+ label += markType.name;
+ label += ", ";
+ });
+ label = label.substring(0, label.length - 2);
+ } else {
+ label = "No marks are currently stored";
+ }
+
+
+ let brushInfo = new MenuItem({
+ title: "",
+ label: label,
+ execEvent: "",
+ class: "button-setting-disabled",
+ css: "",
+ enable() { return false; },
+ run(p1, p2, p3, event) {
+ event.stopPropagation();
+ }
+ });
+
+ let self = this;
+ let clearBrush = new MenuItem({
+ title: "Clear brush",
+ execEvent: "",
+ class: "separated-button",
+ css: "",
+ render() {
+ let button = document.createElement("button");
+ button.textContent = "Clear brush";
+
+ let wrapper = document.createElement("div");
+ wrapper.appendChild(button);
+ return wrapper;
+ },
+ enable() { return true; },
+ run() {
+ TooltipTextMenuManager.Instance._brushIsEmpty = true;
+ TooltipTextMenuManager.Instance._brushMarks = new Set();
+
+ // update brush tool
+ // TODO: this probably isn't very clean
+ let newBrushdom = self.createBrush().render(self.view).dom;
+ self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom);
+ self._brushdom = newBrushdom;
+ let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
+ self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
+ self._brushDropdownDom = newBrushDropdowndom;
+ }
+ });
+
+ let hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0;
+ let brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem;
+ return brushDom;
+ }
//for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
let { $cursor, ranges } = view.state.selection as TextSelection;
@@ -594,7 +1183,7 @@ export class TooltipTextMenu {
title: "",
label: label,
execEvent: "",
- class: "menuicon",
+ class: "dropdown-item",
css: css,
enable() { return true; },
run() {
@@ -609,7 +1198,7 @@ export class TooltipTextMenu {
title: "",
label: label,
execEvent: "",
- class: "menuicon",
+ class: "dropdown-item",
css: css,
enable() { return true; },
run() {
@@ -618,187 +1207,6 @@ export class TooltipTextMenu {
});
}
- createStar() {
- return new MenuItem({
- title: "Summarize",
- label: "Summarize",
- icon: icons.join,
- css: "color:white;",
- class: "summarize",
- execEvent: "",
- run: (state, dispatch) => {
- TooltipTextMenu.insertStar(this.view.state, this.view.dispatch);
- }
-
- });
- }
-
- deleteLinkItem() {
- const icon = {
- height: 16, width: 16,
- path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z"
- };
- return new MenuItem({
- title: "Delete Link",
- label: "X",
- icon: icon,
- css: "color: red",
- class: "summarize",
- execEvent: "",
- run: (state, dispatch) => {
- this.deleteLink();
- }
- });
- }
-
- createBrush(active: boolean = false) {
- const icon = {
- height: 32, width: 32,
- path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z"
- };
- return new MenuItem({
- title: "Brush tool",
- label: "Brush tool",
- icon: icon,
- css: "color:white;",
- class: active ? "brush-active" : "brush",
- execEvent: "",
- run: (state, dispatch) => {
- this.brush_function(state, dispatch);
- },
- active: (state) => {
- return true;
- }
- });
- }
-
- // selectionchanged event handler
-
- brush_function(state: EditorState<any>, dispatch: any) {
- if (this._brushIsEmpty) {
- const selected_marks = this.getMarksInSelection(this.view.state);
- if (this._brushdom) {
- if (selected_marks.size >= 0) {
- this._brushMarks = selected_marks;
- const newbrush = this.createBrush(true).render(this.view).dom;
- this.tooltip.replaceChild(newbrush, this._brushdom);
- this._brushdom = newbrush;
- this._brushIsEmpty = !this._brushIsEmpty;
- }
- }
- }
- else {
- let { from, to, $from } = this.view.state.selection;
- if (this._brushdom) {
- if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
- if (this._brushMarks && to - from > 0) {
- this.view.dispatch(this.view.state.tr.removeMark(from, to));
- Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
- const markType = mark.type;
- if (mark.type === schema.marks.pFontFamily) this.changeToFontFamily(mark, this.view);
- else if (mark.type === schema.marks.pFontSize) this.changeToFontSize(mark, this.view);
- else this.changeToMarkInGroup(markType, this.view, []);
- });
- }
- }
- else {
- const newbrush = this.createBrush(false).render(this.view).dom;
- this.tooltip.replaceChild(newbrush, this._brushdom);
- this._brushdom = newbrush;
- this._brushIsEmpty = !this._brushIsEmpty;
- }
- }
- }
-
-
- }
-
- createCollapse() {
- this._collapseBtn = new MenuItem({
- title: "Collapse",
- //label: "Collapse",
- icon: icons.join,
- execEvent: "",
- css: "color:white;",
- class: "summarize",
- run: () => {
- this.collapseToolTip();
- }
- });
- }
-
- collapseToolTip() {
- if (this._collapseBtn) {
- if (this._collapseBtn.spec.title === "Collapse") {
- // const newcollapseBtn = new MenuItem({
- // title: "Expand",
- // icon: icons.join,
- // execEvent: "",
- // css: "color:white;",
- // class: "summarize",
- // run: (state, dispatch, view) => {
- // this.collapseToolTip();
- // }
- // });
- // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom);
- // this._collapseBtn = newcollapseBtn;
- this.tooltip.style.width = "30px";
- this._collapseBtn.spec.title = "Expand";
- this._collapseBtn.render(this.view);
- }
- else {
- this._collapseBtn.spec.title = "Collapse";
- this.tooltip.style.width = "550px";
- this._collapseBtn.render(this.view);
- }
- }
- }
-
- createLink() {
- let markType = schema.marks.link;
- return new MenuItem({
- title: "Add or remove link",
- label: "Add or remove link",
- execEvent: "",
- icon: icons.link,
- css: "color:white;",
- class: "menuicon",
- enable(state) { return !state.selection.empty; },
- run: (state, dispatch, view) => {
- // to remove link
- let curLink = "";
- if (this.markActive(state, markType)) {
-
- let { from, $from, to, empty } = state.selection;
- let node = state.doc.nodeAt(from);
- node && node.marks.map(m => {
- m.type === markType && (curLink = m.attrs.href);
- });
- //toggleMark(markType)(state, dispatch);
- //return true;
- }
- // to create link
- openPrompt({
- title: "Create a link",
- fields: {
- href: new TextField({
- value: curLink,
- label: "Link Target",
- required: true
- }),
- title: new TextField({ label: "Title" })
- },
- callback(attrs: any) {
- toggleMark(markType, attrs)(view.state, view.dispatch);
- view.focus();
- },
- flyout_top: 0,
- flyout_left: 0
- });
- }
- });
- }
-
//makes a button for the drop down FOR NODE TYPES
//css is the style you want applied to the button
dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
@@ -806,7 +1214,7 @@ export class TooltipTextMenu {
title: "",
label: label,
execEvent: "",
- class: "menuicon",
+ class: "dropdown-item",
css: css,
enable() { return true; },
run() {
@@ -815,7 +1223,7 @@ export class TooltipTextMenu {
});
}
- markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
+ markActive = function(state: EditorState<any>, type: MarkType<Schema<string, string>>) {
let { from, $from, to, empty } = state.selection;
if (empty) return type.isInSet(state.storedMarks || $from.marks());
else return state.doc.rangeHasMark(from, to, type);
@@ -831,6 +1239,21 @@ export class TooltipTextMenu {
return span;
}
+ svgIcon(name: string, title: string = name, dpath: string) {
+ let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("viewBox", "-100 -100 650 650");
+ let path = document.createElementNS('http://www.w3.org/2000/svg', "path");
+ path.setAttributeNS(null, "d", dpath);
+ svg.appendChild(path);
+
+ let span = document.createElement("span");
+ span.className = name + " menuicon";
+ span.title = title;
+ span.appendChild(svg);
+
+ return span;
+ }
+
//method for checking whether node can be inserted
canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) {
let $from = state.selection.$from;
@@ -893,7 +1316,7 @@ export class TooltipTextMenu {
update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); }
//updates the tooltip menu when the selection changes
- public updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
+ public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) {
if (!view) {
console.log("no editor? why?");
return;
@@ -913,12 +1336,18 @@ export class TooltipTextMenu {
//this.tooltip.style.display = "none";
//return;
}
- //UPDATE LIST ITEM DROPDOWN
+
+ // update link dropdown
+ let linkDropdown = await this.createLinkDropdown();
+ let newLinkDropdowndom = linkDropdown.render(this.view).dom;
+ this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom);
+ this._linkDropdownDom = newLinkDropdowndom;
//UPDATE FONT STYLE DROPDOWN
let activeStyles = this.activeFontFamilyOnSelection();
if (activeStyles !== undefined) {
if (activeStyles.length === 1) {
+ console.log("updating font style dropdown", activeStyles[0]);
activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]);
} else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default");
}
@@ -935,33 +1364,19 @@ export class TooltipTextMenu {
}
update_mark_doms() {
this.reset_mark_doms();
- let foundlink = false;
- let children = this.extras.childNodes;
this._activeMarks.forEach((mark) => {
if (this._marksToDoms.has(mark)) {
let dom = this._marksToDoms.get(mark);
if (dom) dom.style.color = "greenyellow";
}
- if (children.length > 1) {
- foundlink = true;
- }
- if (mark.type.name === "link" && children.length === 1) {
- // let del = document.createElement("button");
- // del.textContent = "X";
- // del.style.color = "red";
- // del.style.height = "10px";
- // del.style.width = "10px";
- // del.style.marginLeft = "5px";
- // del.onclick = this.deleteLink;
- // this.extras.appendChild(del);
- let del = this.deleteLinkItem().render(this.view).dom;
- this.extras.appendChild(del);
- foundlink = true;
- }
});
- if (!foundlink) {
- if (children.length > 1) {
- this.extras.removeChild(children[1]);
+
+ // keeps brush tool highlighted if active when switching between textboxes
+ if (!TooltipTextMenuManager.Instance._brushIsEmpty) {
+ if (this._brushdom) {
+ const newbrush = this.createBrush(true).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
}
}
@@ -1000,12 +1415,10 @@ export class TooltipTextMenu {
let activeMarks: MarkType[];
if (!empty) {
activeMarks = markGroup.filter(mark => {
- if (dispatch) {
- let has = false;
- for (let i = 0; !has && i < ranges.length; i++) {
- let { $from, $to } = ranges[i];
- return state.doc.rangeHasMark($from.pos, $to.pos, mark);
- }
+ let has = false;
+ for (let i = 0; !has && i < ranges.length; i++) {
+ let { $from, $to } = ranges[i];
+ return state.doc.rangeHasMark($from.pos, $to.pos, mark);
}
return false;
});
@@ -1024,13 +1437,11 @@ export class TooltipTextMenu {
}
this._activeMarks = ref_node.marks;
activeMarks = markGroup.filter(mark_type => {
- if (dispatch) {
- if (mark_type === state.schema.marks.pFontSize) {
- return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
- }
- let mark = state.schema.mark(mark_type);
- return ref_node.marks.includes(mark);
+ if (mark_type === state.schema.marks.pFontSize) {
+ return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
}
+ let mark = state.schema.mark(mark_type);
+ return ref_node.marks.includes(mark);
return false;
});
}
@@ -1069,6 +1480,40 @@ export class TooltipTextMenu {
}
destroy() {
- this.wrapper.remove();
+ // this.wrapper.remove();
+ }
+}
+
+
+class TooltipTextMenuManager {
+ private static _instance: TooltipTextMenuManager;
+
+ public pinnedX: number = 0;
+ public pinnedY: number = 0;
+ public unpinnedX: number = 0;
+ public unpinnedY: number = 0;
+ private _isPinned: boolean = false;
+
+ public _brushMarks: Set<Mark> | undefined;
+ public _brushIsEmpty: boolean = true;
+
+ public color: String = "#000";
+ public highlight: String = "transparent";
+
+ public activeMenu: TooltipTextMenu | undefined;
+
+ static get Instance() {
+ if (!TooltipTextMenuManager._instance) {
+ TooltipTextMenuManager._instance = new TooltipTextMenuManager();
+ }
+ return TooltipTextMenuManager._instance;
+ }
+
+ public get isPinned() {
+ return this._isPinned;
+ }
+
+ public toggleIsPinned() {
+ this._isPinned = !this._isPinned;
}
}
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
new file mode 100644
index 000000000..f3da5f284
--- /dev/null
+++ b/src/client/views/AntimodeMenu.scss
@@ -0,0 +1,29 @@
+.antimodeMenu-cont {
+ position: absolute;
+ z-index: 10000;
+ height: 35px;
+ background: #323232;
+ box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+ border-radius: 0px 6px 6px 6px;
+ overflow: hidden;
+ display: flex;
+
+ .antimodeMenu-button {
+ background-color: transparent;
+ width: 35px;
+ height: 35px;
+ }
+
+ .antimodeMenu-button:hover {
+ background-color: #121212;
+ }
+
+ .antimodeMenu-dragger {
+ height: 100%;
+ transition: width .2s;
+ background-image: url("https://logodix.com/logo/1020374.png");
+ background-size: 90% 100%;
+ background-repeat: no-repeat;
+ background-position: left center;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
new file mode 100644
index 000000000..408df8bc2
--- /dev/null
+++ b/src/client/views/AntimodeMenu.tsx
@@ -0,0 +1,128 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+import "./AntimodeMenu.scss";
+
+/**
+ * This is an abstract class that serves as the base for a PDF-style or Marquee-style
+ * menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
+ */
+export default abstract class AntimodeMenu extends React.Component {
+ protected _offsetY: number = 0;
+ protected _offsetX: number = 0;
+ protected _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ protected _dragging: boolean = false;
+
+ @observable protected _top: number = -300;
+ @observable protected _left: number = -300;
+ @observable protected _opacity: number = 1;
+ @observable protected _transition: string = "opacity 0.5s";
+ @observable protected _transitionDelay: string = "";
+
+ @observable public Pinned: boolean = false;
+
+ @action
+ /**
+ * @param x
+ * @param y
+ * @param forceJump: If the menu is pinned down, do you want to force it to jump to the new location?
+ * Called when you want the menu to show up at a location
+ */
+ public jumpTo = (x: number, y: number, forceJump: boolean = false) => {
+ if (!this.Pinned || forceJump) {
+ this._transition = this._transitionDelay = "";
+ this._opacity = 1;
+ this._left = x;
+ this._top = y;
+ }
+ }
+
+ @action
+ /**
+ * @param forceOut: Do you want the menu to disappear immediately or to slowly fadeout?
+ * Called when you want the menu to disappear
+ */
+ public fadeOut = (forceOut: boolean) => {
+ if (!this.Pinned) {
+ if (this._opacity === 0.2) {
+ this._transition = "opacity 0.1s";
+ this._transitionDelay = "";
+ this._opacity = 0;
+ this._left = this._top = -300;
+ }
+
+ if (forceOut) {
+ this._transition = "";
+ this._transitionDelay = "";
+ this._opacity = 0;
+ this._left = this._top = -300;
+ }
+ }
+ }
+
+ @action
+ protected pointerLeave = (e: React.PointerEvent) => {
+ if (!this.Pinned) {
+ this._transition = "opacity 0.5s";
+ this._transitionDelay = "1s";
+ this._opacity = 0.2;
+ setTimeout(() => this.fadeOut(false), 3000);
+ }
+ }
+
+ @action
+ protected pointerEntered = (e: React.PointerEvent) => {
+ this._transition = "opacity 0.1s";
+ this._transitionDelay = "";
+ this._opacity = 1;
+ }
+
+ @action
+ protected togglePin = (e: React.MouseEvent) => {
+ this.Pinned = !this.Pinned;
+ }
+
+ protected dragStart = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragging);
+ document.addEventListener("pointermove", this.dragging);
+ 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();
+ }
+
+ @action
+ protected dragging = (e: PointerEvent) => {
+ this._left = e.pageX - this._offsetX;
+ this._top = e.pageY - this._offsetY;
+
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ protected dragEnd = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragging);
+ document.removeEventListener("pointerup", this.dragEnd);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ protected handleContextMenu = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ protected getElement(buttons: JSX.Element[]) {
+ return (
+ <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
+ style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
+ {buttons}
+ <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/CollectionLinearView.scss b/src/client/views/CollectionLinearView.scss
index 4423a7020..81210d7ae 100644
--- a/src/client/views/CollectionLinearView.scss
+++ b/src/client/views/CollectionLinearView.scss
@@ -17,6 +17,7 @@
height: 18px;
margin-top:auto;
margin-bottom:auto;
+ margin-right: 3px;
cursor: pointer;
transition: transform 0.2s;
}
@@ -51,8 +52,9 @@
.collectionLinearView-docBtn, .collectionLinearView-docBtn-scalable {
position:relative;
- margin-top: auto;
- margin-bottom: auto;
+ margin:auto;
+ margin-left: 3px;
+ transform-origin: center 80%;
}
.collectionLinearView-docBtn-scalable:hover {
transform: scale(1.15);
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 7c6d33d36..f718735a8 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -22,18 +22,17 @@ const LinearDocument = makeInterface(documentSchema);
export class CollectionLinearView extends CollectionSubView(LinearDocument) {
@observable public addMenuToggle = React.createRef<HTMLInputElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
- private _heightDisposer?: IReactionDisposer;
- private _spacing = 20;
+ private _widthDisposer?: IReactionDisposer;
componentWillUnmount() {
this._dropDisposer && this._dropDisposer();
- this._heightDisposer && this._heightDisposer();
+ this._widthDisposer && this._widthDisposer();
}
componentDidMount() {
// is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported).
- this._heightDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
- () => this.props.Document.width = 18 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+ this._widthDisposer = reaction(() => NumCast(this.props.Document.height, 0) + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
+ () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
{ fireImmediately: true }
);
}
@@ -52,6 +51,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
let { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
return new Transform(-translateX, -translateY, 1 / scale);
}
+
render() {
let guid = Utils.GenerateGuid();
return <div className="collectionLinearView-outer">
@@ -60,19 +60,16 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
<label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
- <div className="collectionLinearView-content">
+ <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document.width, 25) }}>
{this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => {
let nested = pair.layout.viewType === CollectionViewType.Linear;
let dref = React.createRef<HTMLDivElement>();
let nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension());
- let scalingContent = nested ? 1 : this.dimension() / (this._spacing + nativeWidth);
- let scalingBox = nested ? 1 : this.dimension() / nativeWidth;
- let deltaSize = nativeWidth * scalingBox - nativeWidth * scalingContent;
+ let deltaSize = nativeWidth * .15 / 2;
return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
style={{
- width: nested ? pair.layout[WidthSym]() : this.dimension(),
- height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension(),
- transform: nested ? undefined : `translate(${deltaSize / 2}px, ${deltaSize / 2}px)`
+ width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
+ height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
}} >
<DocumentView
Document={pair.layout}
@@ -85,9 +82,9 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
ruleProvider={undefined}
onClick={undefined}
ScreenToLocalTransform={this.getTransform(dref)}
- ContentScaling={() => scalingContent} // ugh - need to get rid of this inline function to avoid recomputing
- PanelWidth={() => nested ? pair.layout[WidthSym]() : this.dimension()}
- PanelHeight={() => nested ? pair.layout[HeightSym]() : this.dimension()}
+ ContentScaling={returnOne}
+ PanelWidth={nested ? pair.layout[WidthSym] : () => this.dimension()}// ugh - need to get rid of this inline function to avoid recomputing
+ PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()}
renderDepth={this.props.renderDepth + 1}
focus={emptyFunction}
backgroundColor={returnEmptyString}
@@ -101,8 +98,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
</DocumentView>
</div>;
})}
- {/* <li key="undoTest"><button className="add-button round-button" title="Click if undo isn't working" onClick={() => UndoManager.TraceOpenBatches()}><FontAwesomeIcon icon="exclamation" size="sm" /></button></li> */}
-
</div>
</div>
</div>;
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index ae4b7cf3a..1bd1006a8 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Doc } from '../../new_fields/Doc';
-import { computed, action } from 'mobx';
+import { Touchable } from './Touchable';
+import { computed, action, observable } from 'mobx';
import { Cast } from '../../new_fields/Types';
import { listSpec } from '../../new_fields/Schema';
import { InkingControl } from './InkingControl';
@@ -13,7 +14,7 @@ interface DocComponentProps {
Document: Doc;
}
export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
- class Component extends React.Component<P> {
+ class Component extends Touchable<P> {
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document(): T { return schemaCtor(this.props.Document); }
@computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document)); }
@@ -26,7 +27,7 @@ interface DocExtendableProps {
Document: Doc;
DataDoc?: Doc;
fieldKey: string;
- isSelected: () => boolean;
+ isSelected: (outsideReaction?: boolean) => boolean;
renderDepth: number;
}
export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCtor: (doc: Doc) => T) {
@@ -34,9 +35,9 @@ export function DocExtendableComponent<P extends DocExtendableProps, T>(schemaCt
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document(): T { return schemaCtor(this.props.Document); }
@computed get layoutDoc() { return Doc.Layout(this.props.Document); }
- @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
+ @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; }
@computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
- active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools
+ active = (outsideReaction?: boolean) => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools
}
return Component;
}
@@ -48,12 +49,12 @@ interface DocAnnotatableProps {
DataDoc?: Doc;
fieldKey: string;
whenActiveChanged: (isActive: boolean) => void;
- isSelected: () => boolean;
+ isSelected: (outsideReaction?: boolean) => boolean;
renderDepth: number;
}
export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) {
class Component extends React.Component<P> {
- _isChildActive = false;
+ @observable _isChildActive = false;
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document(): T { return schemaCtor(this.props.Document); }
@computed get layoutDoc() { return Doc.Layout(this.props.Document); }
@@ -80,9 +81,11 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this.annotationsKey, doc) ? true : false;
}
- whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
- active = () => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
- (this.props.Document.forceActive || this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+ whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
+ active = (outsideReaction?: boolean) => ((InkingControl.Instance.selectedTool === InkTool.None && !this.props.Document.isBackground) &&
+ (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
+ annotationsActive = (outsideReaction?: boolean) => (InkingControl.Instance.selectedTool !== InkTool.None ||
+ (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false)
}
return Component;
} \ No newline at end of file
diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss
index 8cd419bbe..db6bf2ba0 100644
--- a/src/client/views/DocumentButtonBar.scss
+++ b/src/client/views/DocumentButtonBar.scss
@@ -2,54 +2,23 @@
$linkGap : 3px;
-.linkFlyout {
+.documentButtonBar-linkFlyout {
grid-column: 2/4;
}
-.linkButton-empty:hover {
+.documentButtonBar-linkButton-empty:hover {
background: $main-accent;
transform: scale(1.05);
cursor: pointer;
}
-.linkButton-nonempty:hover {
+.documentButtonBar-linkButton-nonempty:hover {
background: $main-accent;
transform: scale(1.05);
cursor: pointer;
}
-
-.documentButtonBar {
- margin-top: $linkGap;
- grid-column: 1/4;
- width: max-content;
- height: auto;
- display: flex;
- flex-direction: row;
-}
-
-.linkButtonWrapper {
- pointer-events: auto;
- padding-right: 5px;
- width: 25px;
-}
-
-.linkButton-linker {
- height: 20px;
- width: 20px;
- text-align: center;
- border-radius: 50%;
- pointer-events: auto;
- color: $dark-color;
- border: $dark-color 1px solid;
-}
-
-.linkButton-linker:hover {
- cursor: pointer;
- transform: scale(1.05);
-}
-
-.linkButton-empty,
-.linkButton-nonempty {
+.documentButtonBar-linkButton-empty,
+.documentButtonBar-linkButton-nonempty {
height: 20px;
width: 20px;
border-radius: 50%;
@@ -73,57 +42,38 @@ $linkGap : 3px;
}
}
-.templating-menu {
- position: absolute;
- pointer-events: auto;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
+.documentButtonBar {
+ margin-top: $linkGap;
+ grid-column: 1/4;
+ width: max-content;
+ height: auto;
display: flex;
- justify-content: center;
- align-items: center;
+ flex-direction: row;
}
-.templating-button,
-.docDecs-tagButton {
- width: 20px;
+.documentButtonBar-button {
+ pointer-events: auto;
+ padding-right: 5px;
+ width: 25px;
+}
+
+.documentButtonBar-linker {
height: 20px;
- border-radius: 50%;
- opacity: 0.9;
- font-size: 14;
- background-color: $dark-color;
- color: $light-color;
+ width: 20px;
text-align: center;
- cursor: pointer;
-
- &:hover {
- background: $main-accent;
- transform: scale(1.05);
- }
+ border-radius: 50%;
+ pointer-events: auto;
+ color: $dark-color;
+ border: $dark-color 1px solid;
+ transition: 0.2s ease all;
}
-#template-list {
- position: absolute;
- top: 25px;
- left: 0px;
- width: max-content;
- font-family: $sans-serif;
- font-size: 12px;
- background-color: $light-color-secondary;
- padding: 2px 12px;
- list-style: none;
-
- .templateToggle, .chromeToggle {
- text-align: left;
- }
-
- input {
- margin-right: 10px;
- }
+.documentButtonBar-linker:hover {
+ cursor: pointer;
+ transform: scale(1.05);
}
+
@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } } \ No newline at end of file
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index ba87ecfb4..1fefc70f1 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -1,20 +1,19 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadAlt, faLink, faShare, faStopCircle, faSyncAlt, faTag, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
+import { action, observable, runInAction, computed } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../new_fields/Doc";
import { RichTextField } from '../../new_fields/RichTextField';
-import { NumCast } from "../../new_fields/Types";
+import { NumCast, StrCast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
-import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
+import { DragManager } from "../util/DragManager";
import { LinkManager } from '../util/LinkManager';
import { UndoManager } from "../util/UndoManager";
import './DocumentButtonBar.scss';
import './collections/ParentDocumentSelector.scss';
import { LinkMenu } from "./linking/LinkMenu";
-import { MetadataEntryMenu } from './MetadataEntryMenu';
import { FormattedTextBox, GoogleRef } from "./nodes/FormattedTextBox";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
@@ -43,53 +42,53 @@ const fetch: IconProp = "sync-alt";
@observer
export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> {
private _linkButton = React.createRef<HTMLDivElement>();
- private _linkerButton = React.createRef<HTMLDivElement>();
- private _aliasButton = React.createRef<HTMLDivElement>();
- private _tooltipoff = React.createRef<HTMLDivElement>();
- private _textDoc?: Doc;
+ private _downX = 0;
+ private _downY = 0;
+ private _pullAnimating = false;
+ private _pushAnimating = false;
+ private _pullColorAnimating = false;
+
+ @observable private pushIcon: IconProp = "arrow-alt-circle-up";
+ @observable private pullIcon: IconProp = "arrow-alt-circle-down";
+ @observable private pullColor: string = "white";
+ @observable private isAnimatingFetch = false;
+ @observable private openHover = false;
+
public static Instance: DocumentButtonBar;
+ public static hasPushedHack = false;
+ public static hasPulledHack = false;
constructor(props: { views: DocumentView[] }) {
super(props);
DocumentButtonBar.Instance = this;
}
- @observable public pushIcon: IconProp = "arrow-alt-circle-up";
- @observable public pullIcon: IconProp = "arrow-alt-circle-down";
- @observable public pullColor: string = "white";
- @observable public isAnimatingFetch = false;
- @observable public openHover = false;
- public pullColorAnimating = false;
-
- private pullAnimating = false;
- private pushAnimating = false;
-
public startPullOutcome = action((success: boolean) => {
- if (!this.pullAnimating) {
- this.pullAnimating = true;
+ if (!this._pullAnimating) {
+ this._pullAnimating = true;
this.pullIcon = success ? "check-circle" : "stop-circle";
setTimeout(() => runInAction(() => {
this.pullIcon = "arrow-alt-circle-down";
- this.pullAnimating = false;
+ this._pullAnimating = false;
}), 1000);
}
});
public startPushOutcome = action((success: boolean) => {
- if (!this.pushAnimating) {
- this.pushAnimating = true;
+ if (!this._pushAnimating) {
+ this._pushAnimating = true;
this.pushIcon = success ? "check-circle" : "stop-circle";
setTimeout(() => runInAction(() => {
this.pushIcon = "arrow-alt-circle-up";
- this.pushAnimating = false;
+ this._pushAnimating = false;
}), 1000);
}
});
public setPullState = action((unchanged: boolean) => {
this.isAnimatingFetch = false;
- if (!this.pullColorAnimating) {
- this.pullColorAnimating = true;
+ if (!this._pullColorAnimating) {
+ this._pullColorAnimating = true;
this.pullColor = unchanged ? "lawngreen" : "red";
setTimeout(this.clearPullColor, 1000);
}
@@ -97,51 +96,36 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
private clearPullColor = action(() => {
this.pullColor = "white";
- this.pullColorAnimating = false;
+ this._pullColorAnimating = false;
});
- onLinkerButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
- document.removeEventListener("pointermove", this.onLinkerButtonMoved);
- document.addEventListener("pointermove", this.onLinkerButtonMoved);
- document.removeEventListener("pointerup", this.onLinkerButtonUp);
- document.addEventListener("pointerup", this.onLinkerButtonUp);
- }
-
- onAliasButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.addEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
- document.addEventListener("pointerup", this.onAliasButtonUp);
- }
-
- onLinkerButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onLinkerButtonMoved);
- document.removeEventListener("pointerup", this.onLinkerButtonUp);
- e.stopPropagation();
- }
-
- onAliasButtonUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
- e.stopPropagation();
- }
@action
- onLinkerButtonMoved = (e: PointerEvent): void => {
- if (this._linkerButton.current !== null) {
- document.removeEventListener("pointermove", this.onLinkerButtonMoved);
- document.removeEventListener("pointerup", this.onLinkerButtonUp);
- let selDoc = this.props.views[0];
- let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined;
- let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
- let _linkDrag = UndoManager.StartBatch("Drag Link");
- DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
+ onLinkButtonMoved = (e: PointerEvent): void => {
+ if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved);
+ document.removeEventListener("pointerup", this.onLinkButtonUp);
+ let docView = this.props.views[0];
+ let container = docView.props.ContainingCollectionDoc?.proto;
+ let dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []);
+ let linkDrag = UndoManager.StartBatch("Drag Link");
+ DragManager.StartLinkDrag(this._linkButton.current, dragData, e.pageX, e.pageY, {
handlers: {
- dragComplete: () => _linkDrag && _linkDrag.end()
+ dragComplete: () => {
+ let tooltipmenu = FormattedTextBox.ToolTipTextMenu;
+ let linkDoc = dragData.linkDocument;
+ if (linkDoc && tooltipmenu) {
+ let proto = Doc.GetProto(linkDoc);
+ if (proto && docView) {
+ proto.sourceContext = docView.props.ContainingCollectionDoc;
+ }
+ let text = tooltipmenu.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), e.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
+ }
+ }
+ linkDrag && linkDrag.end();
+ }
},
hideSource: false
});
@@ -149,37 +133,15 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
e.stopPropagation();
}
- @action
- onAliasButtonMoved = (e: PointerEvent): void => {
- if (this._aliasButton.current !== null) {
- document.removeEventListener("pointermove", this.onAliasButtonMoved);
- document.removeEventListener("pointerup", this.onAliasButtonUp);
-
- let dragDocView = this.props.views[0];
- let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
- const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
- dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top);
- dragData.embedDoc = true;
- dragData.dropAction = "alias";
- DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, {
- offsetX: dragData.offset[0],
- offsetY: dragData.offset[1],
- handlers: {
- dragComplete: action(emptyFunction),
- },
- hideSource: false
- });
- }
- e.stopPropagation();
- }
onLinkButtonDown = (e: React.PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
+ this._downX = e.clientX;
+ this._downY = e.clientY;
document.removeEventListener("pointermove", this.onLinkButtonMoved);
document.addEventListener("pointermove", this.onLinkButtonMoved);
document.removeEventListener("pointerup", this.onLinkButtonUp);
document.addEventListener("pointerup", this.onLinkButtonUp);
+ e.stopPropagation();
}
onLinkButtonUp = (e: PointerEvent): void => {
@@ -188,173 +150,91 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
e.stopPropagation();
}
- onLinkButtonMoved = async (e: PointerEvent) => {
- if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) {
- document.removeEventListener("pointermove", this.onLinkButtonMoved);
- document.removeEventListener("pointerup", this.onLinkButtonUp);
- DragLinksAsDocuments(this._linkButton.current, e.x, e.y, this.props.views[0].props.Document);
- }
- e.stopPropagation();
- }
-
- aliasDragger = () => {
- return (<div className="linkButtonWrapper">
- <div title="Drag Alias" className="linkButton-linker" ref={this._aliasButton} onPointerDown={this.onAliasButtonDown}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
- </div>
- </div>);
- }
-
- private get targetDoc() {
- return this.props.views[0].props.Document;
- }
-
- considerGoogleDocsPush = () => {
- let canPush = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
- if (!canPush) return (null);
- let published = Doc.GetProto(this.targetDoc)[GoogleRef] !== undefined;
- let icon: IconProp = published ? (this.pushIcon as any) : cloud;
- return (
- <div className={"linkButtonWrapper"}>
- <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="linkButton-linker" onClick={() => {
- DocumentButtonBar.hasPushedHack = false;
- this.targetDoc[Pushes] = NumCast(this.targetDoc[Pushes]) + 1;
- }}>
- <FontAwesomeIcon className="documentdecorations-icon" icon={icon} size={published ? "sm" : "xs"} />
- </div>
- </div>
- );
+ @computed
+ get considerGoogleDocsPush() {
+ let targetDoc = this.props.views[0].props.Document;
+ let published = Doc.GetProto(targetDoc)[GoogleRef] !== undefined;
+ return <div title={`${published ? "Push" : "Publish"} to Google Docs`} className="documentButtonBar-linker" onClick={() => {
+ DocumentButtonBar.hasPushedHack = false;
+ targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1;
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} />
+ </div>;
}
- considerGoogleDocsPull = () => {
- let canPull = this.targetDoc.data && this.targetDoc.data instanceof RichTextField;
- let dataDoc = Doc.GetProto(this.targetDoc);
- if (!canPull || !dataDoc[GoogleRef]) return (null);
- let icon = dataDoc.unchanged === false ? (this.pullIcon as any) : fetch;
- icon = this.openHover ? "share" : icon;
+ @computed
+ get considerGoogleDocsPull() {
+ let targetDoc = this.props.views[0].props.Document;
+ let dataDoc = Doc.GetProto(targetDoc);
let animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none";
- let title = `${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`;
- return (
- <div className={"linkButtonWrapper"}>
- <div
- title={title}
- className="linkButton-linker"
- style={{
- backgroundColor: this.pullColor,
- transition: "0.2s ease all"
- }}
- onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
- onPointerLeave={() => runInAction(() => this.openHover = false)}
- onClick={e => {
- if (e.altKey) {
- e.preventDefault();
- window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
- } else {
- this.clearPullColor();
- DocumentButtonBar.hasPulledHack = false;
- this.targetDoc[Pulls] = NumCast(this.targetDoc[Pulls]) + 1;
- dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
- }
- }}>
- <FontAwesomeIcon
- style={{
- WebkitAnimation: animation,
- MozAnimation: animation
- }}
- className="documentdecorations-icon"
- icon={icon}
- size="sm"
- />
- </div>
- </div>
- );
- }
-
- public static hasPushedHack = false;
- public static hasPulledHack = false;
-
- considerTooltip = () => {
- let thisDoc = this.props.views[0].props.Document;
- let isTextDoc = thisDoc.data && thisDoc.data instanceof RichTextField;
- if (!isTextDoc) return null;
- this._textDoc = thisDoc;
- return (
- <div className="tooltipwrapper">
- <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}>
- {/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */}
+ return !dataDoc[GoogleRef] ? (null) : <div className="documentButtonBar-linker"
+ title={`${!dataDoc.unchanged ? "Pull from" : "Fetch"} Google Docs`}
+ style={{ backgroundColor: this.pullColor }}
+ onPointerEnter={e => e.altKey && runInAction(() => this.openHover = true)}
+ onPointerLeave={action(() => this.openHover = false)}
+ onClick={e => {
+ if (e.altKey) {
+ e.preventDefault();
+ window.open(`https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`);
+ } else {
+ this.clearPullColor();
+ DocumentButtonBar.hasPulledHack = false;
+ targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1;
+ dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true);
+ }
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm"
+ style={{ WebkitAnimation: animation, MozAnimation: animation }}
+ icon={this.openHover ? "share" : dataDoc.unchanged === false ? (this.pullIcon as any) : fetch}
+ />
+ </div>;
+ }
+
+ @computed
+ get linkButton() {
+ let linkCount = LinkManager.Instance.getAllRelatedLinks(this.props.views[0].props.Document).length;
+ return <div title="Drag(create link) Tap(view links)" className="documentButtonBar-linkFlyout" ref={this._linkButton}>
+ <Flyout anchorPoint={anchorPoints.RIGHT_TOP}
+ content={<LinkMenu docView={this.props.views[0]} addDocTab={this.props.views[0].props.addDocTab} changeFlyout={emptyFunction} />}>
+ <div className={"documentButtonBar-linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >
+ {linkCount ? linkCount : <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />}
</div>
- </div>
-
- );
+ </Flyout>
+ </div>;
}
- onTooltipOff = (e: React.PointerEvent): void => {
- e.stopPropagation();
- if (this._textDoc) {
- if (this._tooltipoff.current) {
- if (this._tooltipoff.current.title === "Hide Tooltip") {
- this._tooltipoff.current.title = "Show Tooltip";
- this._textDoc.tooltip = "hi";
- }
- else {
- this._tooltipoff.current.title = "Hide Tooltip";
- }
- }
- }
- }
-
- get metadataMenu() {
- return (
- <div className="linkButtonWrapper">
- <Flyout anchorPoint={anchorPoints.TOP_LEFT}
- content={<MetadataEntryMenu docs={() => this.props.views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
- <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
- </Flyout>
- </div>
- );
+ @computed
+ get contextButton() {
+ return <ParentDocSelector Views={this.props.views} Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => {
+ where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) :
+ this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) :
+ this.props.views[0].props.addDocTab(doc, data, "onRight");
+ return true;
+ }} />;
}
render() {
- let linkButton = null;
- if (this.props.views.length > 0) {
- let selFirst = this.props.views[0];
-
- let linkCount = LinkManager.Instance.getAllRelatedLinks(selFirst.props.Document).length;
- linkButton = (<Flyout
- anchorPoint={anchorPoints.RIGHT_TOP}
- content={<LinkMenu docView={selFirst}
- addDocTab={selFirst.props.addDocTab}
- changeFlyout={emptyFunction} />}>
- <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
- </Flyout >);
- }
-
let templates: Map<Template, boolean> = new Map();
Array.from(Object.values(Templates.TemplateList)).map(template =>
templates.set(template, this.props.views.reduce((checked, doc) => checked || doc.getLayoutPropStr("show" + template.Name) ? true : false, false as boolean)));
- return (<div className="documentButtonBar">
- <div className="linkButtonWrapper">
- <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div>
+ let isText = this.props.views[0].props.Document.data instanceof RichTextField; // bcz: Todo - can't assume layout is using the 'data' field. need to add fieldKey to DocumentView
+ let considerPull = isText && this.considerGoogleDocsPull;
+ let considerPush = isText && this.considerGoogleDocsPush;
+ return <div className="documentButtonBar">
+ <div className="documentButtonBar-button">
+ {this.linkButton}
</div>
- <div className="linkButtonWrapper">
- <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />
- </div>
- </div>
- <div className="linkButtonWrapper">
+ <div className="documentButtonBar-button">
<TemplateMenu docs={this.props.views} templates={templates} />
</div>
- {this.metadataMenu}
- {this.aliasDragger()}
- {this.considerGoogleDocsPush()}
- {this.considerGoogleDocsPull()}
- <ParentDocSelector Document={this.props.views[0].props.Document} addDocTab={(doc, data, where) => {
- where === "onRight" ? CollectionDockingView.AddRightSplit(doc, data) : this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc, data) : this.props.views[0].props.addDocTab(doc, data, "onRight");
- return true;
- }} />
- {/* {this.considerTooltip()} */}
- </div>
- );
+ <div className="documentButtonBar-button" style={{ display: !considerPush ? "none" : "" }}>
+ {this.considerGoogleDocsPush}
+ </div>
+ <div className="documentButtonBar-button" style={{ display: !considerPull ? "none" : "" }}>
+ {this.considerGoogleDocsPull}
+ </div>
+ {this.contextButton}
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 55c211d1d..66f47147f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,6 +24,7 @@ import { DocumentView } from "./nodes/DocumentView";
import { FieldView } from "./nodes/FieldView";
import { IconBox } from "./nodes/IconBox";
import React = require("react");
+import { PointData } from '../../new_fields/InkField';
import { DocumentType } from '../documents/DocumentTypes';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -157,22 +158,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this.onBackgroundUp(e);
}
- @observable _forceUpdate = 0;
- _lastBox = { x: 0, y: 0, r: 0, b: 0 };
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
- let x = this._forceUpdate;
- this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
+ return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.renderDepth === 0 ||
Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
return bounds;
}
let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
- if (transform.TranslateX === 0 && transform.TranslateY === 0) {
- setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds
- return this._lastBox;
- }
-
var [sptX, sptY] = transform.transformPoint(0, 0);
let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight());
if (documentView.props.Document.type === DocumentType.LINK) {
@@ -187,7 +180,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
};
}, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
- return this._lastBox;
}
onBackgroundDown = (e: React.PointerEvent): void => {
@@ -212,7 +204,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.removeEventListener("pointerup", this.onBackgroundUp);
document.removeEventListener("pointermove", this.onTitleMove);
document.removeEventListener("pointerup", this.onTitleUp);
- DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
+ DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(documentView => documentView.ContentDiv!), dragData, e.x, e.y, {
handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
hideSource: true
});
@@ -471,7 +463,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
break;
}
- SelectionManager.SelectedDocuments().forEach(element => {
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
let doc = PositionDocument(element.props.Document);
let layoutDoc = PositionDocument(Doc.Layout(element.props.Document));
@@ -517,7 +509,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
}
}
- });
+ }));
}
@action
@@ -537,7 +529,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@computed
get selectionTitle(): string {
if (SelectionManager.SelectedDocuments().length === 1) {
- let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
+ let selected = SelectionManager.SelectedDocuments()[0];
+ let field = selected.props.Document[this._fieldKey];
if (typeof field === "string") {
return field;
}
@@ -557,8 +550,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
}
public showTextBar = () => {
- if (this.TextBar) {
- TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && this.TextBar.appendChild(TooltipTextMenu.Toolbar);
+ if (this.TextBar && TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1) {
+ this.TextBar.appendChild(TooltipTextMenu.Toolbar);
}
}
render() {
@@ -569,6 +562,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
let minimizeIcon = (
<div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
+ {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
{SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
</div>);
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss
new file mode 100644
index 000000000..daff58fd6
--- /dev/null
+++ b/src/client/views/InkSelectDecorations.scss
@@ -0,0 +1,5 @@
+.inkSelectDecorations {
+ position: absolute;
+ border: black 1px solid;
+ z-index: 9001;
+} \ No newline at end of file
diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx
new file mode 100644
index 000000000..d40df9b75
--- /dev/null
+++ b/src/client/views/InkSelectDecorations.tsx
@@ -0,0 +1,55 @@
+import React = require("react");
+import { Touchable } from "./Touchable";
+import { PointData } from "../../new_fields/InkField";
+import { observer } from "mobx-react";
+import { computed, observable, action, runInAction } from "mobx";
+import "./InkSelectDecorations.scss";
+
+@observer
+export default class InkSelectDecorations extends Touchable {
+ static Instance: InkSelectDecorations;
+
+ @observable private _selectedInkNodes: Map<any, any> = new Map();
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ InkSelectDecorations.Instance = this;
+ }
+
+ @action
+ public SetSelected = (inkNodes: Map<any, any>, keepOld: boolean = false) => {
+ if (!keepOld) {
+ this._selectedInkNodes = new Map();
+ }
+ inkNodes.forEach((value: any, key: any) => {
+ runInAction(() => this._selectedInkNodes.set(key, value));
+ });
+ }
+
+ @computed
+ get Bounds(): { x: number, y: number, b: number, r: number } {
+ let left = Number.MAX_VALUE;
+ let top = Number.MAX_VALUE;
+ let right = -Number.MAX_VALUE;
+ let bottom = -Number.MAX_VALUE;
+ this._selectedInkNodes.forEach((value: PointData, key: string) => {
+ // value.pathData.map(val => {
+ // left = Math.min(val.x, left);
+ // top = Math.min(val.y, top);
+ // right = Math.max(val.x, right);
+ // bottom = Math.max(val.y, bottom);
+ // });
+ });
+ return { x: left, y: top, b: bottom, r: right };
+ }
+
+ render() {
+ let bounds = this.Bounds;
+ return <div style={{
+ top: bounds.y, left: bounds.x,
+ height: bounds.b - bounds.y,
+ width: bounds.r - bounds.x
+ }} />;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
deleted file mode 100644
index 8f32652ed..000000000
--- a/src/client/views/InkingCanvas.scss
+++ /dev/null
@@ -1,51 +0,0 @@
-@import "globalCssVariables";
-
-.inkingCanvas {
- // opacity: 0.99;
- touch-action: none;
-
- .jsx-parser {
- position: absolute;
- width: 100%;
- height: 100%;
- background: inherit;
- //z-index: -1; // allows annotations to appear on videos when screen is full-size & ...
- }
-}
-
-.inkingCanvas-paths-ink,
-.inkingCanvas-paths-markers,
-.inkingCanvas-noSelect,
-.inkingCanvas-canSelect {
- position: absolute;
- top: 0;
- left: 0;
- width: 8192px;
- height: 8192px;
- cursor: "crosshair";
- pointer-events: all;
-}
-
-.inkingCanvas-canSelect,
-.inkingCanvas-noSelect {
- top: -50000px;
- left: -50000px;
- width: 100000px;
- height: 100000px;
-}
-
-.inkingCanvas-noSelect {
- pointer-events: none;
- cursor: "crosshair";
-}
-
-.inkingCanvas-paths-ink,
-.inkingCanvas-paths-markers {
- pointer-events: none;
- z-index: 10000; // overlays ink on top of everything
- cursor: "arrow";
-}
-
-.inkingCanvas-paths-markers {
- mix-blend-mode: multiply;
-} \ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
deleted file mode 100644
index 0037b95d0..000000000
--- a/src/client/views/InkingCanvas.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import { action, computed, trace, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { Utils } from "../../Utils";
-import { Transform } from "../util/Transform";
-import "./InkingCanvas.scss";
-import { InkingControl } from "./InkingControl";
-import { InkingStroke } from "./InkingStroke";
-import React = require("react");
-import { UndoManager } from "../util/UndoManager";
-import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
-import { Doc } from "../../new_fields/Doc";
-import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
-
-interface InkCanvasProps {
- getScreenTransform: () => Transform;
- AnnotationDocument: Doc;
- Document: Doc;
- inkFieldKey: string;
- children: () => JSX.Element[];
-}
-
-@observer
-export class InkingCanvas extends React.Component<InkCanvasProps> {
- maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
- @observable inkMidX: number = 0;
- @observable inkMidY: number = 0;
- private previousState?: Map<string, StrokeData>;
- private _currentStrokeId: string = "";
- public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
- return stroke.pathData.reduce((inside: boolean, val) => inside ||
- (selRect.left < val.x && selRect.left + selRect.width > val.x &&
- selRect.top < val.y && selRect.top + selRect.height > val.y)
- , false);
- }
- public static StrokeRect(stroke: StrokeData): { left: number, top: number, right: number, bottom: number } {
- return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) =>
- ({
- left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y),
- right: Math.max(bounds.right, val.x), bottom: Math.max(bounds.bottom, val.y)
- })
- , { left: Number.MAX_VALUE, top: Number.MAX_VALUE, right: -Number.MAX_VALUE, bottom: -Number.MAX_VALUE });
- }
-
- componentDidMount() {
- PromiseValue(Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField)).then(ink => runInAction(() => {
- if (ink) {
- let bounds = Array.from(ink.inkData).reduce(([mix, max, miy, may], [id, strokeData]) =>
- strokeData.pathData.reduce(([mix, max, miy, may], p) =>
- [Math.min(mix, p.x), Math.max(max, p.x), Math.min(miy, p.y), Math.max(may, p.y)],
- [mix, max, miy, may]),
- [Number.MAX_VALUE, Number.MIN_VALUE, Number.MAX_VALUE, Number.MIN_VALUE]);
- this.inkMidX = (bounds[0] + bounds[1]) / 2;
- this.inkMidY = (bounds[2] + bounds[3]) / 2;
- }
- }));
- }
-
- @computed
- get inkData(): Map<string, StrokeData> {
- let map = Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField);
- return !map ? new Map : new Map(map.inkData);
- }
-
- set inkData(value: Map<string, StrokeData>) {
- this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value);
- }
-
- @action
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.button !== 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) {
- return;
- }
-
- document.addEventListener("pointermove", this.onPointerMove, true);
- document.addEventListener("pointerup", this.onPointerUp, true);
- e.stopPropagation();
- e.preventDefault();
-
- this.previousState = new Map(this.inkData);
-
- if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
- // start the new line, saves a uuid to represent the field of the stroke
- this._currentStrokeId = Utils.GenerateGuid();
- const data = this.inkData;
- data.set(this._currentStrokeId, {
- pathData: [this.relativeCoordinatesForEvent(e.clientX, e.clientY)],
- color: InkingControl.Instance.selectedColor,
- width: InkingControl.Instance.selectedWidth,
- tool: InkingControl.Instance.selectedTool,
- displayTimecode: NumCast(this.props.Document.currentTimecode, -1),
- creationTime: new Date().getTime()
- });
- this.inkData = data;
- }
- }
-
- @action
- onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove, true);
- document.removeEventListener("pointerup", this.onPointerUp, true);
- let coord = this.relativeCoordinatesForEvent(e.clientX, e.clientY);
- if (Math.abs(coord.x - this.inkMidX) > 500 || Math.abs(coord.y - this.inkMidY) > 500) {
- this.inkMidX = coord.x;
- this.inkMidY = coord.y;
- }
- e.stopPropagation();
- e.preventDefault();
-
- const batch = UndoManager.StartBatch("One ink stroke");
- const oldState = this.previousState || new Map;
- this.previousState = undefined;
- const newState = new Map(this.inkData);
- UndoManager.AddEvent({
- undo: () => this.inkData = oldState,
- redo: () => this.inkData = newState
- });
- batch.end();
- }
-
- @action
- onPointerMove = (e: PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
- if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
- let data = this.inkData; // add points to new line as it is being drawn
- let strokeData = data.get(this._currentStrokeId);
- if (strokeData) {
- strokeData.pathData.push(this.relativeCoordinatesForEvent(e.clientX, e.clientY));
- data.set(this._currentStrokeId, strokeData);
- }
- this.inkData = data;
- }
- }
-
- relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
- let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
- return { x, y };
- }
-
- @action
- removeLine = (id: string): void => {
- if (!this.previousState) {
- this.previousState = new Map(this.inkData);
- document.addEventListener("pointermove", this.onPointerMove, true);
- document.addEventListener("pointerup", this.onPointerUp, true);
- }
- let data = this.inkData;
- data.delete(id);
- this.inkData = data;
- }
-
- @computed
- get drawnPaths() {
- let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
- let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
- if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
- paths.push(<InkingStroke key={id} id={id}
- line={strokeData.pathData}
- count={strokeData.pathData.length}
- offsetX={this.maxCanvasDim - this.inkMidX}
- offsetY={this.maxCanvasDim - this.inkMidY}
- color={strokeData.color}
- width={strokeData.width}
- tool={strokeData.tool}
- creationTime={strokeData.creationTime}
- deleteCallback={this.removeLine} />);
- }
- return paths;
- }, [] as JSX.Element[]);
- let markerPaths = paths.filter(path => path.props.tool === InkTool.Highlighter);
- let penPaths = paths.filter(path => path.props.tool !== InkTool.Highlighter);
- return [!penPaths.length ? (null) :
- <svg className={`inkingCanvas-paths-ink`} key="Pens"
- style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
- {penPaths}
- </svg>,
- !markerPaths.length ? (null) :
- <svg className={`inkingCanvas-paths-markers`} key="Markers"
- style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
- {markerPaths}
- </svg>];
- }
-
- render() {
- let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
- let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser ||
- InkingControl.Instance.selectedTool === InkTool.Scrubber ? "pointer" : "default") : undefined;
- return (
- <div className="inkingCanvas">
- <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} style={{ cursor: cursor }} />
- {this.props.children()}
- {this.drawnPaths}
- </div >
- );
- }
-} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 332c22512..723b8cac4 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,70 +1,58 @@
+import { computed } from "mobx";
import { observer } from "mobx-react";
-import { observable, trace, runInAction } from "mobx";
+import { documentSchema } from "../../new_fields/documentSchemas";
+import { InkData, InkField, InkTool } from "../../new_fields/InkField";
+import { makeInterface } from "../../new_fields/Schema";
+import { Cast } from "../../new_fields/Types";
+import { DocExtendableComponent } from "./DocComponent";
import { InkingControl } from "./InkingControl";
-import React = require("react");
-import { InkTool } from "../../new_fields/InkField";
import "./InkingStroke.scss";
-import { AudioBox } from "./nodes/AudioBox";
-
+import { FieldView, FieldViewProps } from "./nodes/FieldView";
+import React = require("react");
-interface StrokeProps {
- offsetX: number;
- offsetY: number;
- id: string;
- count: number;
- line: Array<{ x: number, y: number }>;
- color: string;
- width: string;
- tool: InkTool;
- creationTime: number;
- deleteCallback: (index: string) => void;
+type InkDocument = makeInterface<[typeof documentSchema]>;
+const InkDocument = makeInterface(documentSchema);
+
+export function CreatePolyline(points: { x: number, y: number }[], left: number, top: number, color?: string, width?: number) {
+ let pts = points.reduce((acc: string, pt: { x: number, y: number }) => acc + `${pt.x - left},${pt.y - top} `, "");
+ return (
+ <polyline
+ points={pts}
+ style={{
+ fill: "none",
+ stroke: color ?? InkingControl.Instance.selectedColor,
+ strokeWidth: width ?? InkingControl.Instance.selectedWidth
+ }}
+ />
+ );
}
@observer
-export class InkingStroke extends React.Component<StrokeProps> {
-
- @observable private _strokeTool: InkTool = this.props.tool;
- @observable private _strokeColor: string = this.props.color;
- @observable private _strokeWidth: string = this.props.width;
-
- deleteStroke = (e: React.PointerEvent): void => {
- if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
- this.props.deleteCallback(this.props.id);
- e.stopPropagation();
- e.preventDefault();
- }
- if (InkingControl.Instance.selectedTool === InkTool.Scrubber && e.buttons === 1) {
- AudioBox.SetScrubTime(this.props.creationTime);
- e.stopPropagation();
- e.preventDefault();
- }
- }
+export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
+ public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
- parseData = (line: Array<{ x: number, y: number }>): string => {
- return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
- }
-
- createStyle() {
- switch (this._strokeTool) {
- // add more tool styles here
- default:
- return {
- fill: "none",
- stroke: this._strokeColor,
- strokeWidth: this._strokeWidth + "px",
- };
- }
- }
+ @computed get PanelWidth() { return this.props.PanelWidth(); }
+ @computed get PanelHeight() { return this.props.PanelHeight(); }
render() {
- let pathStyle = this.createStyle();
- let pathData = this.parseData(this.props.line);
- let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
- let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
-
- let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ||
- InkingControl.Instance.selectedTool === InkTool.Scrubber ? "all" : "none";
- return (<path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }}
- strokeLinejoin="round" strokeLinecap="round" onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />);
+ let data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
+ let xs = data.map(p => p.x);
+ let ys = data.map(p => p.y);
+ let left = Math.min(...xs);
+ let top = Math.min(...ys);
+ let right = Math.max(...xs);
+ let bottom = Math.max(...ys);
+ let points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth);
+ let width = right - left;
+ let height = bottom - top;
+ let scaleX = this.PanelWidth / width;
+ let scaleY = this.PanelHeight / height;
+ return <svg width={width} height={height} style={{
+ transformOrigin: "top left",
+ transform: `translate(${left}px, ${top}px) scale(${scaleX}, ${scaleY})`,
+ mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset"
+ }}>
+ {points}
+ </svg>;
}
} \ No newline at end of file
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 134a4ac85..3b66160fb 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -5,7 +5,7 @@ html,
body {
width: 100%;
height: 100%;
- overflow: auto;
+ overflow: hidden;
font-family: $sans-serif;
margin: 0;
position: absolute;
@@ -27,7 +27,7 @@ div {
pointer-events: none;
border-radius: inherit;
position: inherit;
- background: inherit;
+ // background: inherit;
}
p {
@@ -38,7 +38,7 @@ p {
::-webkit-scrollbar {
-webkit-appearance: none;
height: 8px;
- width: 8px;
+ width: 20px;
}
::-webkit-scrollbar-thumb {
@@ -65,10 +65,6 @@ button:hover {
cursor: pointer;
}
-#root {
- overflow: visible;
-}
-
.svg-inline--fa {
vertical-align: unset;
} \ No newline at end of file
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index a940e6889..87820abff 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -20,7 +20,6 @@
position: absolute;
top: 0;
left: 0;
- overflow: auto;
z-index: 1;
}
@@ -83,8 +82,8 @@
.mainView-expandFlyoutButton {
position: absolute;
- top: 5px;
- right: 5px;
+ top: 100px;
+ right: 30px;
cursor: pointer;
}
@@ -96,6 +95,7 @@
border-radius: 5px;
position: absolute;
z-index: 1;
+ touch-action: none;
}
.mainView-workspace {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 440332e42..d45aebe30 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
- faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone
+ faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -36,6 +36,8 @@ import { DocumentView } from './nodes/DocumentView';
import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
+import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import InkSelectDecorations from './InkSelectDecorations';
import { Scripting } from '../util/Scripting';
import { AudioBox } from './nodes/AudioBox';
import SettingsManager from '../util/SettingsManager';
@@ -117,6 +119,7 @@ export class MainView extends React.Component {
library.add(faMusic);
library.add(faTree);
library.add(faPlay);
+ library.add(faCompressArrowsAlt);
library.add(faPause);
library.add(faClone);
library.add(faCut);
@@ -131,6 +134,7 @@ export class MainView extends React.Component {
library.add(faBolt);
library.add(faChevronRight);
library.add(faEllipsisV);
+ library.add(faMusic);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -297,13 +301,15 @@ export class MainView extends React.Component {
}
onPointerDown = (e: React.PointerEvent) => {
- this._flyoutSizeOnDown = e.clientX;
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
- e.preventDefault();
+ if (this._flyoutTranslate) {
+ this._flyoutSizeOnDown = e.clientX;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
}
@action
@@ -425,10 +431,10 @@ export class MainView extends React.Component {
style={{ cursor: "ew-resize", left: `${(this.flyoutWidth * (this._flyoutTranslate ? 1 : 0)) - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}>
<span title="library View Dragger" style={{
- width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "5vw",
- height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "30vh",
- position: "absolute",
- top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "-10vh"
+ width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
+ height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
+ position: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "absolute" : "fixed",
+ top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "0"
}} />
</div>
<div className="mainView-libraryFlyout" style={{
@@ -508,13 +514,15 @@ export class MainView extends React.Component {
<SettingsManager />
<GoogleAuthenticationManager />
<DocumentDecorations />
+ <InkSelectDecorations />
{this.mainContent}
<PreviewCursor />
<ContextMenu />
{this.docButtons}
<PDFMenu />
+ <MarqueeOptionsMenu />
<OverlayView />
</div >);
}
}
-Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); }); \ No newline at end of file
+Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss
index 20f9b9a49..d384fd284 100644
--- a/src/client/views/PreviewCursor.scss
+++ b/src/client/views/PreviewCursor.scss
@@ -6,4 +6,5 @@
top: 0;
left:0;
pointer-events: none;
+ opacity: 1;
} \ No newline at end of file
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index eed2cc5da..136a272ab 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -1,4 +1,4 @@
-import { action, observable, runInAction } from 'mobx';
+import { action, observable, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import "normalize.css";
import * as React from 'react';
@@ -7,21 +7,16 @@ import { Docs } from '../documents/Documents';
// import { Transform } from 'prosemirror-transform';
import { Doc } from '../../new_fields/Doc';
import { Transform } from "../util/Transform";
+import { TraceMobx } from '../../new_fields/util';
@observer
export class PreviewCursor extends React.Component<{}> {
- private _prompt = React.createRef<HTMLDivElement>();
static _onKeyPress?: (e: KeyboardEvent) => void;
static _getTransform: () => Transform;
static _addLiveTextDoc: (doc: Doc) => void;
static _addDocument: (doc: Doc) => boolean;
@observable static _clickPoint = [0, 0];
@observable public static Visible = false;
- //when focus is lost, this will remove the preview cursor
- @action onBlur = (): void => {
- PreviewCursor.Visible = false;
- }
-
constructor(props: any) {
super(props);
document.addEventListener("keydown", this.onKeyPress);
@@ -108,6 +103,12 @@ export class PreviewCursor extends React.Component<{}> {
}
}
}
+
+ //when focus is lost, this will remove the preview cursor
+ @action onBlur = (): void => {
+ PreviewCursor.Visible = false;
+ }
+
@action
public static Show(x: number, y: number,
onKeyPress: (e: KeyboardEvent) => void,
@@ -119,18 +120,13 @@ export class PreviewCursor extends React.Component<{}> {
this._addLiveTextDoc = addLiveText;
this._getTransform = getTransform;
this._addDocument = addDocument;
- setTimeout(action(() => this.Visible = true), (1));
+ this.Visible = true;
}
render() {
- if (!PreviewCursor._clickPoint) {
- return (null);
- }
- if (PreviewCursor.Visible && this._prompt.current) {
- this._prompt.current.focus();
- }
- return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._prompt}
- style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
- I
+ return (!PreviewCursor._clickPoint || !PreviewCursor.Visible) ? (null) :
+ <div className="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={e => e && e.focus()}
+ style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}>
+ I
</div >;
}
} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss
new file mode 100644
index 000000000..186d3ab0d
--- /dev/null
+++ b/src/client/views/TemplateMenu.scss
@@ -0,0 +1,50 @@
+@import "globalCssVariables";
+.templating-menu {
+ position: absolute;
+ pointer-events: auto;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.templating-button {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ font-size: 14;
+ background-color: $dark-color;
+ color: $light-color;
+ text-align: center;
+ cursor: pointer;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ }
+}
+
+.template-list {
+ position: absolute;
+ top: 25px;
+ left: 0px;
+ width: max-content;
+ font-family: $sans-serif;
+ font-size: 12px;
+ background-color: $light-color-secondary;
+ padding: 2px 12px;
+ list-style: none;
+
+ .templateToggle, .chromeToggle {
+ text-align: left;
+ }
+
+ input {
+ margin-right: 10px;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 96265385e..c65b338b4 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -4,12 +4,13 @@ import { DocumentManager } from "../util/DocumentManager";
import { DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
import { undoBatch } from "../util/UndoManager";
-import './DocumentDecorations.scss';
+import './TemplateMenu.scss';
import { DocumentView } from "./nodes/DocumentView";
import { Template, Templates } from "./Templates";
import React = require("react");
import { Doc } from "../../new_fields/Doc";
import { StrCast } from "../../new_fields/Types";
+import { emptyFunction } from "../../Utils";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -46,10 +47,13 @@ export interface TemplateMenuProps {
templates: Map<Template, boolean>;
}
+
@observer
export class TemplateMenu extends React.Component<TemplateMenuProps> {
@observable private _hidden: boolean = true;
- dragRef = React.createRef<HTMLUListElement>();
+ private _downx = 0;
+ private _downy = 0;
+ private _dragRef = React.createRef<HTMLUListElement>();
toggleCustom = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.props.docs.map(dv => dv.setCustomView(e.target.checked));
@@ -122,6 +126,43 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled");
});
}
+ onAliasButtonUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onAliasButtonMoved);
+ document.removeEventListener("pointerup", this.onAliasButtonUp);
+ e.stopPropagation();
+ }
+
+ onAliasButtonDown = (e: React.PointerEvent): void => {
+ this._downx = e.clientX;
+ this._downy = e.clientY;
+ e.stopPropagation();
+ e.preventDefault();
+ document.removeEventListener("pointermove", this.onAliasButtonMoved);
+ document.addEventListener("pointermove", this.onAliasButtonMoved);
+ document.removeEventListener("pointerup", this.onAliasButtonUp);
+ document.addEventListener("pointerup", this.onAliasButtonUp);
+ }
+ onAliasButtonMoved = (e: PointerEvent): void => {
+ if (this._dragRef.current !== null && (Math.abs(e.clientX - this._downx) > 4 || Math.abs(e.clientY - this._downy) > 4)) {
+ document.removeEventListener("pointermove", this.onAliasButtonMoved);
+ document.removeEventListener("pointerup", this.onAliasButtonUp);
+
+ let dragDocView = this.props.docs[0];
+ let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]);
+ const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ dragData.embedDoc = true;
+ dragData.dropAction = "alias";
+ DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, {
+ offsetX: dragData.offset[0],
+ offsetY: dragData.offset[1],
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+ e.stopPropagation();
+ }
render() {
let layout = Doc.Layout(this.props.docs[0].Document);
@@ -132,9 +173,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
templateMenu.push(<OtherToggle key={"custom"} name={"Custom"} checked={StrCast(this.props.docs[0].Document.layoutKey, "layout") !== "layout"} toggle={this.toggleCustom} />);
templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout.chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
return (
- <div className="templating-menu" >
- <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
- <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
+ <div className="templating-menu" onPointerDown={this.onAliasButtonDown}>
+ <div title="Drag:(create alias). Tap:(modify layout)." className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
+ <ul className="template-list" ref={this._dragRef} style={{ display: this._hidden ? "none" : "block" }}>
{templateMenu}
{<button onClick={this.clearTemplates}>Restore Defaults</button>}
</ul>
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
new file mode 100644
index 000000000..0dd4f734c
--- /dev/null
+++ b/src/client/views/Touchable.tsx
@@ -0,0 +1,94 @@
+import * as React from 'react';
+import { action } from 'mobx';
+import { InteractionUtils } from '../util/InteractionUtils';
+
+export abstract class Touchable<T = {}> extends React.Component<T> {
+ protected _touchDrag: boolean = false;
+ protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
+
+ public FirstX: number = 0;
+ public FirstY: number = 0;
+ public SecondX: number = 0;
+ public SecondY: number = 0;
+
+ /**
+ * When a touch even starts, we keep track of each touch that is associated with that event
+ */
+ @action
+ protected onTouchStart = (e: React.TouchEvent): void => {
+ for (let i = 0; i < e.targetTouches.length; i++) {
+ let pt = e.targetTouches.item(i);
+ this.prevPoints.set(pt.identifier, pt);
+ }
+
+ switch (e.targetTouches.length) {
+ case 1:
+ this.handle1PointerDown(e);
+ break;
+ case 2:
+ this.handle2PointersDown(e);
+ }
+
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ }
+
+ /**
+ * Handle touch move event
+ */
+ @action
+ protected onTouch = (e: TouchEvent): void => {
+ // if we're not actually moving a lot, don't consider it as dragging yet
+ if (!InteractionUtils.IsDragging(this.prevPoints, e.targetTouches, 5) && !this._touchDrag) return;
+ this._touchDrag = true;
+ switch (e.targetTouches.length) {
+ case 1:
+ this.handle1PointerMove(e);
+ break;
+ case 2:
+ this.handle2PointersMove(e);
+ break;
+ }
+ }
+
+ @action
+ protected onTouchEnd = (e: TouchEvent): void => {
+ this._touchDrag = false;
+ e.stopPropagation();
+
+ // remove all the touches associated with the event
+ for (let i = 0; i < e.targetTouches.length; i++) {
+ let pt = e.targetTouches.item(i);
+ if (pt) {
+ if (this.prevPoints.has(pt.identifier)) {
+ this.prevPoints.delete(pt.identifier);
+ }
+ }
+ }
+
+ if (e.targetTouches.length === 0) {
+ this.prevPoints.clear();
+ }
+ this.cleanUpInteractions();
+ }
+
+ cleanUpInteractions = (): void => {
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ }
+
+ handle1PointerMove = (e: TouchEvent): any => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ handle2PointersMove = (e: TouchEvent): any => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ handle1PointerDown = (e: React.TouchEvent): any => { };
+ handle2PointersDown = (e: React.TouchEvent): any => { };
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 12f54d69d..bcdc9c97e 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,12 +1,13 @@
@import "../../views/globalCssVariables.scss";
-.lm_active .messageCounter{
- color:white;
+.lm_active .messageCounter {
+ color: white;
background: #999999;
}
+
.messageCounter {
- width:18px;
- height:20px;
+ width: 18px;
+ height: 20px;
text-align: center;
border-radius: 20px;
margin-left: 5px;
@@ -18,25 +19,33 @@
.collectiondockingview-container {
width: 100%;
- height:100%;
+ height: 100%;
border-style: solid;
border-width: $COLLECTION_BORDER_WIDTH;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
+
+ .collectionDockingView-dragAsDocument {
+ touch-action: none;
+ }
+
.lm_content {
background: white;
}
+
.lm_controls>li {
opacity: 0.6;
transform: scale(1.2);
}
+
.lm_maximised .lm_controls .lm_maximise {
opacity: 1;
transform: scale(0.8);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important;
}
+
.flexlayout__layout {
left: 0;
top: 0;
@@ -45,17 +54,21 @@
position: absolute;
overflow: hidden;
}
+
.flexlayout__splitter {
background-color: black;
}
+
.flexlayout__splitter:hover {
background-color: #333;
}
+
.flexlayout__splitter_drag {
border-radius: 5px;
background-color: #444;
z-index: 1000;
}
+
.flexlayout__outline_rect {
position: absolute;
cursor: move;
@@ -65,6 +78,7 @@
z-index: 1000;
box-sizing: border-box;
}
+
.flexlayout__outline_rect_edge {
cursor: move;
border: 2px solid #b7d1b5;
@@ -73,12 +87,14 @@
z-index: 1000;
box-sizing: border-box;
}
+
.flexlayout__edge_rect {
position: absolute;
z-index: 1000;
box-shadow: inset 0 0 5px rgba(0, 0, 0, .2);
background-color: lightgray;
}
+
.flexlayout__drag_rect {
position: absolute;
cursor: move;
@@ -97,11 +113,13 @@
padding: 10px;
word-wrap: break-word;
}
+
.flexlayout__tabset {
overflow: hidden;
background-color: #222;
box-sizing: border-box;
}
+
.flexlayout__tab {
overflow: auto;
position: absolute;
@@ -109,6 +127,7 @@
background-color: #222;
color: black;
}
+
.flexlayout__tab_button {
cursor: pointer;
padding: 2px 8px 3px 8px;
@@ -120,28 +139,35 @@
vertical-align: top;
box-sizing: border-box;
}
+
.flexlayout__tab_button--selected {
color: #ddd;
background-color: #222;
}
+
.flexlayout__tab_button--unselected {
color: gray;
}
+
.flexlayout__tab_button_leading {
display: inline-block;
}
+
.flexlayout__tab_button_content {
display: inline-block;
}
+
.flexlayout__tab_button_textbox {
float: left;
border: none;
color: lightgreen;
background-color: #222;
}
+
.flexlayout__tab_button_textbox:focus {
outline: none;
}
+
.flexlayout__tab_button_trailing {
display: inline-block;
margin-left: 5px;
@@ -149,10 +175,12 @@
width: 8px;
height: 8px;
}
+
.flexlayout__tab_button:hover .flexlayout__tab_button_trailing,
.flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
}
+
.flexlayout__tab_button_overflow {
float: left;
width: 20px;
@@ -165,6 +193,7 @@
font-family: Arial, sans-serif;
background: transparent url("../../../../node_modules/flexlayout-react/images/more.png") no-repeat left;
}
+
.flexlayout__tabset_header {
position: absolute;
left: 0;
@@ -175,6 +204,7 @@
/*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/
box-sizing: border-box;
}
+
.flexlayout__tab_header_inner {
position: absolute;
left: 0;
@@ -182,6 +212,7 @@
bottom: 0;
width: 10000px;
}
+
.flexlayout__tab_header_outer {
background-color: black;
position: absolute;
@@ -191,12 +222,15 @@
/*height: 100px;*/
overflow: hidden;
}
+
.flexlayout__tabset-selected {
background-image: linear-gradient(#111, #444);
}
+
.flexlayout__tabset-maximized {
background-image: linear-gradient(#666, #333);
}
+
.flexlayout__tab_toolbar {
position: absolute;
display: flex;
@@ -206,6 +240,7 @@
bottom: 0;
right: 0;
}
+
.flexlayout__tab_toolbar_button-min {
width: 20px;
height: 20px;
@@ -213,6 +248,7 @@
outline-width: 0;
background: transparent url("../../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center;
}
+
.flexlayout__tab_toolbar_button-max {
width: 20px;
height: 20px;
@@ -220,14 +256,18 @@
outline-width: 0;
background: transparent url("../../../../node_modules/flexlayout-react/images/restore.png") no-repeat center;
}
+
.flexlayout__popup_menu {}
+
.flexlayout__popup_menu_item {
padding: 2px 10px 2px 10px;
color: #ddd;
}
+
.flexlayout__popup_menu_item:hover {
background-color: #444444;
}
+
.flexlayout__popup_menu_container {
box-shadow: inset 0 0 5px rgba(0, 0, 0, .15);
border: 1px solid #555;
@@ -236,33 +276,39 @@
position: absolute;
z-index: 1000;
}
+
.flexlayout__border_top {
background-color: black;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_bottom {
background-color: black;
border-top: 1px solid #333;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_left {
background-color: black;
border-right: 1px solid #333;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_right {
background-color: black;
border-left: 1px solid #333;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_inner_bottom {
display: flex;
}
+
.flexlayout__border_inner_left {
position: absolute;
white-space: nowrap;
@@ -270,6 +316,7 @@
transform-origin: top right;
transform: rotate(-90deg);
}
+
.flexlayout__border_inner_right {
position: absolute;
white-space: nowrap;
@@ -277,6 +324,7 @@
transform-origin: top left;
transform: rotate(90deg);
}
+
.flexlayout__border_button {
background-color: #222;
color: white;
@@ -288,29 +336,36 @@
vertical-align: top;
box-sizing: border-box;
}
+
.flexlayout__border_button--selected {
color: #ddd;
background-color: #222;
}
+
.flexlayout__border_button--unselected {
color: gray;
}
+
.flexlayout__border_button_leading {
float: left;
display: inline;
}
+
.flexlayout__border_button_content {
display: inline-block;
}
+
.flexlayout__border_button_textbox {
float: left;
border: none;
color: green;
background-color: #ddd;
}
+
.flexlayout__border_button_textbox:focus {
outline: none;
}
+
.flexlayout__border_button_trailing {
display: inline-block;
margin-left: 5px;
@@ -318,10 +373,12 @@
width: 8px;
height: 8px;
}
+
.flexlayout__border_button:hover .flexlayout__border_button_trailing,
.flexlayout__border_button--selected .flexlayout__border_button_trailing {
background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
}
+
.flexlayout__border_toolbar_left {
position: absolute;
display: flex;
@@ -331,6 +388,7 @@
left: 0;
right: 0;
}
+
.flexlayout__border_toolbar_right {
position: absolute;
display: flex;
@@ -340,6 +398,7 @@
left: 0;
right: 0;
}
+
.flexlayout__border_toolbar_top {
position: absolute;
display: flex;
@@ -349,6 +408,7 @@
bottom: 0;
right: 0;
}
+
.flexlayout__border_toolbar_bottom {
position: absolute;
display: flex;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 42d372f4a..75d92105b 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -426,15 +426,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
tab.setActive(true);
};
- ReactDOM.render(<span title="Drag as document" onPointerDown={
- e => {
- e.preventDefault();
- e.stopPropagation();
- DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
- handlers: { dragComplete: emptyFunction },
- hideSource: false
- });
- }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+ ReactDOM.render(<span title="Drag as document"
+ className="collectionDockingView-dragAsDocument"
+ onPointerDown={
+ e => {
+ e.preventDefault();
+ e.stopPropagation();
+ DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
+ handlers: { dragComplete: emptyFunction },
+ hideSource: false
+ });
+ }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan);
// ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, data, where) => {
// where === "onRight" ? CollectionDockingView.AddRightSplit(doc, dataDoc) : CollectionDockingView.Instance.AddTab(stack, doc, dataDoc);
@@ -446,7 +448,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
tab.element.append(upDiv);
tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => {
tab.titleElement[0].textContent = doc.title, { fireImmediately: true };
- tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegree(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegree(doc)]} 1px`;
+ tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegreeUnmemoized(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegreeUnmemoized(doc)]} 1px`;
});
//TODO why can't this just be doc instead of the id?
tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
@@ -615,7 +617,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
}
- get layoutDoc() { return this._document && Doc.Layout(this._document);}
+ get layoutDoc() { return this._document && Doc.Layout(this._document); }
panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth;
panelHeight = () => this._panelHeight;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 203c68463..65856cad3 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -112,7 +112,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (this.props.isSelected()) e.stopPropagation();
+ if (this.props.isSelected(true)) e.stopPropagation();
else {
this.props.select(false);
}
@@ -141,7 +141,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<ContentFittingDocumentView
Document={layoutDoc}
DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined}
- fieldKey={this.props.fieldKey}
childDocs={this.childDocs}
renderDepth={this.props.renderDepth}
ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
@@ -202,7 +201,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
render() {
return <div className="collectionSchemaView-container">
- <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active() && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}>
+ <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}>
{this.schemaTable}
</div>
{this.dividerDragger}
@@ -226,11 +225,11 @@ export interface SchemaTableProps {
addDocument: (document: Doc) => boolean;
moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
- active: () => boolean;
+ active: (outsideReaction: boolean) => boolean;
onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
- isSelected: () => boolean;
+ isSelected: (outsideReaction?: boolean) => boolean;
isFocused: (document: Doc) => boolean;
setFocused: (document: Doc) => void;
setPreviewDoc: (document: Doc) => void;
@@ -443,14 +442,14 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
onPointerDown = (e: React.PointerEvent): void => {
this.props.setFocused(this.props.Document);
- if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected()) {
+ if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected(true)) {
e.stopPropagation();
}
}
@action
onKeyDown = (e: KeyboardEvent): void => {
- if (!this._cellIsEditing && !this._headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected()) {
+ if (!this._cellIsEditing && !this._headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected(true)) {
let direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : "";
this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col);
@@ -779,7 +778,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
render() {
- return <div className="collectionSchemaView-table" onPointerDown={this.onPointerDown} onWheel={e => this.props.active() && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
+ return <div className="collectionSchemaView-table" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
{this.reactTable}
<div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
</div>;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 15033e51a..be3bfca0a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -168,7 +168,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return <ContentFittingDocumentView
Document={doc}
DataDocument={dataDoc}
- fieldKey={this.props.fieldKey}
showOverlays={this.overlays}
renderDepth={this.props.renderDepth}
ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider}
diff --git a/src/client/views/collections/CollectionStaffView.scss b/src/client/views/collections/CollectionStaffView.scss
new file mode 100644
index 000000000..493a5f670
--- /dev/null
+++ b/src/client/views/collections/CollectionStaffView.scss
@@ -0,0 +1,13 @@
+.collectionStaffView {
+ .collectionStaffView-staff {
+ width: 100%;
+ margin-top: 100px;
+ margin-bottom: 100px;
+ }
+
+ .collectionStaffView-line {
+ margin: 10px;
+ height: 2px;
+ background: black;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
new file mode 100644
index 000000000..40e860b12
--- /dev/null
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -0,0 +1,59 @@
+import { CollectionSubView } from "./CollectionSubView";
+import { Transform } from "../../util/Transform";
+import React = require("react");
+import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
+import { Doc, HeightSym } from "../../../new_fields/Doc";
+import { NumCast } from "../../../new_fields/Types";
+import "./CollectionStaffView.scss";
+import { observer } from "mobx-react";
+
+@observer
+export class CollectionStaffView extends CollectionSubView(doc => doc) {
+ private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(0, -this._mainCont.current!.scrollTop);
+ private _mainCont = React.createRef<HTMLDivElement>();
+ private _reactionDisposer: IReactionDisposer | undefined;
+ @observable private _staves = NumCast(this.props.Document.staves);
+
+ componentDidMount = () => {
+ this._reactionDisposer = reaction(
+ () => NumCast(this.props.Document.staves),
+ (staves) => runInAction(() => this._staves = staves)
+ );
+
+ this.props.Document.staves = 5;
+ }
+
+ @computed get fieldExtensionDoc() {
+ return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
+ }
+
+ @computed get addStaffButton() {
+ return <div onPointerDown={this.addStaff}>+</div>;
+ }
+
+ @computed get staves() {
+ let staves = [];
+ for (let i = 0; i < this._staves; i++) {
+ let rows = [];
+ for (let j = 0; j < 5; j++) {
+ rows.push(<div key={`staff-${i}-${j}`} className="collectionStaffView-line"></div>);
+ }
+ staves.push(<div key={`staff-${i}`} className="collectionStaffView-staff">
+ {rows}
+ </div>);
+ }
+ return staves;
+ }
+
+ @action
+ addStaff = (e: React.PointerEvent) => {
+ this.props.Document.staves = this._staves + 1;
+ }
+
+ render() {
+ return <div className="collectionStaffView" ref={this._mainCont}>
+ {this.staves}
+ {this.addStaffButton}
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8726726bb..8b993820b 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -23,7 +23,6 @@ import { EditableView } from "../EditableView";
import { MainView } from '../MainView';
import { KeyValueBox } from '../nodes/KeyValueBox';
import { Templates } from '../Templates';
-import { CollectionViewType } from './CollectionView';
import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView';
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
@@ -50,7 +49,7 @@ export interface TreeViewProps {
outerXf: () => { translateX: number, translateY: number };
treeViewId: string;
parentKey: string;
- active: () => boolean;
+ active: (outsideReaction?: boolean) => boolean;
showHeaderFields: () => boolean;
preventTreeViewOpen: boolean;
renderedIds: string[];
@@ -131,7 +130,7 @@ class TreeView extends React.Component<TreeViewProps> {
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
onPointerEnter = (e: React.PointerEvent): void => {
- this.props.active() && Doc.BrushDoc(this.dataDoc);
+ this.props.active(true) && Doc.BrushDoc(this.dataDoc);
if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
this._header!.current!.className = "treeViewItem-header";
document.addEventListener("pointermove", this.onDragMove, true);
@@ -177,8 +176,10 @@ class TreeView extends React.Component<TreeViewProps> {
/>)
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking && this.props.document !== CurrentUserUtils.UserDocument.workspaces) {
+ if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view
+ if (this.props.document === CurrentUserUtils.UserDocument.recentlyClosed) {
+ ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.GetProto(CurrentUserUtils.UserDocument.recentlyClosed as Doc).data = new List<Doc>(), icon: "plus" });
+ } else if (this.props.document !== CurrentUserUtils.UserDocument.workspaces) {
ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" });
ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "inTab"), icon: "folder" });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight"), icon: "caret-square-right" });
@@ -317,7 +318,6 @@ class TreeView extends React.Component<TreeViewProps> {
<ContentFittingDocumentView
Document={layoutDoc}
DataDocument={this.templateDataDoc}
- fieldKey={this.fieldKey}
renderDepth={this.props.renderDepth}
showOverlays={this.noOverlays}
ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider}
@@ -412,7 +412,7 @@ class TreeView extends React.Component<TreeViewProps> {
pinToPres: (document: Doc) => void,
screenToLocalXf: () => Transform,
outerXf: () => { translateX: number, translateY: number },
- active: () => boolean,
+ active: (outsideReaction?: boolean) => boolean,
panelWidth: () => number,
renderDepth: number,
showHeaderFields: () => boolean,
@@ -538,6 +538,11 @@ export class CollectionTreeView extends CollectionSubView(Document) {
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ } else if (!e.isPropagationStopped() && this.props.Document === CurrentUserUtils.UserDocument.recentlyClosed) {
+ ContextMenu.Instance.addItem({ description: "Clear All", event: () => CurrentUserUtils.UserDocument.recentlyClosed = new List<Doc>(), icon: "plus" });
+ e.stopPropagation();
+ e.preventDefault();
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
} else {
let layoutItems: ContextMenuProps[] = [];
layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8d5694bf0..8387e95df 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,6 +17,7 @@ import { CollectionTreeView } from "./CollectionTreeView";
import { CollectionViewBaseChrome } from './CollectionViewChromes';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { CollectionLinearView } from '../CollectionLinearView';
+import { CollectionStaffView } from './CollectionStaffView';
import { DocumentType } from '../../documents/DocumentTypes';
import { ImageField } from '../../../new_fields/URLField';
import { DocListCast } from '../../../new_fields/Doc';
@@ -30,6 +31,7 @@ import { DocumentManager } from '../../util/DocumentManager';
import { SelectionManager } from '../../util/SelectionManager';
import './CollectionView.scss';
import { FieldViewProps, FieldView } from '../nodes/FieldView';
+import { Touchable } from '../Touchable';
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
export enum CollectionViewType {
@@ -42,6 +44,7 @@ export enum CollectionViewType {
Masonry,
Pivot,
Linear,
+ Staff
}
export namespace CollectionViewType {
@@ -69,7 +72,7 @@ export interface CollectionRenderProps {
}
@observer
-export class CollectionView extends React.Component<FieldViewProps> {
+export class CollectionView extends Touchable<FieldViewProps> {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
private _reactionDisposer: IReactionDisposer | undefined;
@@ -110,7 +113,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
// bcz: Argh? What's the height of the collection chromes??
chromeHeight = () => (this.props.ChromeHeight ? this.props.ChromeHeight() : 0) + (this.props.Document.chromeStatus === "enabled" ? -60 : 0);
- active = () => this.props.isSelected() || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;
+ active = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0;
whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); };
@@ -165,6 +168,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
+ case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />);
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
@@ -205,6 +209,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
this.props.Document.autoHeight = true;
}, icon: "ellipsis-v"
});
+ subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" });
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) {
@@ -220,7 +225,11 @@ export class CollectionView extends React.Component<FieldViewProps> {
let layoutItems = existing && "subitems" in existing ? existing.subitems : [];
layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
!existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" });
- ContextMenu.Instance.addItem({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
+
+ let more = ContextMenu.Instance.findByDescription("More...");
+ let moreItems = more && "subitems" in more ? more.subitems : [];
+ moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) });
+ !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
}
}
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index c186d15f8..aa25a900c 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -23,6 +23,12 @@
.parentDocumentSelector-button {
pointer-events: all;
}
+.parentDocumentSelector-metadata {
+ pointer-events: auto;
+ padding-right: 5px;
+ width: 25px;
+ display: inline-block;
+}
.buttonSelector {
position: absolute;
display: inline-block;
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 8b6fa330c..4eb9e9d1e 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -13,10 +13,15 @@ import { DocumentManager } from "../../util/DocumentManager";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEdit } from "@fortawesome/free-solid-svg-icons";
import { library } from "@fortawesome/fontawesome-svg-core";
+import { MetadataEntryMenu } from "../MetadataEntryMenu";
+import { DocumentView } from "../nodes/DocumentView";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
library.add(faEdit);
-type SelectorProps = { Document: Doc, Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
+type SelectorProps = { Document: Doc, Views: DocumentView[], Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
@observer
export class SelectorContextMenu extends React.Component<SelectorProps> {
@observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -53,16 +58,23 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
};
}
+ get metadataMenu() {
+ return <div className="parentDocumentSelector-metadata">
+ <Flyout anchorPoint={anchorPoints.TOP_LEFT}
+ content={<MetadataEntryMenu docs={() => this.props.Views.map(dv => dv.props.Document)} suggestWithFunction />}>{/* tfs: @bcz This might need to be the data document? */}
+ <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
+ </Flyout>
+ </div>;
+ }
render() {
- return (
- <>
- <p key="contexts">Contexts:</p>
- {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
- {this._otherDocs.length ? <hr key="hr" /> : null}
- {this._otherDocs.map(doc => <p key="p"><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
- </>
- );
+ return <div >
+ <div key="metadata">Metadata: {this.metadataMenu}</div>
+ <p key="contexts">Contexts:</p>
+ {this._docs.map(doc => <p key={doc.col[Id] + doc.target[Id]}><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+ {this._otherDocs.length ? <hr key="hr" /> : null}
+ {this._otherDocs.map(doc => <p key="p"><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+ </div>;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 48d330674..e1d23ddcb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -6,6 +6,8 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
import { OverlayView, OverlayElementOptions } from "../../OverlayView";
import { emptyFunction } from "../../../../Utils";
import React = require("react");
+import { ObservableMap, runInAction } from "mobx";
+import { Id } from "../../../../new_fields/FieldSymbols";
interface PivotData {
type: string;
@@ -31,8 +33,7 @@ export interface ViewDefResult {
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();
+export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], viewDefsToJSX: (views: any) => ViewDefResult[]) {
const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200);
const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
@@ -49,6 +50,8 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: PivotData[] = [];
+ const expander = 1.05;
+ const gap = .15;
let x = 0;
pivotColumnGroups.forEach((val, key) => {
let y = 0;
@@ -58,25 +61,31 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
text: String(key),
x,
y: pivotAxisWidth + 50,
- width: pivotAxisWidth * 1.25 * numCols,
+ width: pivotAxisWidth * expander * numCols,
height: 100,
fontSize: NumCast(pivotDoc.pivotFontSize, 10)
});
for (const doc of val) {
let layoutDoc = Doc.Layout(doc);
+ let wid = pivotAxisWidth;
+ let hgt = layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
+ if (hgt > pivotAxisWidth) {
+ hgt = pivotAxisWidth;
+ wid = layoutDoc.nativeHeight ? (NumCast(layoutDoc.nativeWidth) / NumCast(layoutDoc.nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
+ }
docMap.set(doc, {
- x: x + xCount * pivotAxisWidth * 1.25,
+ x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2,
y: -y,
- width: pivotAxisWidth,
- height: layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth
+ width: wid,
+ height: hgt
});
xCount++;
if (xCount >= numCols) {
- xCount = 0;
- y += pivotAxisWidth * 1.25;
+ xCount = (pivotAxisWidth - wid) / 2;
+ y += pivotAxisWidth * expander;
}
}
- x += pivotAxisWidth * 1.25 * (numCols + 1);
+ x += pivotAxisWidth * (numCols * expander + gap);
});
childPairs.map(pair => {
@@ -88,9 +97,12 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs:
height: NumCast(pair.layout.height)
};
const pos = docMap.get(pair.layout) || defaultPosition;
- layoutPoolData.set(pair, { transition: "transform 1s", ...pos });
+ let data = poolData.get(pair.layout[Id]);
+ if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.width !== data.width || pos.height !== data.height) {
+ runInAction(() => poolData.set(pair.layout[Id], { transition: "transform 1s", ...pos }));
+ }
});
- return { map: layoutPoolData, elements: viewDefsToJSX(groupNames) };
+ return { elements: viewDefsToJSX(groupNames) };
}
export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 837413842..73b45edc6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -6,7 +6,8 @@ import "./CollectionFreeFormLinkView.scss";
import React = require("react");
import v5 = require("uuid/v5");
import { DocumentType } from "../../../documents/DocumentTypes";
-import { observable, action } from "mobx";
+import { observable, action, reaction, IReactionDisposer } from "mobx";
+import { StrCast, Cast } from "../../../../new_fields/Types";
export interface CollectionFreeFormLinkViewProps {
A: DocumentView;
@@ -16,33 +17,57 @@ export interface CollectionFreeFormLinkViewProps {
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
- @observable _alive: number = 0;
@observable _opacity: number = 1;
+ @observable _update: number = 0;
+ _anchorDisposer: IReactionDisposer | undefined;
@action
componentDidMount() {
- this._alive = 1;
- setTimeout(this.rerender, 50);
- setTimeout(action(() => this._opacity = 0.05), 50);
+ setTimeout(action(() => this._opacity = 0.05), 750);
+ this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform()],
+ () => {
+ let acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ let bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ let adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
+ let bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
+ let a = adiv.getBoundingClientRect();
+ let b = bdiv.getBoundingClientRect();
+ let abounds = adiv.parentElement!.getBoundingClientRect();
+ let bbounds = bdiv.parentElement!.getBoundingClientRect();
+ let apt = Utils.closestPtBetweenRectangles(abounds.left, abounds.top, abounds.width, abounds.height,
+ bbounds.left, bbounds.top, bbounds.width, bbounds.height,
+ a.left + a.width / 2, a.top + a.height / 2);
+ let bpt = Utils.closestPtBetweenRectangles(bbounds.left, bbounds.top, bbounds.width, bbounds.height,
+ abounds.left, abounds.top, abounds.width, abounds.height,
+ apt.point.x, apt.point.y);
+ let afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
+ let bfield = afield === "anchor1" ? "anchor2" : "anchor1";
+ this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
+ this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
+ this.props.A.props.Document[bfield + "_x"] = (bpt.point.x - bbounds.left) / bbounds.width * 100;
+ this.props.A.props.Document[bfield + "_y"] = (bpt.point.y - bbounds.top) / bbounds.height * 100;
+ this._update++;
+ }
+ , { fireImmediately: true });
}
@action
componentWillUnmount() {
- this._alive = 0;
+ this._anchorDisposer?.();
}
- rerender = action(() => {
- if (this._alive) {
- setTimeout(this.rerender, 50);
- this._alive++;
- }
- });
render() {
- let y = this._alive;
+ let y = this._update;
let acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
let bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
let a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
let b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
- let pt1 = Utils.getNearestPointInPerimeter(a.left, a.top, a.width, a.height, b.left + b.width / 2, b.top + b.height / 2);
- let pt2 = Utils.getNearestPointInPerimeter(b.left, b.top, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2);
+ let apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
+ b.left, b.top, b.width, b.height,
+ a.left + a.width / 2, a.top + a.height / 2);
+ let bpt = Utils.closestPtBetweenRectangles(b.left, b.top, b.width, b.height,
+ a.left, a.top, a.width, a.height,
+ apt.point.x, apt.point.y);
+ let pt1 = [apt.point.x, apt.point.y];
+ let pt2 = [bpt.point.x, bpt.point.y];
return (<line key="linkLine" className="collectionfreeformlinkview-linkLine"
style={{ opacity: this._opacity }}
x1={`${pt1[0]}`} y1={`${pt1[1]}`}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index bb1a12f88..070d4aa65 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,5 +1,6 @@
@import "../../globalCssVariables";
+.collectionfreeformview-none,
.collectionfreeformview-ease {
position: inherit;
top: 0;
@@ -7,16 +8,15 @@
width: 100%;
height: 100%;
transform-origin: left top;
+ border-radius: inherit;
+}
+
+.collectionfreeformview-ease {
transition: transform 1s;
}
.collectionfreeformview-none {
- position: inherit;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- transform-origin: left top;
+ touch-action: none;
}
.collectionFreeform-customText {
@@ -25,6 +25,9 @@
}
.collectionfreeformview-container {
+ // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
+ touch-action: none;
+
.collectionfreeformview>.jsx-parser {
position: inherit;
height: 100%;
@@ -41,7 +44,6 @@
// linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
// background-size: 30px 30px;
// }
- opacity: 0.99;
border: 0px solid $light-color-secondary;
border-radius: inherit;
box-sizing: border-box;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6e0f75bc1..9bbaa20e7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,41 +1,46 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace, ObservableMap, untracked, reaction, runInAction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
+import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { InkTool } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
import { aggregateBounds, emptyFunction, intersectRect, 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 { InteractionUtils } from "../../../util/InteractionUtils";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
import { ContextMenu } from "../../ContextMenu";
import { ContextMenuProps } from "../../ContextMenuItem";
-import { InkingCanvas } from "../../InkingCanvas";
+import { InkingControl } from "../../InkingControl";
+import { CreatePolyline } from "../../InkingStroke";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentViewProps } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
+import PDFMenu from "../../pdf/PDFMenu";
import { CollectionSubView } from "../CollectionSubView";
import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
+import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
+import { computedFn, keepAlive } from "mobx-utils";
+import { TraceMobx } from "../../../../new_fields/util";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -65,11 +70,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private _lastY: number = 0;
private _clusterDistance: number = 75;
private _hitCluster = false;
+ private _layoutComputeReaction: IReactionDisposer | undefined;
+ private _layoutPoolData = new ObservableMap<string, any>();
+
+ public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
+ @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@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 contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); }
@computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; }
@computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
@@ -182,21 +192,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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);
- let clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
- let de = new DragManager.DocumentDragData(eles);
- de.moveDocument = this.props.moveDocument;
- const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
- de.offset = this.getTransform().transformDirection(e.x - left, e.y - top);
- de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
- DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
- handlers: { dragComplete: action(emptyFunction) },
- hideSource: !de.dropAction
- });
- return true;
+ tryDragCluster(e: PointerEvent | TouchEvent) {
+ let ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0);
+ if (ptsParent) {
+ let cluster = this.pickCluster(this.getTransform().transformPoint(ptsParent.clientX, ptsParent.clientY));
+ if (cluster !== -1) {
+ let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster);
+ let clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
+ let de = new DragManager.DocumentDragData(eles);
+ de.moveDocument = this.props.moveDocument;
+ const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
+ de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
+ de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
+ DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, {
+ handlers: { dragComplete: action(emptyFunction) },
+ hideSource: !de.dropAction
+ });
+ return true;
+ }
}
return false;
@@ -259,94 +272,224 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return clusterColor;
}
+ @observable private _points: { x: number, y: number }[] = [];
+
@action
onPointerDown = (e: React.PointerEvent): void => {
if (e.nativeEvent.cancelBubble) return;
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 && !e.ctrlKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
+ if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ }
+ else {
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+ let point = this.getTransform().transformPoint(e.pageX, e.pageY);
+ this._points.push({ x: point[0], y: point[1] });
+ }
+ }
+ }
+ }
+
+ @action
+ handle1PointerDown = (e: React.TouchEvent) => {
+ let pt = e.targetTouches.item(0);
+ if (pt) {
+ this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false;
}
}
+ @action
onPointerUp = (e: PointerEvent): void => {
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCH) && this._points.length <= 1) return;
+
+ if (this._points.length > 1) {
+ let B = this.svgBounds;
+ let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top }));
+ let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
+ this.addDocument(inkDoc);
+ this._points = [];
+ }
+
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ }
+
+ @action
+ pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
+ // I think it makes sense for the marquee menu to go away when panned. -syip2
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+
+ let x = this.Document.panX || 0;
+ let y = this.Document.panY || 0;
+ let docs = this.childLayoutPairs.map(pair => pair.layout);
+ let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ if (!this.isAnnotationOverlay) {
+ PDFMenu.Instance.fadeOut(true);
+ let minx = docs.length ? NumCast(docs[0].x) : 0;
+ let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
+ let miny = docs.length ? NumCast(docs[0].y) : 0;
+ let maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+ let ranges = docs.filter(doc => doc).reduce((range, doc) => {
+ let layoutDoc = Doc.Layout(doc);
+ let x = NumCast(doc.x);
+ let xe = x + NumCast(layoutDoc.width);
+ let y = NumCast(doc.y);
+ let ye = y + NumCast(layoutDoc.height);
+ return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
+ [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
+ }, [[minx, maxx], [miny, maxy]]);
+
+ let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
+ 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;
+ if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;
+ }
+ this.setPan(x - dx, y - dy);
+ this._lastX = e.clientX;
+ this._lastY = e.clientY;
}
@action
onPointerMove = (e: PointerEvent): void => {
- if (!e.cancelBubble) {
- 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);
- document.removeEventListener("pointerup", this.onPointerUp);
- return;
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) {
+ if (this.props.active(true)) {
+ e.stopPropagation();
}
- let x = this.Document.panX || 0;
- let y = this.Document.panY || 0;
- let docs = this.childLayoutPairs.map(pair => pair.layout);
- let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- if (!this.isAnnotationOverlay) {
- let minx = docs.length ? NumCast(docs[0].x) : 0;
- let maxx = docs.length ? NumCast(Doc.Layout(docs[0]).width) + minx : minx;
- let miny = docs.length ? NumCast(docs[0].y) : 0;
- let maxy = docs.length ? NumCast(Doc.Layout(docs[0]).height) + miny : miny;
- let ranges = docs.filter(doc => doc).reduce((range, doc) => {
- let layoutDoc = Doc.Layout(doc);
- let x = NumCast(doc.x);
- let xe = x + NumCast(layoutDoc.width);
- let y = NumCast(doc.y);
- let ye = y + NumCast(layoutDoc.height);
- return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
- [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
- }, [[minx, maxx], [miny, maxy]]);
- let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField);
- if (ink && ink.inkData) {
- ink.inkData.forEach((value: StrokeData, key: string) => {
- let bounds = InkingCanvas.StrokeRect(value);
- ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
- ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
- });
+ return;
+ }
+ if (!e.cancelBubble) {
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
+ 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);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ return;
}
-
- let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
- 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;
- if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;
+ this.pan(e);
+ }
+ else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+ let point = this.getTransform().transformPoint(e.clientX, e.clientY);
+ this._points.push({ x: point[0], y: point[1] });
}
- this.setPan(x - dx, y - dy);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
}
- @action
- onPointerWheel = (e: React.WheelEvent): void => {
- if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return;
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
+ handle1PointerMove = (e: TouchEvent) => {
+ // panning a workspace
+ if (!e.cancelBubble) {
+ let pt = e.targetTouches.item(0);
+ if (pt) {
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
+ 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);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ return;
+ }
+ this.pan(pt);
+ }
+ else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+ let point = this.getTransform().transformPoint(pt.clientX, pt.clientY);
+ this._points.push({ x: point[0], y: point[1] });
+ }
+ }
e.stopPropagation();
+ e.preventDefault();
}
- else if (this.props.active()) {
- e.stopPropagation();
- let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1;
- if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
- deltaScale = 1 / this.zoomScaling();
+ }
+
+ handle2PointersMove = (e: TouchEvent) => {
+ // pinch zooming
+ if (!e.cancelBubble) {
+ let pt1: Touch | null = e.targetTouches.item(0);
+ let pt2: Touch | null = e.targetTouches.item(1);
+ if (!pt1 || !pt2) return;
+
+ if (this.prevPoints.size === 2) {
+ let oldPoint1 = this.prevPoints.get(pt1.identifier);
+ let oldPoint2 = this.prevPoints.get(pt2.identifier);
+ if (oldPoint1 && oldPoint2) {
+ let dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2);
+
+ // if zooming, zoom
+ if (dir !== 0) {
+ let d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2));
+ let d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2));
+ let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+
+ // calculate the raw delta value
+ let rawDelta = (dir * (d1 + d2));
+
+ // this floors and ceils the delta value to prevent jitteriness
+ let delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 16);
+ this.zoom(centerX, centerY, delta);
+ this.prevPoints.set(pt1.identifier, pt1);
+ this.prevPoints.set(pt2.identifier, pt2);
+ }
+ // this is not zooming. derive some form of panning from it.
+ else {
+ // use the centerx and centery as the "new mouse position"
+ let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+ this.pan({ clientX: centerX, clientY: centerY });
+ this._lastX = centerX;
+ this._lastY = centerY;
+ }
+ }
}
- if (deltaScale < 0) deltaScale = -deltaScale;
- let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
- let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ handle2PointersDown = (e: React.TouchEvent) => {
+ let pt1: React.Touch | null = e.targetTouches.item(0);
+ let pt2: React.Touch | null = e.targetTouches.item(1);
+ if (!pt1 || !pt2) return;
+
+ let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+ this._lastX = centerX;
+ this._lastY = centerY;
+ }
+
+ cleanUpInteractions = () => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ }
+
+ @action
+ zoom = (pointX: number, pointY: number, deltaY: number): void => {
+ let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1;
+ if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
+ deltaScale = 1 / this.zoomScaling();
+ }
+ if (deltaScale < 0) deltaScale = -deltaScale;
+ let [x, y] = this.getTransform().transformPoint(pointX, pointY);
+ let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
+
+ if (localTransform.Scale >= 0.15) {
let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
this.props.Document.scale = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
@@ -354,8 +497,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@action
+ onPointerWheel = (e: React.WheelEvent): void => {
+ if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return;
+ if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming
+ e.stopPropagation();
+ }
+ else if (this.props.active(true)) {
+ e.stopPropagation();
+ this.zoom(e.clientX, e.clientY, e.deltaY);
+ }
+ }
+
+ @action
setPan(panX: number, panY: number, panType: string = "None") {
- if (!this.Document.lockedPosition || this.Document.inOverlay) {
+ if (!this.Document.lockedTransform || this.Document.inOverlay) {
this.Document.panTransformType = panType;
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
@@ -467,12 +622,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } {
- const script = this.Document.arrangeScript;
- const result = script && script.script.run(params, console.log);
- const layoutDoc = Doc.Layout(params.doc);
- if (result && result.success) {
+ const result = this.Document.arrangeScript?.script.run(params, console.log);
+ if (result?.success) {
return { ...result, transition: "transform 1s" };
}
+ const layoutDoc = Doc.Layout(params.doc);
return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") };
}
@@ -499,64 +653,57 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- 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;
- }
- computedElementData.map.forEach((value: any, key: { layout: Doc, data?: Doc }) => {
- if (key.layout === doc && key.data === dataDoc) {
- data = value;
- }
- });
- return data && { x: data.x, y: data.y, z: data.z, width: data.width, height: data.height, transition: data.transition };
- }
+ childDataProvider = computedFn(function childDataProvider(doc: Doc) { return (this as any)._layoutPoolData.get(doc[Id]); }.bind(this));
- @computed
- get doPivotLayout() {
- return computePivotLayout(this.props.Document, this.childDocs,
+ doPivotLayout(poolData: ObservableMap<string, any>) {
+ return computePivotLayout(poolData, this.props.Document, this.childDocs,
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), this.viewDefsToJSX);
}
- @computed
- get doFreeformLayout() {
- let layoutPoolData: Map<{ layout: Doc, data?: Doc }, any> = new Map();
+ doFreeformLayout(poolData: ObservableMap<string, any>) {
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 data = poolData.get(pair.layout[Id]);
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);
+ if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.width !== data.width || pos.height !== data.height || pos.transition !== data.transition) {
+ runInAction(() => poolData.set(pair.layout[Id], pos));
+ }
});
- return { map: layoutPoolData, elements: elements };
+ return { elements: elements };
}
- @computed
get doLayoutComputation() {
- let computedElementData: { map: Map<{ layout: Doc, data?: Doc | undefined }, any>, elements: ViewDefResult[] };
+ let computedElementData: { elements: ViewDefResult[] };
switch (this.Document.freeformLayoutEngine) {
- case "pivot": computedElementData = this.doPivotLayout; break;
- default: computedElementData = this.doFreeformLayout; break;
+ case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break;
+ default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break;
}
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
computedElementData.elements.push({
- ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.lookupLayout}
+ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.childDataProvider}
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)
+ bounds: this.childDataProvider(pair.layout)
}));
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); }
+ componentDidMount() {
+ this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; },
+ action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)),
+ { fireImmediately: true, name: "doLayout" });
+ }
+ componentWillUnmount() {
+ this._layoutComputeReaction && this._layoutComputeReaction();
+ }
+ @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
+ elementFunc = () => this._layoutElements;
@action
onCursorMove = (e: React.PointerEvent) => {
@@ -601,11 +748,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
analyzeStrokes = async () => {
- const extensionDoc = this.extensionDoc;
- let data = extensionDoc && Cast(extensionDoc.ink, InkField);
- if (data && extensionDoc) {
- CognitiveServices.Inking.Appliers.ConcatenateHandwriting(extensionDoc, ["inkAnalysis", "handwriting"], data.inkData);
- }
+ // CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], data.inkData);
}
onContextMenu = (e: React.MouseEvent) => {
@@ -669,34 +812,74 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
...this.views,
];
}
+
+ @computed get svgBounds() {
+ let xs = this._points.map(p => p.x);
+ let ys = this._points.map(p => p.y);
+ let right = Math.max(...xs);
+ let left = Math.min(...xs);
+ let bottom = Math.max(...ys);
+ let top = Math.min(...ys);
+ return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
+ }
+
+ @computed get currentStroke() {
+ if (this._points.length <= 1) {
+ return (null);
+ }
+
+ let B = this.svgBounds;
+
+ return (
+ <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)` }}>
+ {CreatePolyline(this._points, B.left, B.top)}
+ </svg>
+ );
+ }
+
+ children = () => {
+ let eles: JSX.Element[] = [];
+ this.extensionDoc && (eles.push(...this.childViews()));
+ this.currentStroke && (eles.push(this.currentStroke));
+ eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
+ return eles;
+ }
render() {
+ TraceMobx();
// update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
- this.Document.fitX = this.contentBounds && this.contentBounds.x;
- this.Document.fitY = this.contentBounds && this.contentBounds.y;
- this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
- this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
+ // this.Document.fitX = this.contentBounds && this.contentBounds.x;
+ // this.Document.fitY = this.contentBounds && this.contentBounds.y;
+ // this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x);
+ // this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
// if isAnnotationOverlay 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
return !this.extensionDoc ? (null) :
- <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
- style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
+ <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ style={{ height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
<MarqueeView {...this.props} extensionDoc={this.extensionDoc} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
- {!this.extensionDoc ? (null) :
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.extensionDoc} inkFieldKey={"ink"} >
- {this.childViews}
- </InkingCanvas>}
- <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+ {this.children}
</CollectionFreeFormViewPannableContents>
</MarqueeView>
- {this.overlayViews}
+ <CollectionFreeFormOverlayView elements={this.elementFunc} />
</div>;
}
}
+interface CollectionFreeFormOverlayViewProps {
+ elements: () => ViewDefResult[];
+}
+
+@observer
+class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps>{
+ render() {
+ return this.props.elements().filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+ }
+}
+
interface CollectionFreeFormViewPannableContentsProps {
centeringShiftX: () => number;
centeringShiftY: () => number;
@@ -704,6 +887,7 @@ interface CollectionFreeFormViewPannableContentsProps {
panY: () => number;
zoomScaling: () => number;
easing: () => boolean;
+ children: () => JSX.Element[];
}
@observer
@@ -715,8 +899,8 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const panx = -this.props.panX();
const pany = -this.props.panY();
const zoom = this.props.zoomScaling();
- return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
- {this.props.children}
+ return <div className={freeformclass} style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
+ {this.props.children()}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
new file mode 100644
index 000000000..28ddc19d7
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -0,0 +1,46 @@
+import React = require("react");
+import AntimodeMenu from "../../AntimodeMenu";
+import { observer } from "mobx-react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { unimplementedFunction } from "../../../../Utils";
+
+@observer
+export default class MarqueeOptionsMenu extends AntimodeMenu {
+ static Instance: MarqueeOptionsMenu;
+
+ public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public showMarquee: () => void = unimplementedFunction;
+ public hideMarquee: () => void = unimplementedFunction;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ MarqueeOptionsMenu.Instance = this;
+ }
+
+ render() {
+ let buttons = [
+ <button
+ className="antimodeMenu-button"
+ title="Create a Collection"
+ onPointerDown={this.createCollection}>
+ <FontAwesomeIcon icon="object-group" size="lg" />
+ </button>,
+ <button
+ className="antimodeMenu-button"
+ title="Summarize Documents"
+ onPointerDown={this.summarize}>
+ <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
+ </button>,
+ <button
+ className="antimodeMenu-button"
+ title="Delete Documents"
+ onPointerDown={this.delete}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" />
+ </button>,
+ ];
+ return this.getElement(buttons);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 04f6ec2ad..d14495626 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -8,6 +8,7 @@
}
.marqueeView {
overflow: hidden;
+ pointer-events: inherit;
}
.marqueeView:focus-within {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 44b6fe030..5ed3fecb5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../../new_fields/Doc";
-import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { InkField, PointData } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
import { listSpec } from "../../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
@@ -13,12 +13,13 @@ import { Docs } from "../../../documents/Documents";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
-import { InkingCanvas } from "../../InkingCanvas";
import { PreviewCursor } from "../../PreviewCursor";
import { CollectionViewType } from "../CollectionView";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
import "./MarqueeView.scss";
import React = require("react");
+import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import InkSelectDecorations from "../../InkSelectDecorations";
import { SubCollectionViewProps } from "../CollectionSubView";
interface MarqueeViewProps {
@@ -26,7 +27,7 @@ interface MarqueeViewProps {
getTransform: () => Transform;
addDocument: (doc: Doc) => boolean;
activeDocuments: () => Doc[];
- selectDocuments: (docs: Doc[]) => void;
+ selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void;
removeDocument: (doc: Doc) => boolean;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
@@ -51,13 +52,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@action
- cleanupInteractions = (all: boolean = false) => {
+ cleanupInteractions = (all: boolean = false, hideMarquee: boolean = true) => {
if (all) {
document.removeEventListener("pointerup", this.onPointerUp, true);
document.removeEventListener("pointermove", this.onPointerMove, true);
}
document.removeEventListener("keydown", this.marqueeCommand, true);
- this._visible = false;
+ if (hideMarquee) {
+ this._visible = false;
+ }
}
@undoBatch
@@ -188,15 +191,33 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onPointerUp = (e: PointerEvent): void => {
- if (!this.props.active()) this.props.selectDocuments([this.props.Document]);
+ if (!this.props.active(true)) this.props.selectDocuments([this.props.Document], []);
if (this._visible) {
let mselect = this.marqueeSelect();
if (!e.shiftKey) {
SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
}
- this.props.selectDocuments(mselect.length ? mselect : [this.props.Document]);
+ // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
+ // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
+ let docs = mselect.length ? mselect : [this.props.Document];
+ this.props.selectDocuments(docs, []);
}
- this.cleanupInteractions(true);
+ if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
+ MarqueeOptionsMenu.Instance.createCollection = this.collection;
+ MarqueeOptionsMenu.Instance.delete = this.delete;
+ MarqueeOptionsMenu.Instance.summarize = this.summary;
+ MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
+ MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
+ MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
+ }
+ this.cleanupInteractions(true, this._commandExecuted);
+
+ let hideMarquee = () => {
+ this.hideMarquee();
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ document.removeEventListener("pointerdown", hideMarquee);
+ };
+ document.addEventListener("pointerdown", hideMarquee);
if (e.altKey) {
e.preventDefault();
@@ -246,6 +267,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
}
+ get inkDoc() {
+ return this.props.extensionDoc;
+ }
+
get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored.
return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField);
}
@@ -254,6 +279,120 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.props.extensionDoc && (this.props.extensionDoc.ink = value);
}
+ @action
+ showMarquee = () => {
+ this._visible = true;
+ }
+
+ @action
+ hideMarquee = () => {
+ this._visible = false;
+ }
+
+ @action
+ delete = () => {
+ this.marqueeSelect(false).map(d => this.props.removeDocument(d));
+ if (this.ink) {
+ // this.marqueeInkDelete(this.ink.inkData);
+ }
+ SelectionManager.DeselectAll();
+ this.cleanupInteractions(false);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ }
+
+ getCollection = (selected: Doc[]) => {
+ let bounds = this.Bounds;
+ let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
+ "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
+ let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string"));
+ if (!colorPalette) this.props.Document.colorPalette = new List<string>(defaultPalette);
+ let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]);
+ let usedPaletted = new Map<string, number>();
+ [...this.props.activeDocuments(), this.props.Document].map(child => {
+ let bg = StrCast(Doc.Layout(child).backgroundColor);
+ if (palette.indexOf(bg) !== -1) {
+ palette.splice(palette.indexOf(bg), 1);
+ if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
+ else usedPaletted.set(bg, 1);
+ }
+ });
+ usedPaletted.delete("#f1efeb");
+ usedPaletted.delete("white");
+ usedPaletted.delete("rgba(255,255,255,1)");
+ let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
+ let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
+ let inkData = this.ink ? this.ink.inkData : undefined;
+ let newCollection = Docs.Create.FreeformDocument(selected, {
+ x: bounds.left,
+ y: bounds.top,
+ panX: 0,
+ panY: 0,
+ backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
+ defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
+ width: bounds.width,
+ height: bounds.height,
+ title: "a nested collection",
+ });
+ let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
+ // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
+ // this.marqueeInkDelete(inkData);
+ this.hideMarquee();
+ return newCollection;
+ }
+
+ @action
+ collection = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect(false);
+ if (e instanceof KeyboardEvent ? e.key === "c" : true) {
+ selected.map(d => {
+ this.props.removeDocument(d);
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+ d.displayTimecode = undefined;
+ return d;
+ });
+ }
+ let newCollection = this.getCollection(selected);
+ this.props.addDocument(newCollection);
+ this.props.selectDocuments([newCollection], []);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ }
+
+ @action
+ summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect(false);
+ let newCollection = this.getCollection(selected);
+
+ selected.map(d => {
+ this.props.removeDocument(d);
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+ d.page = -1;
+ return d;
+ });
+ newCollection.chromeStatus = "disabled";
+ let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+ Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
+ newCollection.x = bounds.left + bounds.width;
+ Doc.GetProto(newCollection).summaryDoc = summary;
+ Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
+ if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
+ let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
+ container.viewType = CollectionViewType.Stacking;
+ container.autoHeight = true;
+ Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight"
+ this.props.addLiveTextDocument(container);
+ } else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
+ Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight"
+ this.props.addLiveTextDocument(summary);
+ }
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ }
+
@undoBatch
@action
marqueeCommand = async (e: KeyboardEvent) => {
@@ -264,12 +403,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._commandExecuted = true;
e.stopPropagation();
(e as any).propagationIsStopped = true;
- this.marqueeSelect(false).map(d => this.props.removeDocument(d));
- if (this.ink) {
- this.marqueeInkDelete(this.ink.inkData);
- }
- SelectionManager.DeselectAll();
- this.cleanupInteractions(false);
+ this.delete();
e.stopPropagation();
}
if (e.key === "c" || e.key === "s" || e.key === "S") {
@@ -277,117 +411,55 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
e.stopPropagation();
e.preventDefault();
(e as any).propagationIsStopped = true;
- let bounds = this.Bounds;
- let selected = this.marqueeSelect(false);
if (e.key === "c") {
- selected.map(d => {
- this.props.removeDocument(d);
- d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
- d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- d.displayTimecode = undefined;
- return d;
- });
+ this.collection(e);
}
- let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
- "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
- let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string"));
- if (!colorPalette) this.props.Document.colorPalette = new List<string>(defaultPalette);
- let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]);
- let usedPaletted = new Map<string, number>();
- [...this.props.activeDocuments(), this.props.Document].map(child => {
- let bg = StrCast(Doc.Layout(child).backgroundColor);
- if (palette.indexOf(bg) !== -1) {
- palette.splice(palette.indexOf(bg), 1);
- if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
- else usedPaletted.set(bg, 1);
- }
- });
- usedPaletted.delete("#f1efeb");
- usedPaletted.delete("white");
- usedPaletted.delete("rgba(255,255,255,1)");
- let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
- let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
- let inkData = this.ink ? this.ink.inkData : undefined;
- let newCollection = Docs.Create.FreeformDocument(selected, {
- x: bounds.left,
- y: bounds.top,
- panX: 0,
- panY: 0,
- backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
- defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
- width: bounds.width,
- height: bounds.height,
- title: "a nested collection",
- });
- let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
- dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
- this.marqueeInkDelete(inkData);
if (e.key === "s" || e.key === "S") {
- selected.map(d => {
- this.props.removeDocument(d);
- d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
- d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- d.page = -1;
- return d;
- });
- newCollection.chromeStatus = "disabled";
- let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
- Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
- newCollection.x = bounds.left + bounds.width;
- Doc.GetProto(newCollection).summaryDoc = summary;
- Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
- if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
- let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
- container.viewType = CollectionViewType.Stacking;
- container.autoHeight = true;
- Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight"
- this.props.addLiveTextDocument(container);
- } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
- Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight"
- this.props.addLiveTextDocument(summary);
- }
- }
- else {
- this.props.addDocument(newCollection);
- this.props.selectDocuments([newCollection]);
+ this.summary(e);
}
this.cleanupInteractions(false);
}
}
- @action
- marqueeInkSelect(ink: Map<any, any>) {
- let idata = new Map();
- let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
- let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
- ink.forEach((value: StrokeData, key: string, map: any) => {
- if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
- idata.set(key,
- {
- pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })),
- color: value.color,
- width: value.width,
- tool: value.tool,
- page: -1
- });
- }
- });
- return idata;
- }
+ // @action
+ // marqueeInkSelect(ink: Map<any, any>) {
+ // let idata = new Map();
+ // let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+ // let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
+ // ink.forEach((value: PointData, key: string, map: any) => {
+ // if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
+ // // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling());
+ // idata.set(key,
+ // {
+ // pathData: value.pathData.map(val => {
+ // let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y);
+ // return { x: tVal[0], y: tVal[1] };
+ // // return { x: val.x + centerShiftX, y: val.y + centerShiftY }
+ // }),
+ // color: value.color,
+ // width: value.width,
+ // tool: value.tool,
+ // page: -1
+ // });
+ // }
+ // });
+ // // InkSelectDecorations.Instance.SetSelected(idata);
+ // return idata;
+ // }
- @action
- marqueeInkDelete(ink?: Map<any, any>) {
- // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
- // ink.forEach((value: StrokeData, key: string, map: any) =>
- // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
+ // @action
+ // marqueeInkDelete(ink?: Map<any, any>) {
+ // // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
+ // // ink.forEach((value: StrokeData, key: string, map: any) =>
+ // // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
- if (ink) {
- let idata = new Map();
- ink.forEach((value: StrokeData, key: string, map: any) =>
- !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
- this.ink = new InkField(idata);
- }
- }
+ // if (ink) {
+ // let idata = new Map();
+ // ink.forEach((value: PointData, key: string, map: any) =>
+ // !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
+ // this.ink = new InkField(idata);
+ // }
+ // }
marqueeSelect(selectBackgrounds: boolean = true) {
let selRect = this.Bounds;
@@ -438,8 +510,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
get marqueeDiv() {
let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0];
let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ /**
+ * @RE - The commented out span below
+ * This contains the "C for collection, ..." text on marquees.
+ * Commented out by syip2 when the marquee menu was added.
+ */
return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} >
- <span className="marquee-legend" />
+ {/* <span className="marquee-legend" /> */}
</div>;
}
diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/ButtonBox.scss
index e8a3d1479..7c3825978 100644
--- a/src/client/views/nodes/ButtonBox.scss
+++ b/src/client/views/nodes/ButtonBox.scss
@@ -3,7 +3,7 @@
height: 100%;
pointer-events: all;
border-radius: inherit;
- display:flex;
+ display: flex;
flex-direction: column;
}
@@ -15,19 +15,22 @@
display: table;
overflow: hidden;
text-overflow: ellipsis;
+ letter-spacing: 2px;
+ text-transform: uppercase;
}
+
.buttonBox-mainButtonCenter {
height: 100%;
- display:table-cell;
+ display: table-cell;
vertical-align: middle;
}
.buttonBox-params {
- display:flex;
- flex-direction: row;
+ display: flex;
+ flex-direction: row;
}
.buttonBox-missingParam {
- width:100%;
+ width: 100%;
background: lightgray;
} \ No newline at end of file
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index beb2b30fd..659ba154a 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -79,7 +79,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
return (
<div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
- <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black" }} >
+ <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black", fontSize: this.Document.fontSize }} >
<div className="buttonBox-mainButtonCenter">
{(this.Document.text || this.Document.title)}
</div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index af9232c2f..da287649e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -2,6 +2,7 @@
transform-origin: left top;
position: absolute;
background-color: transparent;
- top:0;
- left:0;
+ touch-action: manipulation;
+ top: 0;
+ left: 0;
} \ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 83638b240..c85b59488 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -10,9 +10,10 @@ import "./CollectionFreeFormDocumentView.scss";
import { DocumentView, DocumentViewProps } from "./DocumentView";
import React = require("react");
import { PositionDocument } from "../../../new_fields/documentSchemas";
+import { TraceMobx } from "../../../new_fields/util";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
+ dataProvider?: (doc: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined;
x?: number;
y?: number;
width?: number;
@@ -24,12 +25,16 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, PositionDocument>(PositionDocument) {
_disposer: IReactionDisposer | undefined = undefined;
+ get displayName() { return "CollectionFreeFormDocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; }
get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); }
get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); }
get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); }
- get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); }
- @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; }
+ get height() {
+ let hgt = this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym]();
+ return (hgt === undefined && this.nativeWidth && this.nativeHeight) ? this.width * this.nativeHeight / this.nativeWidth : hgt;
+ }
+ @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document) ? this.props.dataProvider(this.props.Document) : undefined; }
@computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); }
@computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); }
@@ -52,11 +57,9 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
this._disposer && this._disposer();
}
componentDidMount() {
- this._disposer = reaction(() => [this.props.Document.animateToPos, this.props.Document.isAnimating],
- () => {
- const target = this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined;
- this._animPos = !target ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]);
- }, { fireImmediately: true });
+ this._disposer = reaction(() => this.props.Document.animateToPos ? Array.from(Cast(this.props.Document.animateToPos, listSpec("number"))!) : undefined,
+ target => this._animPos = !target ? undefined : target[2] ? [NumCast(this.layoutDoc.x), NumCast(this.layoutDoc.y)] : this.props.ScreenToLocalTransform().transformPoint(target[0], target[1]),
+ { fireImmediately: true });
}
contentScaling = () => this.nativeWidth > 0 && !this.props.Document.ignoreAspect ? this.width / this.nativeWidth : 1;
@@ -84,7 +87,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight();
render() {
- // trace();
+ TraceMobx();
return <div className="collectionFreeFormDocumentView-container"
style={{
boxShadow:
@@ -95,7 +98,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 : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
+ transition: this.Document.isAnimating ? ".5s ease-in" : this.props.transition ? this.props.transition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.transition),
width: this.width,
height: this.height,
zIndex: this.Document.zIndex || 0,
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index fda6d64f4..40674b034 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -38,7 +38,9 @@ export class ColorBox extends DocExtendableComponent<FieldViewProps, ColorDocume
render() {
return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
- onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
+ onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
+ style={{ transformOrigin: "top left", transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
+
<SketchPicker color={this._startupColor} onChange={InkingControl.Instance.switchColor} />
</div>;
}
diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx
index f41c4fc91..573a55710 100644
--- a/src/client/views/nodes/ContentFittingDocumentView.tsx
+++ b/src/client/views/nodes/ContentFittingDocumentView.tsx
@@ -20,7 +20,6 @@ interface ContentFittingDocumentViewProps {
childDocs?: Doc[];
renderDepth: number;
fitToBox?: boolean;
- fieldKey: string;
PanelWidth: () => number;
PanelHeight: () => number;
ruleProvider: Doc | undefined;
@@ -33,10 +32,11 @@ interface ContentFittingDocumentViewProps {
addDocument: (document: Doc) => boolean;
moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
removeDocument: (document: Doc) => boolean;
- active: () => boolean;
+ active: (outsideReaction: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
+ dontRegisterView?: boolean;
setPreviewScript: (script: string) => void;
previewScript?: string;
}
@@ -108,6 +108,7 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo
focus={this.props.focus || emptyFunction}
backgroundColor={returnEmptyString}
bringToFront={emptyFunction}
+ dontRegisterView={this.props.dontRegisterView}
zoomToScale={emptyFunction}
getScale={returnOne}
/>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 779d25cdd..b9b84d5ce 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -30,6 +30,7 @@ import { DocuLinkBox } from "./DocuLinkBox";
import { PresElementBox } from "../presentationview/PresElementBox";
import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
+import { InkingStroke } from "../InkingStroke";
import React = require("react");
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -49,7 +50,7 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
@observer
export class DocumentContentsView extends React.Component<DocumentViewProps & {
- isSelected: () => boolean,
+ isSelected: (outsideReaction: boolean) => boolean,
select: (ctrl: boolean) => void,
onClick?: ScriptField,
layoutKey: string,
@@ -97,7 +98,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
components={{
FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DocuLinkBox
+ PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
+ ColorBox, DocuLinkBox, InkingStroke
}}
bindings={this.CreateBindings()}
jsx={this.layout}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 65df86d27..dfb84ed5c 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,13 +1,14 @@
@import "../globalCssVariables";
-.documentView-node, .documentView-node-topmost {
+.documentView-node,
+.documentView-node-topmost {
position: inherit;
top: 0;
- left:0;
+ left: 0;
border-radius: inherit;
- transition : outline .3s linear;
+ transition: outline .3s linear;
cursor: grab;
-
+
// background: $light-color; //overflow: hidden;
transform-origin: left top;
@@ -37,44 +38,48 @@
position: absolute;
transform-origin: top left;
width: 100%;
- height: 100%;
+ height: 100%;
}
+
.documentView-styleWrapper {
- position: absolute;
+ position: absolute;
display: inline-block;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
pointer-events: none;
.documentView-styleContentWrapper {
- width:100%;
+ width: 100%;
display: inline-block;
position: absolute;
}
+
.documentView-titleWrapper {
- overflow:hidden;
- color: white;
- transform-origin: top left;
- top: 0;
+ overflow: hidden;
+ color: white;
+ transform-origin: top left;
+ top: 0;
+ width: 100%;
height: 25;
background: rgba(0, 0, 0, .4);
- padding: 4px;
- text-align: center;
- text-overflow: ellipsis;
+ padding: 4px;
+ text-align: center;
+ text-overflow: ellipsis;
white-space: pre;
}
.documentView-searchHighlight {
- position: absolute;
- background: yellow;
+ position: absolute;
+ background: yellow;
bottom: -20px;
border-radius: 5px;
- transform-origin: bottom left;
+ transform-origin: bottom left;
}
.documentView-captionWrapper {
- position: absolute;
- bottom: 0;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
transform-origin: bottom left;
}
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 304555c30..467fd4b32 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -11,13 +11,12 @@ import { ScriptField } from '../../../new_fields/ScriptField';
import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { ImageField } from '../../../new_fields/URLField';
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, returnTransparent, returnTrue, Utils } from "../../../Utils";
+import { emptyFunction, returnTransparent, returnTrue, Utils, returnOne } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentType } from '../../documents/DocumentTypes';
import { ClientUtils } from '../../util/ClientUtils';
-import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, dropActionType } from "../../util/DragManager";
import { LinkManager } from '../../util/LinkManager';
@@ -41,6 +40,10 @@ import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import { FormattedTextBox } from './FormattedTextBox';
import React = require("react");
+import { InteractionUtils } from '../../util/InteractionUtils';
+import { InkingControl } from '../InkingControl';
+import { InkTool } from '../../../new_fields/InkField';
+import { TraceMobx } from '../../../new_fields/util';
library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight,
fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale,
@@ -64,7 +67,7 @@ export interface DocumentViewProps {
PanelWidth: () => number;
PanelHeight: () => number;
focus: (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => void;
- parentActive: () => boolean;
+ parentActive: (outsideReaction: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
bringToFront: (doc: Doc, sendToBack?: boolean) => void;
addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean;
@@ -74,6 +77,7 @@ export interface DocumentViewProps {
getScale: () => number;
animateBetweenIcon?: (maximize: boolean, target: number[]) => void;
ChromeHeight?: () => number;
+ dontRegisterView?: boolean;
layoutKey?: string;
}
@@ -88,8 +92,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
+ public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
public get ContentDiv() { return this._mainCont.current; }
- @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
+ @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); }
@computed get topMost() { return this.props.renderDepth === 0; }
@computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; }
@computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; }
@@ -98,7 +103,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
componentDidMount() {
this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }));
- DocumentManager.Instance.DocumentViews.push(this);
+
+ !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
}
@action
@@ -111,7 +117,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
componentWillUnmount() {
this._dropDisposer && this._dropDisposer();
Doc.UnBrushDoc(this.props.Document);
- DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+ !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
}
startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) {
@@ -136,7 +142,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
e.stopPropagation();
let preventDefault = true;
- if (this._doubleTap && this.props.renderDepth && (!this.onClickHandler || !this.onClickHandler.script)) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+ if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
let fullScreenAlias = Doc.MakeAlias(this.props.Document);
if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) {
fullScreenAlias.layoutKey = "layoutCustom";
@@ -206,14 +212,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
document.addEventListener("pointerup", this.onPointerUp);
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
}
+
onPointerMove = (e: PointerEvent): void => {
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
if (e.cancelBubble && this.active) {
document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
}
- else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive() || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+ else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
- if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && e.buttons === 1) {
+ if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCH))) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
@@ -223,6 +230,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.preventDefault();
}
}
+
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -350,9 +358,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
setCustomView = (custom: boolean): void => {
- if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) {
- Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc);
- } else { // bcz: not robust -- for now documents with string layout are native documents, and those with Doc layouts are customized
+ if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document);
+ } else {
custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document);
}
}
@@ -370,15 +378,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true;
}
- listen = async () => {
- Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({
- continuous: { indefinite: true },
- interimHandler: (results: string) => {
- DictationOverlay.Instance.dictationSuccess = true;
- DictationOverlay.Instance.dictatedPhrase = results;
- DictationOverlay.Instance.isListening = { interim: true };
- }
- });
+ @undoBatch
+ @action
+ toggleLockTransform = (): void => {
+ this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true;
}
@action
@@ -402,14 +405,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" });
cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" });
- if (Cast(this.props.Document.data, ImageField)) {
- cm.addItem({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
- }
- if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
- cm.addItem({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
- cm.addItem({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
- cm.addItem({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
- }
let existingOnClick = ContextMenu.Instance.findByDescription("OnClick...");
let onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -427,10 +422,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
!existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
let funcs: ContextMenuProps[] = [];
- funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
- funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
- funcs.push({ description: "Drag Document", icon: "edit", event: () => this.Document.onDragStart = undefined });
- ContextMenu.Instance.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+ if (this.Document.onDragStart) {
+ funcs.push({ description: "Drag an Alias", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) });
+ funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.Document.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) });
+ funcs.push({ description: "Drag Document", icon: "edit", event: () => this.Document.onDragStart = undefined });
+ ContextMenu.Instance.addItem({ description: "OnDrag...", subitems: funcs, icon: "asterisk" });
+ }
let existing = ContextMenu.Instance.findByDescription("Layout...");
let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : [];
@@ -442,6 +439,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" });
layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" });
+ layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" });
layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) {
@@ -450,18 +448,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
layoutItems.push({ description: "Use Native Layout", event: () => DocumentView.makeNativeViewClicked(this.props.Document), icon: "concierge-bell" });
}
!existing && cm.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
+
+ let more = ContextMenu.Instance.findByDescription("More...");
+ let moreItems: ContextMenuProps[] = more && "subitems" in more ? more.subitems : [];
+
if (!ClientUtils.RELEASE) {
// let copies: ContextMenuProps[] = [];
- cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ moreItems.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
// cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
}
- let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
- let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
- analyzers.push({ description: "Transcribe Speech", event: this.listen, icon: "microphone" });
- !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" });
- cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
- cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
- cm.addItem({
+ if (Cast(this.props.Document.data, ImageField)) {
+ moreItems.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" });
+ }
+ if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
+ moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
+ moreItems.push({ description: "Tag Child Images via Google Photos", event: () => GooglePhotos.Query.TagChildImages(this.props.Document), icon: "caret-square-right" });
+ moreItems.push({ description: "Write Back Link to Album", event: () => GooglePhotos.Transactions.AddTextEnrichment(this.props.Document), icon: "caret-square-right" });
+ }
+ moreItems.push({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle!
+ moreItems.push({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
+ moreItems.push({
description: "Download document", icon: "download", event: async () =>
console.log(JSON.parse(await rp.get(Utils.CorsProxy("http://localhost:8983/solr/dash/select"), {
qs: { q: 'world', fq: 'NOT baseProto_b:true AND NOT deleted:true', start: '0', rows: '100', hl: true, 'hl.fl': '*' }
@@ -473,8 +479,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// a.click();
});
- cm.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
- cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+ moreItems.push({ description: "Publish", event: () => DocUtils.Publish(this.props.Document, this.Document.title || "", this.props.addDocument, this.props.removeDocument), icon: "file" });
+ moreItems.push({ description: "Delete", event: this.deleteClicked, icon: "trash" });
+ moreItems.push({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
+ !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" });
runInAction(() => {
if (!ClientUtils.RELEASE) {
let setWriteMode = (mode: DocServer.WriteMode) => {
@@ -497,7 +505,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" });
aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" });
cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" });
- cm.addItem({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" });
}
});
runInAction(() => {
@@ -512,7 +519,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.stopPropagation();
}
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- if (!SelectionManager.IsSelected(this)) {
+ if (!SelectionManager.IsSelected(this, true)) {
SelectionManager.SelectDoc(this, false);
}
});
@@ -524,7 +531,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
getLayoutPropNum = (prop: string) => NumCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]);
- isSelected = () => SelectionManager.IsSelected(this);
+ isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction);
select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
chromeHeight = () => {
@@ -533,8 +540,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
return (showTitle ? 25 : 0) + 1;
}
+ @computed get finalLayoutKey() { return this.props.layoutKey || "layout"; }
childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling());
@computed get contents() {
+ TraceMobx();
return (<DocumentContentsView ContainingCollectionView={this.props.ContainingCollectionView}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
Document={this.props.Document}
@@ -563,12 +572,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
isSelected={this.isSelected}
select={this.select}
onClick={this.onClickHandler}
- layoutKey={this.props.layoutKey || "layout"}
+ layoutKey={this.finalLayoutKey}
DataDoc={this.props.DataDoc} />);
}
linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
- // used to decide whether a link document should be created or not.
+ // used to decide whether a link document should be created or not.
// if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
// would be good to generalize this some way.
isNonTemporalLink = (linkDoc: Doc) => {
@@ -578,16 +587,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
@computed get innards() {
+ TraceMobx();
const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined;
const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle");
const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption");
const showTextTitle = showTitle && StrCast(this.Document.layout).indexOf("FormattedTextBox") !== -1 ? showTitle : undefined;
const searchHighlight = (!this.Document.searchFields ? (null) :
- <div className="documentView-searchHighlight" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
+ <div className="documentView-searchHighlight">
{this.Document.searchFields}
</div>);
const captionView = (!showCaption ? (null) :
- <div className="documentView-captionWrapper" style={{ width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
+ <div className="documentView-captionWrapper">
<FormattedTextBox {...this.props}
onClick={this.onClickHandler} DataDoc={this.props.DataDoc} active={returnTrue}
isSelected={this.isSelected} focus={emptyFunction} select={this.select}
@@ -596,7 +606,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
</div>);
const titleView = (!showTitle ? (null) :
<div className="documentView-titleWrapper" style={{
- width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})`,
position: showTextTitle ? "relative" : "absolute",
pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all",
}}>
@@ -608,9 +617,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
/>
</div>);
return <>
- {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) =>
- <div className="documentView-docuLinkWrapper" key={`${d[Id]}`} style={{ transform: `scale(${this.layoutDoc.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
- <DocumentView {...this.props} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} />
+ {this.Document.links && DocListCast(this.Document.links).filter(d => !d.hidden).filter(this.isNonTemporalLink).map((d, i) =>
+ <div className="documentView-docuLinkWrapper" key={`${d[Id]}`}>
+ <DocumentView {...this.props} ContentScaling={returnOne} Document={d} layoutKey={this.linkEndpoint(d)} backgroundColor={returnTransparent} removeDocument={undoBatch(doc => doc.hidden = true)} />
</div>)}
{!showTitle && !showCaption ?
this.Document.searchFields ?
@@ -630,45 +639,52 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
{searchHighlight}
</div>
}
- </>
+ </>;
}
+ @computed get ignorePointerEvents() {
+ return (this.Document.isBackground && !this.isSelected()) || (this.Document.type === DocumentType.INK && InkingControl.Instance.selectedTool !== InkTool.None);
+ }
+
render() {
if (!this.props.Document) return (null);
- // trace();
- const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
const ruleColor = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleColor_" + this.Document.heading]) : undefined;
const ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
const colorSet = this.setsLayoutProp("backgroundColor");
const clusterCol = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.clusterOverridesDefaultBackground;
- const backgroundColor = this.Document.isBackground || (clusterCol && !colorSet) ?
+ const backgroundColor = (clusterCol && !colorSet) ?
this.props.backgroundColor(this.Document) || StrCast(this.layoutDoc.backgroundColor) :
ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document);
- const nativeWidth = this.layoutDoc.fitWidth ? this.props.PanelWidth() - 2 : this.nativeWidth > 0 && !this.layoutDoc.ignoreAspect ? `${this.nativeWidth}px` : "100%";
- const nativeHeight = this.layoutDoc.fitWidth ? this.props.PanelHeight() - 2 : this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
const fullDegree = Doc.isBrushedHighlightedDegree(this.props.Document);
const borderRounding = this.getLayoutPropStr("borderRounding") || ruleRounding;
- const localScale = this.props.ScreenToLocalTransform().Scale * fullDegree;
+ const localScale = fullDegree;
- let animheight = animDims ? animDims[1] : nativeHeight;
- let animwidth = animDims ? animDims[0] : nativeWidth;
+ const animDims = this.Document.animateToDimensions ? Array.from(this.Document.animateToDimensions) : undefined;
+ let animheight = animDims ? animDims[1] : "100%";
+ let animwidth = animDims ? animDims[0] : "100%";
const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"];
- const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"];
+ const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"];
let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc.viewType !== CollectionViewType.Linear;
return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont}
onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
- onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
+ onPointerEnter={e => {
+ console.log("Brush" + this.props.Document.title);
+ Doc.BrushDoc(this.props.Document);
+ }} onPointerLeave={e => {
+ console.log("UnBrush" + this.props.Document.title);
+ Doc.UnBrushDoc(this.props.Document);
+
+ }}
style={{
- transition: this.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition),
- pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all",
+ transition: this.Document.isAnimating ? ".5s linear" : StrCast(this.Document.transition),
+ pointerEvents: this.ignorePointerEvents ? "none" : "all",
color: StrCast(this.Document.color),
outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px",
border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined,
background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor,
width: animwidth,
height: animheight,
- transform: `scale(${this.layoutDoc.fitWidth ? 1 : this.props.ContentScaling()})`,
opacity: this.Document.opacity
}} >
{this.innards}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 5108954bb..c93746773 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -31,7 +31,7 @@ export interface FieldViewProps {
Document: Doc;
DataDoc?: Doc;
onClick?: ScriptField;
- isSelected: () => boolean;
+ isSelected: (outsideReaction?: boolean) => boolean;
select: (isCtrlPressed: boolean) => void;
renderDepth: number;
addDocument?: (document: Doc) => boolean;
@@ -40,7 +40,7 @@ export interface FieldViewProps {
removeDocument?: (document: Doc) => boolean;
moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
- active: () => boolean;
+ active: (outsideReaction?: boolean) => boolean;
whenActiveChanged: (isActive: boolean) => void;
focus: (doc: Doc) => void;
PanelWidth: () => number;
diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss
index 75d093fcb..905601ce3 100644
--- a/src/client/views/nodes/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox.scss
@@ -5,9 +5,9 @@
border-radius: inherit;
background: black;
border-radius: 100%;
+ transform-origin: top left;
svg {
- margin:18%;
- width:65% !important;
- height:65%;
+ width:95% !important;
+ height:95%;
}
}
diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx
index ae9273639..960b55e3e 100644
--- a/src/client/views/nodes/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox.tsx
@@ -25,7 +25,7 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
this._backgroundReaction = reaction(() => this.props.Document.backgroundColor,
() => {
if (this._ref && this._ref.current) {
- let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor!);
+ let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor);
let colsum = (col.r + col.g + col.b);
if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black");
else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white");
@@ -39,7 +39,10 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>(
let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document);
let referenceLayout = Doc.Layout(referenceDoc);
return <button className="fontIconBox-outerDiv" title={StrCast(this.props.Document.title)} ref={this._ref}
- style={{ background: StrCast(referenceLayout.backgroundColor), boxShadow: this.props.Document.unchecked ? undefined : `4px 4px 12px black` }}>
+ style={{
+ background: StrCast(referenceLayout.backgroundColor),
+ boxShadow: this.props.Document.ischecked ? `4px 4px 12px black` : undefined
+ }}>
<FontAwesomeIcon className="fontIconBox-icon" icon={this.Document.icon as any} color={this._foregroundColor} size="sm" />
</button>;
}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index a4acd3b82..77cdd3d42 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -27,6 +27,9 @@
pointer-events: all;
overflow-y: auto;
max-height: 100%;
+ display: flex;
+ flex-direction: row;
+
.formattedTextBox-dictation {
height: 20px;
width: 20px;
@@ -36,6 +39,45 @@
}
}
+.collectionfreeformview-container {
+ position: relative;
+}
+
+.formattedTextBox-outer {
+ position: relative;
+ overflow: auto;
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+}
+.formattedTextBox-sidebar-handle {
+ position: absolute;
+ top: calc(50% - 17.5px);
+ width: 10px;
+ height: 35px;
+ background: lightgray;
+ border-radius: 20px;
+}
+.formattedTextBox-cont > .formattedTextBox-sidebar-handle {
+ right: 0;
+ left: unset;
+}
+.formattedTextBox-sidebar, .formattedTextBox-sidebar-inking {
+ border-left: dashed 1px black;
+ height: 100%;
+ display: inline-block;
+ position: absolute;
+ right: 0;
+ > .formattedTextBox-sidebar-handle {
+ right:unset;
+ left:-5;
+ }
+}
+
+.formattedTextBox-sidebar-inking {
+ pointer-events: all;
+}
+
.formattedTextBox-inner-rounded {
height: 70%;
width: 85%;
@@ -45,23 +87,23 @@
left: 10%;
}
-.formattedTextBox-inner-rounded ,
-.formattedTextBox-inner {
+.formattedTextBox-inner-rounded,
+.formattedTextBox-inner {
padding: 10px 10px;
height: 100%;
}
-.menuicon {
- display: inline-block;
- border-right: 1px solid rgba(0, 0, 0, 0.2);
- color: #888;
- line-height: 1;
- padding: 0 7px;
- margin: 1px;
- cursor: pointer;
- text-align: center;
- min-width: 1.4em;
-}
+// .menuicon {
+// display: inline-block;
+// border-right: 1px solid rgba(0, 0, 0, 0.2);
+// color: #888;
+// line-height: 1;
+// padding: 0 7px;
+// margin: 1px;
+// cursor: pointer;
+// text-align: center;
+// min-width: 1.4em;
+// }
.strong,
.heading {
@@ -73,19 +115,20 @@
}
.userMarkOpen {
- background: rgba(255, 255, 0, 0.267);
+ background: rgba(255, 255, 0, 0.267);
display: inline;
}
+
.userMark {
- background: rgba(255, 255, 0, 0.267);
+ background: rgba(255, 255, 0, 0.267);
font-size: 2px;
display: inline-grid;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- width:10px;
- min-height:10px;
- text-align:center;
+ width: 10px;
+ min-height: 10px;
+ text-align: center;
align-content: center;
}
@@ -93,8 +136,9 @@ footnote {
display: inline-block;
position: relative;
cursor: pointer;
+
div {
- padding : 0 !important;
+ padding: 0 !important;
}
}
@@ -107,7 +151,7 @@ footnote::after {
.ProseMirror {
counter-reset: prosemirror-footnote;
- }
+}
.footnote-tooltip {
cursor: auto;
@@ -126,6 +170,7 @@ footnote::after {
.prosemirror-attribution {
font-size: 8px;
}
+
.footnote-tooltip::before {
border: 5px solid silver;
border-top-width: 0px;
@@ -140,45 +185,247 @@ footnote::after {
}
.formattedTextBox-summarizer {
- opacity :0.5;
+ opacity: 0.5;
position: relative;
- width:40px;
- height:20px;
+ width: 40px;
+ height: 20px;
}
-.formattedTextBox-summarizer::after{
- content: "←" ;
+
+.formattedTextBox-summarizer::after {
+ content: "←";
}
.formattedTextBox-summarizer-collapsed {
- opacity :0.5;
+ opacity: 0.5;
position: relative;
- width:40px;
- height:20px;
+ width: 40px;
+ height: 20px;
}
+
.formattedTextBox-summarizer-collapsed::after {
content: "...";
}
.ProseMirror {
-ol { counter-reset: deci1 0; padding-left: 0px; }
-.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 ; ul, ol { padding-left:30px; } }
-.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 ; ul, ol { padding-left:30px; } }
-.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 ; ul, ol { padding-left:30px; } }
-.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } }
-.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18; }
-.lower-roman-ol {counter-reset: lroman; p { display: inline }; font-size: 14; }
-.lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10; }
-.decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;}
-.decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35}
-.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35}
-.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ") "; counter-increment: deci4; display:inline-block; min-width: 40}
-.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ") "; counter-increment: deci5; display:inline-block; min-width: 40}
-.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ") "; counter-increment: deci6; display:inline-block; min-width: 45}
-.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ") "; counter-increment: deci7; display:inline-block; min-width: 50}
-.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 }
-.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 }
-.lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35}
-}
+ touch-action: none;
+
+ ol {
+ counter-reset: deci1 0;
+ padding-left: 0px;
+ }
+
+ .decimal1-ol {
+ counter-reset: deci1;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 24;
+
+ ul,
+ ol {
+ padding-left: 30px;
+ }
+ }
+
+ .decimal2-ol {
+ counter-reset: deci2;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 18;
+
+ ul,
+ ol {
+ padding-left: 30px;
+ }
+ }
+
+ .decimal3-ol {
+ counter-reset: deci3;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 14;
+
+ ul,
+ ol {
+ padding-left: 30px;
+ }
+ }
+
+ .decimal4-ol {
+ counter-reset: deci4;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 10;
+
+ ul,
+ ol {
+ padding-left: 30px;
+ }
+ }
+
+ .decimal5-ol {
+ counter-reset: deci5;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 10;
+
+ ul,
+ ol {
+ padding-left: 30px;
+ }
+ }
+
+ .decimal6-ol {
+ counter-reset: deci6;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 10;
+
+ ul,
+ ol {
+ padding-left: 30px;
+ }
+ }
+
+ .decimal7-ol {
+ counter-reset: deci7;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 10;
+
+ ul,
+ ol {
+ padding-left: 30px;
+ }
+ }
+
+ .upper-alpha-ol {
+ counter-reset: ualph;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 18;
+ }
+
+ .lower-roman-ol {
+ counter-reset: lroman;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 14;
+ }
+
+ .lower-alpha-ol {
+ counter-reset: lalpha;
+
+ p {
+ display: inline
+ }
+
+ ;
+ font-size: 10;
+ }
+
+ .decimal1:before {
+ content: counter(deci1) ") ";
+ counter-increment: deci1;
+ display: inline-block;
+ min-width: 30;
+ }
+
+ .decimal2:before {
+ content: counter(deci1) "."counter(deci2) ") ";
+ counter-increment: deci2;
+ display: inline-block;
+ min-width: 35
+ }
+
+ .decimal3:before {
+ content: counter(deci1) "."counter(deci2) "."counter(deci3) ") ";
+ counter-increment: deci3;
+ display: inline-block;
+ min-width: 35
+ }
+
+ .decimal4:before {
+ content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ") ";
+ counter-increment: deci4;
+ display: inline-block;
+ min-width: 40
+ }
+
+ .decimal5:before {
+ content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ") ";
+ counter-increment: deci5;
+ display: inline-block;
+ min-width: 40
+ }
+
+ .decimal6:before {
+ content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ") ";
+ counter-increment: deci6;
+ display: inline-block;
+ min-width: 45
+ }
+
+ .decimal7:before {
+ content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ") ";
+ counter-increment: deci7;
+ display: inline-block;
+ min-width: 50
+ }
+
+ .upper-alpha:before {
+ content: counter(deci1) "."counter(ualph, upper-alpha) ") ";
+ counter-increment: ualph;
+ display: inline-block;
+ min-width: 35
+ }
+
+ .lower-roman:before {
+ content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) ") ";
+ counter-increment: lroman;
+ display: inline-block;
+ min-width: 50
+ }
+
+ .lower-alpha:before {
+ content: counter(deci1) "."counter(ualph, upper-alpha) "."counter(lroman, lower-roman) "."counter(lalpha, lower-alpha) ") ";
+ counter-increment: lalpha;
+ display: inline-block;
+ min-width: 35
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 24d6f2509..9910c9ecd 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -17,14 +17,13 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols';
import { RichTextField } from "../../../new_fields/RichTextField";
import { RichTextUtils } from '../../../new_fields/RichTextUtils';
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types";
-import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from '../../../Utils';
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils';
import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils';
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DictationManager } from '../../util/DictationManager';
-import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from "../../util/DragManager";
import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import { inpRules } from "../../util/RichTextRules";
@@ -33,7 +32,7 @@ import { SelectionManager } from "../../util/SelectionManager";
import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DocExtendableComponent } from "../DocComponent";
+import { DocAnnotatableComponent } from "../DocComponent";
import { DocumentButtonBar } from '../DocumentButtonBar';
import { DocumentDecorations } from '../DocumentDecorations';
import { InkingControl } from "../InkingControl";
@@ -46,6 +45,9 @@ import { ContextMenu } from '../ContextMenu';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { documentSchema } from '../../../new_fields/documentSchemas';
import { AudioBox } from './AudioBox';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
+import { InkTool } from '../../../new_fields/InkField';
+import { TraceMobx } from '../../../new_fields/util';
library.add(faEdit);
library.add(faSmile, faTextHeight, faUpload);
@@ -70,11 +72,11 @@ const RichTextDocument = makeInterface(richTextSchema, documentSchema);
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@observer
-export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
+export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); }
public static blankState = () => EditorState.create(FormattedTextBox.Instance.config);
public static Instance: FormattedTextBox;
- private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
+ public static ToolTipTextMenu: TooltipTextMenu | undefined = undefined;
private _ref: React.RefObject<HTMLDivElement> = React.createRef();
private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
@@ -85,7 +87,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
private _searchReactionDisposer?: Lambda;
private _scrollToRegionReactionDisposer: Opt<IReactionDisposer>;
private _reactionDisposer: Opt<IReactionDisposer>;
- private _textReactionDisposer: Opt<IReactionDisposer>;
private _heightReactionDisposer: Opt<IReactionDisposer>;
private _rulesReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
@@ -120,7 +121,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
public static getToolTip(ev: EditorView) {
- return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev);
+ return this.ToolTipTextMenu ? this.ToolTipTextMenu : this.ToolTipTextMenu = new TooltipTextMenu(ev);
}
@undoBatch
@@ -191,9 +192,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
let tsel = this._editorView.state.selection.$from;
tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000)));
this._applyingChange = true;
- this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n"));
this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now())));
- this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n"));
this._applyingChange = false;
this.updateTitle();
this.tryUpdateHeight();
@@ -250,7 +250,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
// replace text contents whend dragging with Alt
if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") {
if (draggedDoc.data instanceof RichTextField) {
- Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data);
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text);
e.stopPropagation();
}
// apply as template when dragging with Meta
@@ -290,6 +290,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
if (context === node) return { from: offset, to: offset + node.nodeSize };
if (node.isBlock) {
+ // tslint:disable-next-line: prefer-for-of
for (let i = 0; i < (context.content as any).content.length; i++) {
let result = this.getNodeEndpoints((context.content as any).content[i], node);
if (result) {
@@ -358,8 +359,11 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
}
+ toggleSidebar = () => this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%";
+
specificContextMenu = (e: React.MouseEvent): void => {
let funcs: ContextMenuProps[] = [];
+ funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.toggleSidebar(); }, icon: "expand-arrows-alt" });
funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" });
["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option =>
funcs.push({
@@ -510,17 +514,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
() => this.tryUpdateHeight()
);
- this._textReactionDisposer = reaction(
- () => this.extensionDoc,
- () => {
- if (this.extensionDoc && (this.dataDoc.text || this.dataDoc.lastModified)) {
- this.extensionDoc.text = this.dataDoc.text;
- this.extensionDoc.lastModified = DateCast(this.dataDoc.lastModified)[Copy]();
- this.dataDoc.text = undefined;
- this.dataDoc.lastModified = undefined;
- }
- }, { fireImmediately: true });
-
this.setupEditor(this.config, this.dataDoc, this.props.fieldKey);
@@ -787,7 +780,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
let r1 = refNode && refNode.getBoundingClientRect();
let r3 = self._ref.current!.getBoundingClientRect();
- r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
+ if (r1.top < r3.top || r1.top > r3.bottom) {
+ r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
+ }
return true;
},
dispatchTransaction: this.dispatchTransaction,
@@ -834,7 +829,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
this._rulesReactionDisposer && this._rulesReactionDisposer();
this._reactionDisposer && this._reactionDisposer();
this._proxyReactionDisposer && this._proxyReactionDisposer();
- this._textReactionDisposer && this._textReactionDisposer();
this._pushReactionDisposer && this._pushReactionDisposer();
this._pullReactionDisposer && this._pullReactionDisposer();
this._heightReactionDisposer && this._heightReactionDisposer();
@@ -848,7 +842,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
if (this.props.onClick && e.button === 0) {
e.preventDefault();
}
- if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
@@ -857,10 +851,13 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
onPointerUp = (e: React.PointerEvent): void => {
- if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; }
+ if (!(e.nativeEvent as any).formattedHandled) {
+ FormattedTextBoxComment.textBox = this;
+ FormattedTextBoxComment.update(this._editorView!);
+ }
(e.nativeEvent as any).formattedHandled = true;
- if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
+ if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
e.stopPropagation();
}
}
@@ -873,7 +870,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
onPointerWheel = (e: React.WheelEvent): void => {
// if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time
- if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
+ if (this.props.isSelected(true) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
e.stopPropagation();
}
}
@@ -884,7 +881,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
onClick = (e: React.MouseEvent): void => {
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; }
(e.nativeEvent as any).formattedHandled = true;
- // if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
+ // if (e.button === 0 && ((!this.props.isSelected(true) && !e.ctrlKey) || (this.props.isSelected(true) && e.ctrlKey)) && !e.metaKey && e.target) {
// let href = (e.target as any).href;
// let location: string;
// if ((e.target as any).attributes.location) {
@@ -924,7 +921,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
// this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them.
hitBulletTargets(x: number, y: number, offsetX: number, select: boolean = false) {
clearStyleSheetRules(FormattedTextBox._bulletStyleSheet);
- if (this.props.isSelected() && offsetX < 40) {
+ if (this.props.isSelected(true) && offsetX < 40) {
let pos = this._editorView!.posAtCoords({ left: x, top: y });
if (pos && pos.pos > 0) {
let node = this._editorView!.state.doc.nodeAt(pos.pos);
@@ -968,7 +965,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
let self = FormattedTextBox;
return new Plugin({
view(newView) {
- return self._toolTipTextMenu = FormattedTextBox.getToolTip(newView);
+ return self.ToolTipTextMenu = FormattedTextBox.getToolTip(newView);
}
});
}
@@ -1003,13 +1000,16 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");
}
- if (this._recording) { this.stopDictation(true); setTimeout(() => this.recordDictation(), 250); }
+ if (this._recording) {
+ this.stopDictation(true);
+ setTimeout(() => this.recordDictation(), 250);
+ }
}
@action
tryUpdateHeight() {
- let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0;
- if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 &&
+ const scrollHeight = this._ref.current?.scrollHeight;
+ if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight &&
getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
let nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0);
let dh = NumCast(this.layoutDoc.height, 0);
@@ -1018,12 +1018,15 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
}
}
+ @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidthPercent, "0%"); }
+ @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); }
+ @computed get annotationsKey() { return "annotations"; }
render() {
- trace();
+ TraceMobx();
let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : "";
let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground;
if (this.props.isSelected()) {
- FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
+ FormattedTextBox.ToolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props);
} else if (FormattedTextBoxComment.textBox === this) {
FormattedTextBoxComment.Hide();
}
@@ -1050,8 +1053,36 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
onPointerEnter={action(() => this._entered = true)}
onPointerLeave={action(() => this._entered = false)}
>
- <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
-
+ <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }}>
+ <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: ((this.Document.isButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined }} ref={this.createDropTarget} />
+ </div>
+ {this.sidebarWidthPercent === "0%" ?
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={e => e.stopPropagation()} onClick={e => this.toggleSidebar()} /> :
+ <div className={"formattedTextBox-sidebar" + (InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : "")}
+ style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${StrCast(this.extensionDoc?.backgroundColor, "transparent")}` }}>
+ <CollectionFreeFormView {...this.props}
+ PanelHeight={() => this.props.PanelHeight()}
+ PanelWidth={() => this.sidebarWidth}
+ annotationsKey={this.annotationsKey}
+ isAnnotationOverlay={true}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ select={emptyFunction}
+ active={this.annotationsActive}
+ ContentScaling={returnOne}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}
+ CollectionView={undefined}
+ ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)}
+ ruleProvider={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ chromeCollapsed={true}>
+ </CollectionFreeFormView>
+ <div className="formattedTextBox-sidebar-handle" onPointerDown={e => e.stopPropagation()} onClick={e => this.toggleSidebar()} />
+ </div>}
<div className="formattedTextBox-dictation"
onClick={e => {
this._recording ? this.stopDictation(true) : this.recordDictation();
diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss
index 792cee182..2dd63ec21 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.scss
+++ b/src/client/views/nodes/FormattedTextBoxComment.scss
@@ -5,7 +5,6 @@
background: white;
border: 1px solid silver;
border-radius: 2px;
- padding: 2px 10px;
margin-bottom: 7px;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx
index f35ca502f..c076fd34a 100644
--- a/src/client/views/nodes/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/FormattedTextBoxComment.tsx
@@ -1,17 +1,20 @@
-import { Plugin, EditorState } from "prosemirror-state";
-import './FormattedTextBoxComment.scss';
-import { ResolvedPos, Mark } from "prosemirror-model";
+import { Mark, ResolvedPos } from "prosemirror-model";
+import { EditorState, Plugin } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
+import * as ReactDOM from 'react-dom';
import { Doc } from "../../../new_fields/Doc";
-import { schema } from "../../util/RichTextSchema";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { emptyFunction, returnEmptyString, returnFalse, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { Utils } from "../../../Utils";
-import { StrCast, Cast, FieldValue } from "../../../new_fields/Types";
-import { FormattedTextBox } from "./FormattedTextBox";
-import { DocUtils } from "../../documents/Documents";
-import { isBuffer } from "util";
import { DocumentManager } from "../../util/DocumentManager";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { schema } from "../../util/RichTextSchema";
+import { Transform } from "../../util/Transform";
+import { ContentFittingDocumentView } from "./ContentFittingDocumentView";
+import { FormattedTextBox } from "./FormattedTextBox";
+import './FormattedTextBoxComment.scss';
+import React = require("react");
+import { Docs } from "../../documents/Documents";
+import wiki from "wikijs";
export let formattedTextBoxCommentPlugin = new Plugin({
view(editorView) { return new FormattedTextBoxComment(editorView); }
@@ -50,6 +53,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark
export class FormattedTextBoxComment {
static tooltip: HTMLElement;
static tooltipText: HTMLElement;
+ static tooltipInput: HTMLInputElement;
static start: number;
static end: number;
static mark: Mark;
@@ -59,30 +63,40 @@ export class FormattedTextBoxComment {
constructor(view: any) {
if (!FormattedTextBoxComment.tooltip) {
const root = document.getElementById("root");
- let input = document.createElement("input");
- input.type = "checkbox";
+ FormattedTextBoxComment.tooltipInput = document.createElement("input");
+ FormattedTextBoxComment.tooltipInput.type = "checkbox";
FormattedTextBoxComment.tooltip = document.createElement("div");
FormattedTextBoxComment.tooltipText = document.createElement("div");
+ FormattedTextBoxComment.tooltipText.style.width = "100%";
+ FormattedTextBoxComment.tooltipText.style.height = "100%";
+ FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis";
FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText);
FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip";
FormattedTextBoxComment.tooltip.style.pointerEvents = "all";
FormattedTextBoxComment.tooltip.style.maxWidth = "350px";
- FormattedTextBoxComment.tooltip.appendChild(input);
+ FormattedTextBoxComment.tooltip.style.maxHeight = "250px";
+ FormattedTextBoxComment.tooltip.style.width = "100%";
+ FormattedTextBoxComment.tooltip.style.height = "100%";
+ FormattedTextBoxComment.tooltip.style.overflow = "hidden";
+ FormattedTextBoxComment.tooltip.style.display = "none";
+ FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput);
FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => {
let keep = e.target && (e.target as any).type === "checkbox" ? true : false;
- if (FormattedTextBoxComment.linkDoc && !keep && FormattedTextBoxComment.textBox) {
- DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, FormattedTextBoxComment.textBox.props.Document,
- (doc: Doc, maxLocation: string) => FormattedTextBoxComment.textBox!.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight"));
+ const textBox = FormattedTextBoxComment.textBox;
+ if (FormattedTextBoxComment.linkDoc && !keep && textBox) {
+ DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document,
+ (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight"));
+ } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) {
+ textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight");
}
FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened;
- FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation(
+ textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation(
FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark,
FormattedTextBoxComment.opened, keep);
e.stopPropagation();
};
root && root.appendChild(FormattedTextBoxComment.tooltip);
}
- this.update(view, undefined);
}
public static Hide() {
@@ -98,7 +112,7 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "");
}
- update(view: EditorView, lastState?: EditorState) {
+ static update(view: EditorView, lastState?: EditorState) {
let state = view.state;
// Don't do anything if the document/selection didn't change
if (lastState && lastState.doc.eq(state.doc) &&
@@ -113,6 +127,12 @@ export class FormattedTextBoxComment {
}
let set = "none";
let nbef = 0;
+ FormattedTextBoxComment.tooltipInput.style.display = "none";
+ FormattedTextBoxComment.tooltip.style.width = "";
+ FormattedTextBoxComment.tooltip.style.height = "";
+ (FormattedTextBoxComment.tooltipText as any).href = "";
+ FormattedTextBoxComment.tooltipText.style.whiteSpace = "";
+ FormattedTextBoxComment.tooltipText.style.overflow = "";
// this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date
if (state.selection.$from) {
nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark);
@@ -127,6 +147,7 @@ export class FormattedTextBoxComment {
if (mark && child && ((nbef && naft) || !noselection)) {
FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString();
set = "";
+ FormattedTextBoxComment.tooltipInput.style.display = "";
}
}
// this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links.
@@ -138,15 +159,48 @@ export class FormattedTextBoxComment {
let mark = child && findLinkMark(child.marks);
if (mark && child && nbef && naft) {
FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href;
+ if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) {
+ wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500)));
+ } else {
+ FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre";
+ FormattedTextBoxComment.tooltipText.style.overflow = "hidden";
+ }
+ (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href;
if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) {
let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0];
docTarget && DocServer.GetRefField(docTarget).then(linkDoc => {
if (linkDoc instanceof Doc) {
FormattedTextBoxComment.linkDoc = linkDoc;
- let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc));
- let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document ....
- let text = ext && StrCast(ext.text);
- ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
+ const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc));
+ try {
+ ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText);
+ } catch (e) { }
+ if (target) {
+ ReactDOM.render(<ContentFittingDocumentView
+ fitToBox={true}
+ Document={target}
+ moveDocument={returnFalse}
+ getTransform={Transform.Identity}
+ active={returnFalse}
+ setPreviewScript={returnEmptyString}
+ addDocument={returnFalse}
+ removeDocument={returnFalse}
+ ruleProvider={undefined}
+ addDocTab={returnFalse}
+ pinToPres={returnFalse}
+ dontRegisterView={true}
+ renderDepth={1}
+ PanelWidth={() => Math.min(350, NumCast(target.width, 350))}
+ PanelHeight={() => Math.min(250, NumCast(target.height, 250))}
+ focus={emptyFunction}
+ whenActiveChanged={returnFalse}
+ />, FormattedTextBoxComment.tooltipText);
+ FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%";
+ FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%";
+ }
+ // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document ....
+ // let text = ext && StrCast(ext.text);
+ // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title)));
}
});
}
@@ -168,5 +222,5 @@ export class FormattedTextBoxComment {
FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set);
}
- destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; }
+ destroy() { }
}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 57c024bbf..ba4ef8879 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -11,10 +11,12 @@
}
.imageBox-container {
+ pointer-events: all;
border-radius: inherit;
width:100%;
height:100%;
position: absolute;
+ transform-origin: top left;
}
.imageBox-cont-interactive {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 212c99f9d..f21ce3bf2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -27,6 +27,7 @@ import React = require("react");
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { documentSchema } from '../../../new_fields/documentSchemas';
import { Id } from '../../../new_fields/FieldSymbols';
+import { TraceMobx } from '../../../new_fields/util';
var requestImageSize = require('../../util/request-image-size');
var path = require('path');
const { Howl } = require('howler');
@@ -268,6 +269,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
}
@computed get content() {
+ TraceMobx();
const extensionDoc = this.extensionDoc;
if (!extensionDoc) return (null);
// let transform = this.props.ScreenToLocalTransform().inverse();
@@ -290,7 +292,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
if (field instanceof ImageField) paths = [this.choosePath(field.url)];
paths.push(...altpaths);
// }
- let interactive = InkingControl.Instance.selectedTool || this.Document.isBackground ? "" : "-interactive";
+ let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive";
let rotation = NumCast(this.Document.rotation, 0);
let aspect = (rotation % 180) ? this.Document[HeightSym]() / this.Document[WidthSym]() : 1;
let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0;
@@ -330,8 +332,10 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
</div>);
}
+ contentFunc = () => [this.content];
render() {
- return (<div className={"imageBox-container"} onContextMenu={this.specificContextMenu}>
+ return (<div className={"imageBox-container"} onContextMenu={this.specificContextMenu}
+ style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
@@ -340,7 +344,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
focus={this.props.focus}
isSelected={this.props.isSelected}
select={emptyFunction}
- active={this.active}
+ active={this.annotationsActive}
ContentScaling={returnOne}
whenActiveChanged={this.whenActiveChanged}
removeDocument={this.removeDocument}
@@ -352,7 +356,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
chromeCollapsed={true}>
- {() => [this.content]}
+ {this.contentFunc}
</CollectionFreeFormView>
</div >);
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 35e9e4862..aa6e135fe 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -95,7 +95,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected()) {
+ if (e.buttons === 1 && this.props.isSelected(true)) {
e.stopPropagation();
}
}
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 2d92c9581..46af63a7d 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -1,3 +1,11 @@
+.pdfBox-ui {
+ position: absolute;
+ width: 100%;
+ height:100%;
+ z-index: 1;
+ pointer-events: none;
+}
+
.pdfBox-cont,
.pdfBox-cont-interactive {
display: inline-block;
@@ -8,6 +16,7 @@
position:absolute;
cursor:auto;
transform-origin: top left;
+ z-index: 0;
}
.pdfBox-title-outer {
@@ -26,10 +35,10 @@
color:lightgray;
margin-top: auto;
margin-bottom: auto;
- transform-origin: 42% -18%;
+ transform-origin: 42% 15%;
width: 100%;
transform: rotate(55deg);
- font-size: 144;
+ font-size: 72;
padding: 5%;
overflow: hidden;
display: inline-block;
@@ -48,7 +57,6 @@
}
.pdfViewer-text {
.textLayer {
- will-change: transform;
span {
user-select: none;
}
@@ -60,7 +68,6 @@
pointer-events: all;
.pdfViewer-text {
.textLayer {
- will-change: transform;
span {
user-select: text;
}
@@ -135,14 +142,13 @@
.pdfBox-overlayCont {
position: absolute;
- width: 100%;
- height: 40px;
+ width: calc(100% - 40px);
+ height: 30px;
background: #121721;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
- padding: 20px;
overflow: hidden;
transition: left .5s;
pointer-events: all;
@@ -172,28 +178,24 @@
border-right: 15px solid #121721;
transition: all 0.5s;
}
+}
- .pdfBox-overlayButton-iconCont,
- .pdfBox-nextIcon,
- .pdfBox-prevIcon {
- background: #121721;
- height: 30px;
- width: 70px;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-left: -2px;
- border-radius: 3px;
- }
+.pdfBox-overlayButton-iconCont,
+.pdfBox-nextIcon,
+.pdfBox-prevIcon {
+ padding: 0;
+ background: #121721;
+ height: 30px;
+ width: 25px;
+ display: inline-block;
+ position: relative;
+ justify-content: center;
+ align-items: center;
+ margin-left: -2px;
+ border-radius: 3px;
+ pointer-events: all;
}
.pdfBox-overlayButton:hover {
background: none;
}
-
-.pdfBox-nextIcon {
- left: 20; top: 5; height: 30px; position: absolute;
-}
-.pdfBox-prevIcon {
- left: 50; top: 5; height: 30px; position: absolute;
-}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 8e0515f8a..23512543a 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -62,7 +62,7 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
this._selectReactionDisposer = reaction(() => this.props.isSelected(),
() => {
document.removeEventListener("keydown", this.onKeyDown);
- this.props.isSelected() && document.addEventListener("keydown", this.onKeyDown);
+ this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown);
}, { fireImmediately: true });
}
@@ -111,26 +111,24 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
private newValueChange = (e: React.ChangeEvent<HTMLInputElement>) => this._valueValue = e.currentTarget.value;
private newScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => this._scriptValue = e.currentTarget.value;
- whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
+ whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive));
setPdfViewer = (pdfViewer: PDFViewer) => { this._pdfViewer = pdfViewer; };
searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => this._searchString = e.currentTarget.value;
settingsPanel() {
let pageBtns = <>
<button className="pdfBox-overlayButton-iconCont" key="back" title="Page Back"
- onPointerDown={e => e.stopPropagation()} onClick={this.backPage}
- style={{ left: 50, top: 5, height: "30px", position: "absolute", pointerEvents: "all" }}>
+ onPointerDown={e => e.stopPropagation()} onClick={e => this.backPage()} style={{ left: 45, top: 5 }}>
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-left"} size="sm" />
</button>
<button className="pdfBox-overlayButton-iconCont" key="fwd" title="Page Forward"
- onPointerDown={e => e.stopPropagation()} onClick={this.forwardPage}
- style={{ left: 80, top: 5, height: "30px", position: "absolute", pointerEvents: "all" }}>
+ onPointerDown={e => e.stopPropagation()} onClick={e => this.forwardPage()} style={{ left: 45, top: 5 }}>
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-right"} size="sm" />
</button>
</>;
return !this.active() ? (null) :
(<div className="pdfBox-ui" onKeyDown={e => e.keyCode === KeyCodes.BACKSPACE || e.keyCode === KeyCodes.DELETE ? e.stopPropagation() : true}
- onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none", position: "absolute", width: "100%", height: "100%", zIndex: 1, pointerEvents: "none" }}>
+ onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}>
<div className="pdfBox-overlayCont" key="cont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}>
<button className="pdfBox-overlayButton" title="Open Search Bar" />
<input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} />
@@ -143,14 +141,14 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
<FontAwesomeIcon style={{ color: "white" }} icon={"arrow-down"} size="sm" />
</button>
</div>
- <button className="pdfBox-overlayButton" key="search" onClick={action(() => this._searching = !this._searching)} title="Open Search Bar" style={{ bottom: 8, right: 0 }}>
+ <button className="pdfBox-overlayButton" key="search" onClick={action(() => this._searching = !this._searching)} title="Open Search Bar" style={{ bottom: 0, right: 0 }}>
<div className="pdfBox-overlayButton-arrow" onPointerDown={(e) => e.stopPropagation()}></div>
<div className="pdfBox-overlayButton-iconCont" onPointerDown={(e) => e.stopPropagation()}>
<FontAwesomeIcon style={{ color: "white", padding: 5 }} icon={this._searching ? "times" : "search"} size="3x" /></div>
</button>
<input value={`${(this.Document.curPage || 1)}`}
onChange={e => this.gotoPage(Number(e.currentTarget.value))}
- style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }}
+ style={{ left: 5, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }}
onClick={action(() => this._pageControls = !this._pageControls)} />
{this._pageControls ? pageBtns : (null)}
<div className="pdfBox-settingsCont" key="settings" onPointerDown={(e) => e.stopPropagation()}>
@@ -199,33 +197,24 @@ export class PDFBox extends DocAnnotatableComponent<FieldViewProps, PdfDocument>
let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
return <div className="pdfBox-title-outer" >
<div className={classname} >
- <strong className="pdfBox-title" >{` ${this.props.Document.title}`}</strong>
+ <strong className="pdfBox-title" >{this.props.Document.title}</strong>
</div>
</div>;
}
+ isChildActive = (outsideReaction?: boolean) => this._isChildActive;
@computed get renderPdfView() {
- trace();
const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField);
- let classname = "pdfBox-cont" + (this.active() ? "-interactive" : "");
- return <div className={classname} style={{
- width: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
- height: this.props.Document.fitWidth ? `${100 / this.props.ContentScaling()}%` : undefined,
- transform: `scale(${this.props.Document.fitWidth ? this.props.ContentScaling() : 1})`
- }} onContextMenu={this.specificContextMenu} onPointerDown={e => {
- let hit = document.elementFromPoint(e.clientX, e.clientY);
- if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation
- e.button === 0 && e.stopPropagation();
- }
- }}>
+ return <div className={"pdfBox-cont"} onContextMenu={this.specificContextMenu}>
<PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded}
setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView}
renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth}
- Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
addDocTab={this.props.addDocTab} focus={this.props.focus}
pinToPres={this.props.pinToPres} addDocument={this.addDocument}
+ Document={this.props.Document} DataDoc={this.dataDoc} ContentScaling={this.props.ContentScaling}
ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select}
isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged}
+ isChildActive={this.isChildActive}
fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} />
{this.settingsPanel()}
</div>;
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 5829c1bd9..0a4c650a8 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,5 +1,6 @@
.videoBox-container {
pointer-events: all;
+ transform-origin: top left;
.inkingCanvas-paths-markers {
opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 53baea4ae..bd5bd918f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -249,7 +249,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
this._youtubeReactionDisposer && this._youtubeReactionDisposer();
this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this._playing && this.Seek(this.Document.currentTimecode || 0));
this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => {
- let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting;
+ let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected(true) && !DocumentDecorations.Instance.Interacting;
iframe.style.pointerEvents = interactive ? "all" : "none";
}, { fireImmediately: true });
};
@@ -333,8 +333,10 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
return this.addDocument(doc);
}
+ contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
render() {
- return (<div className={"videoBox-container"} onContextMenu={this.specificContextMenu}>
+ return (<div className={"videoBox-container"} onContextMenu={this.specificContextMenu}
+ style={{ transform: `scale(${this.props.ContentScaling()})`, width: `${100 / this.props.ContentScaling()}%`, height: `${100 / this.props.ContentScaling()}%` }} >
<CollectionFreeFormView {...this.props}
PanelHeight={this.props.PanelHeight}
PanelWidth={this.props.PanelWidth}
@@ -343,7 +345,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
isSelected={this.props.isSelected}
isAnnotationOverlay={true}
select={emptyFunction}
- active={this.active}
+ active={this.annotationsActive}
ContentScaling={returnOne}
whenActiveChanged={this.whenActiveChanged}
removeDocument={this.removeDocument}
@@ -355,7 +357,7 @@ export class VideoBox extends DocAnnotatableComponent<FieldViewProps, VideoDocum
renderDepth={this.props.renderDepth + 1}
ContainingCollectionDoc={this.props.ContainingCollectionDoc}
chromeCollapsed={true}>
- {() => [this.youtubeVideoId ? this.youtubeContent : this.content]}
+ {this.contentFunc}
</CollectionFreeFormView>
{this.uIButtons}
</div >);
diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss
index 44e075153..3c08ba80d 100644
--- a/src/client/views/pdf/PDFMenu.scss
+++ b/src/client/views/pdf/PDFMenu.scss
@@ -1,36 +1,6 @@
-.pdfMenu-cont {
- position: absolute;
- z-index: 10000;
- height: 35px;
- background: #323232;
- box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
- border-radius: 0px 6px 6px 6px;
- overflow: hidden;
- display: flex;
-
- .pdfMenu-button {
- background-color: transparent;
- width: 35px;
- height: 35px;
- }
-
- .pdfMenu-button:hover {
- background-color: #d4d4d4;
- }
-
- .pdfMenu-dragger {
- height: 100%;
- transition: width .2s;
- background-image: url("https://logodix.com/logo/1020374.png");
- background-size: 90% 100%;
- background-repeat: no-repeat;
- background-position: left center;
- }
-
- .pdfMenu-addTag {
- display: grid;
- width: 200px;
- padding: 5px;
- grid-template-columns: 90px 20px 90px;
- }
+.pdfMenu-addTag {
+ display: grid;
+ width: 200px;
+ padding: 5px;
+ grid-template-columns: 90px 20px 90px;
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 517a99a68..c64741769 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -3,39 +3,30 @@ import "./PDFMenu.scss";
import { observable, action, } from "mobx";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { emptyFunction, returnFalse } from "../../../Utils";
+import { unimplementedFunction, returnFalse } from "../../../Utils";
+import AntimodeMenu from "../AntimodeMenu";
import { Doc, Opt } from "../../../new_fields/Doc";
@observer
-export default class PDFMenu extends React.Component {
+export default class PDFMenu extends AntimodeMenu {
static Instance: PDFMenu;
- private _offsetY: number = 0;
- private _offsetX: number = 0;
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _commentCont = React.createRef<HTMLButtonElement>();
private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef();
- private _dragging: boolean = false;
- @observable private _top: number = -300;
- @observable private _left: number = -300;
- @observable private _opacity: number = 1;
- @observable private _transition: string = "opacity 0.5s";
- @observable private _transitionDelay: string = "";
@observable private _keyValue: string = "";
@observable private _valueValue: string = "";
@observable private _added: boolean = false;
@observable public Highlighting: boolean = false;
@observable public Status: "pdf" | "annotation" | "snippet" | "" = "";
- @observable public Pinned: boolean = false;
- public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction;
+ public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
- public Delete: () => void = emptyFunction;
- public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
+ public Delete: () => void = unimplementedFunction;
+ public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = unimplementedFunction;
public AddTag: (key: string, value: string) => boolean = returnFalse;
- public PinToPres: () => void = emptyFunction;
+ public PinToPres: () => void = unimplementedFunction;
public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
constructor(props: Readonly<{}>) {
@@ -73,86 +64,11 @@ export default class PDFMenu extends React.Component {
}
@action
- jumpTo = (x: number, y: number, forceJump: boolean = false) => {
- if (!this.Pinned || forceJump) {
- this._transition = this._transitionDelay = "";
- this._opacity = 1;
- this._left = x;
- this._top = y;
- }
- }
-
- @action
- fadeOut = (forceOut: boolean) => {
- if (!this.Pinned) {
- if (this._opacity === 0.2) {
- this._transition = "opacity 0.1s";
- this._transitionDelay = "";
- this._opacity = 0;
- this._left = this._top = -300;
- }
-
- if (forceOut) {
- this._transition = "";
- this._transitionDelay = "";
- this._opacity = 0;
- this._left = this._top = -300;
- }
- }
- }
-
- @action
- pointerLeave = (e: React.PointerEvent) => {
- if (!this.Pinned) {
- this._transition = "opacity 0.5s";
- this._transitionDelay = "1s";
- this._opacity = 0.2;
- setTimeout(() => this.fadeOut(false), 3000);
- }
- }
-
- @action
- pointerEntered = (e: React.PointerEvent) => {
- this._transition = "opacity 0.1s";
- this._transitionDelay = "";
- this._opacity = 1;
- }
-
- @action
togglePin = (e: React.MouseEvent) => {
this.Pinned = !this.Pinned;
!this.Pinned && (this.Highlighting = false);
}
- dragStart = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.addEventListener("pointermove", this.dragging);
- 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();
- }
-
- @action
- dragging = (e: PointerEvent) => {
- this._left = e.pageX - this._offsetX;
- this._top = e.pageY - this._offsetY;
-
- e.stopPropagation();
- e.preventDefault();
- }
-
- dragEnd = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
- e.stopPropagation();
- e.preventDefault();
- }
-
@action
highlightClicked = (e: React.MouseEvent) => {
if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { // yellowish highlight color for a marker type highlight
@@ -164,11 +80,6 @@ export default class PDFMenu extends React.Component {
this.Delete();
}
- handleContextMenu = (e: React.MouseEvent) => {
- e.stopPropagation();
- e.preventDefault();
- }
-
snippetStart = (e: React.PointerEvent) => {
document.removeEventListener("pointermove", this.snippetDrag);
document.addEventListener("pointermove", this.snippetDrag);
@@ -219,33 +130,27 @@ export default class PDFMenu extends React.Component {
render() {
let buttons = this.Status === "pdf" || this.Status === "snippet" ?
[
- <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
+ <button key="1" className="antimodeMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>,
- <button key="2" className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
+ <button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
<FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
- <button key="3" className="pdfMenu-button" title="Drag to Snippetize Selection" style={{ display: this.Status === "snippet" ? "" : "none" }} onPointerDown={this.snippetStart} ref={this._snippetButton}>
+ <button key="3" className="antimodeMenu-button" title="Drag to Snippetize Selection" style={{ display: this.Status === "snippet" ? "" : "none" }} onPointerDown={this.snippetStart} ref={this._snippetButton}>
<FontAwesomeIcon icon="cut" size="lg" /></button>,
- <button key="4" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+ <button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button>
] : [
- <button key="5" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
+ <button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
<FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
- <button key="6" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}>
+ <button key="6" className="antimodeMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}>
<FontAwesomeIcon icon="map-pin" size="lg" /></button>,
<div key="7" className="pdfMenu-addTag" >
<input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
<input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
</div>,
- <button key="8" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
+ <button key="8" className="antimodeMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
<FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
];
- return (
- <div className="pdfMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
- style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
- {buttons}
- <div className="pdfMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
- </div >
- );
+ return this.getElement(buttons);
}
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss
index f6fedf3da..ac018aa0e 100644
--- a/src/client/views/pdf/PDFViewer.scss
+++ b/src/client/views/pdf/PDFViewer.scss
@@ -1,11 +1,12 @@
.pdfViewer-viewer, .pdfViewer-viewer-zoomed {
- pointer-events: inherit;
+ pointer-events: all;
width: 100%;
height: 100%;
position: absolute;
overflow-y: auto;
overflow-x: hidden;
+ transform-origin: top left;
// .canvasWrapper {
// transform: scale(0.75);
@@ -13,8 +14,7 @@
// }
.textLayer {
- mix-blend-mode: multiply;
- opacity: 0.9;
+ mix-blend-mode: multiply;// bcz: makes text fuzzy!
span {
padding-right: 5px;
padding-bottom: 4px;
@@ -35,26 +35,46 @@
pointer-events: none;
}
+ .pdfViewer-text-selected {
+ .textLayer{
+ pointer-events: all;
+ user-select: text;
+ }
+ }
+ .pdfViewer-text {
+ transform-origin: top left;
+ .textLayer {
+ will-change: transform;
+ }
+ }
+
.pdfViewer-dragAnnotationBox {
position:absolute;
background-color: transparent;
opacity: 0.1;
}
- .pdfViewer-overlay {
+ .pdfViewer-overlay, .pdfViewer-overlay-inking {
transform-origin: left top;
position: absolute;
top: 0px;
left: 0px;
display: inline-block;
width:100%;
+ pointer-events: none;
+ }
+ .pdfViewer-overlay-inking {
+ .collectionfreeformview-container {
+ pointer-events: all;
+ }
}
.pdfViewer-annotationLayer {
position: absolute;
+ transform-origin: left top;
top: 0;
width: 100%;
pointer-events: none;
- mix-blend-mode: multiply;
+ mix-blend-mode: multiply; // bcz: makes text fuzzy!
.pdfViewer-annotationBox {
position: absolute;
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0cb671156..f1c500391 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -26,6 +26,10 @@ import { undoBatch } from "../../util/UndoManager";
import { DocAnnotatableComponent } from "../DocComponent";
import { DocumentType } from "../../documents/DocumentTypes";
import { documentSchema } from "../../../new_fields/documentSchemas";
+import { DocumentDecorations } from "../DocumentDecorations";
+import { InkingControl } from "../InkingControl";
+import { InkTool } from "../../../new_fields/InkField";
+import { TraceMobx } from "../../../new_fields/util";
const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer");
const pdfjsLib = require("pdfjs-dist");
@@ -56,9 +60,10 @@ interface IViewerProps {
startupLive: boolean;
renderDepth: number;
focus: (doc: Doc) => void;
- isSelected: () => boolean;
+ isSelected: (outsideReaction?: boolean) => boolean;
loaded: (nw: number, nh: number, np: number) => void;
- active: () => boolean;
+ active: (outsideReaction?: boolean) => boolean;
+ isChildActive: (outsideReaction?: boolean) => boolean;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean;
pinToPres: (document: Doc) => void;
addDocument?: (doc: Doc) => boolean;
@@ -117,6 +122,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
_lastSearch: string = "";
componentDidMount = async () => {
+ !this.props.Document.lockedTransform && (this.props.Document.lockedTransform = true);
// change the address to be the file address of the PNG version of each page
// file address of the pdf
this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${(this.Document.curPage || 1)}.PNG`)));
@@ -161,7 +167,7 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
}
copy = (e: ClipboardEvent) => {
- if (this.props.active() && e.clipboardData) {
+ if (this.props.active(true) && e.clipboardData) {
let annoDoc = this.makeAnnotationDocument("rgba(3,144,152,0.3)"); // copied text markup color (blueish)
if (annoDoc) {
e.clipboardData.setData("text/plain", this._selectionText);
@@ -391,16 +397,20 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
@action
onPointerDown = (e: React.PointerEvent): void => {
+ let hit = document.elementFromPoint(e.clientX, e.clientY);
+ if (hit && hit.localName === "span" && this.props.isSelected(true)) { // drag selecting text stops propagation
+ e.button === 0 && e.stopPropagation();
+ }
// if alt+left click, drag and annotate
this._downX = e.clientX;
this._downY = e.clientY;
addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" });
if ((this.Document.scale || 1) !== 1) return;
- if ((e.button !== 0 || e.altKey) && this.active()) {
+ if ((e.button !== 0 || e.altKey) && this.active(true)) {
this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true);
}
this._marqueeing = false;
- if (!e.altKey && e.button === 0 && this.active()) {
+ if (!e.altKey && e.button === 0 && this.active(true)) {
// clear out old marquees and initialize menu for new selection
PDFMenu.Instance.StartDrag = this.startDrag;
PDFMenu.Instance.Highlight = this.highlight;
@@ -461,10 +471,10 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
let annoBox = document.createElement("div");
annoBox.className = "pdfViewer-annotationBox";
// transforms the positions from screen onto the pdf div
- annoBox.style.top = ((rect.top - boundingRect.top) * scaleY + this._mainCont.current.scrollTop).toString();
- annoBox.style.left = ((rect.left - boundingRect.left) * scaleX).toString();
- annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width).toString();
- annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height).toString();
+ annoBox.style.top = ((rect.top - boundingRect.top) * scaleY / this._zoomed + this._mainCont.current.scrollTop).toString();
+ annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString();
+ annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString();
+ annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString();
this.createAnnotation(annoBox, this.getPageFromScroll(rect.top));
}
}
@@ -605,45 +615,50 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
e.stopPropagation();
if (e.ctrlKey) {
let curScale = Number(this._pdfViewer.currentScaleValue);
- this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000));
+ this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - curScale * e.deltaY / 1000));
this._zoomed = Number(this._pdfViewer.currentScaleValue);
}
}
@computed get annotationLayer() {
- trace();
- return <div className="pdfViewer-annotationLayer" style={{ height: (this.Document.nativeHeight || 0) }} ref={this._annotationLayer}>
+ TraceMobx();
+ return <div className="pdfViewer-annotationLayer" style={{ height: (this.Document.nativeHeight || 0), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}>
{this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) =>
<Annotation {...this.props} focus={this.props.focus} extensionDoc={this.extensionDoc!} anno={anno} key={`${anno[Id]}-annotation`} />)}
- <div className="pdfViewer-overlay" id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
- <CollectionFreeFormView {...this.props}
- annotationsKey={this.annotationsKey}
- setPreviewCursor={this.setPreviewCursor}
- PanelHeight={() => (this.Document.scrollHeight || this.Document.nativeHeight || 0)}
- PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0)}
- VisibleHeight={this.visibleHeight}
- focus={this.props.focus}
- isSelected={this.props.isSelected}
- isAnnotationOverlay={true}
- select={emptyFunction}
- active={this.active}
- ContentScaling={returnOne}
- whenActiveChanged={this.whenActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocument}
- CollectionView={undefined}
- ScreenToLocalTransform={this.scrollXf}
- ruleProvider={undefined}
- renderDepth={this.props.renderDepth + 1}
- ContainingCollectionDoc={this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document}
- chromeCollapsed={true}>
- </CollectionFreeFormView>
- </div>
+ </div>;
+ }
+ overlayTransform = () => this.scrollXf().scale(1 / this._zoomed);
+ panelWidth = () => (this.Document.scrollHeight || this.Document.nativeHeight || 0);
+ panelHeight = () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0);
+ @computed get overlayLayer() {
+ return <div className={`pdfViewer-overlay${InkingControl.Instance.selectedTool !== InkTool.None ? "-inking" : ""}`} id="overlay" style={{ transform: `scale(${this._zoomed})` }}>
+ <CollectionFreeFormView {...this.props}
+ annotationsKey={this.annotationsKey}
+ setPreviewCursor={this.setPreviewCursor}
+ PanelHeight={this.panelWidth}
+ PanelWidth={this.panelHeight}
+ VisibleHeight={this.visibleHeight}
+ focus={this.props.focus}
+ isSelected={this.props.isSelected}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ active={this.annotationsActive}
+ ContentScaling={this.contentZoom}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocument}
+ CollectionView={undefined}
+ ScreenToLocalTransform={this.overlayTransform}
+ ruleProvider={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ContainingCollectionDoc={this.props.ContainingCollectionView?.props.Document}
+ chromeCollapsed={true}>
+ </CollectionFreeFormView>
</div>;
}
@computed get pdfViewerDiv() {
- return <div className="pdfViewer-text" ref={this._viewer} style={{ transformOrigin: "left top" }} />;
+ return <div className={"pdfViewer-text" + ((!DocumentDecorations.Instance.Interacting && (this.props.isSelected() || this.props.isChildActive())) ? "-selected" : "")} ref={this._viewer} />;
}
@computed get standinViews() {
return <>
@@ -657,11 +672,19 @@ export class PDFViewer extends DocAnnotatableComponent<IViewerProps, PdfDocument
marqueeY = () => this._marqueeY;
marqueeing = () => this._marqueeing;
visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96;
+ contentZoom = () => this._zoomed;
+ @computed get contentScaling() { return this.props.ContentScaling(); }
render() {
- trace();
+ TraceMobx();
return !this.extensionDoc ? (null) :
- <div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} ref={this._mainCont}>
+ <div className={"pdfViewer-viewer" + (this._zoomed !== 1 ? "-zoomed" : "")} ref={this._mainCont}
+ style={{
+ width: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeWidth) : `${100 / this.contentScaling}%`,
+ height: !this.props.Document.fitWidth ? NumCast(this.props.Document.nativeHeight) : `${100 / this.contentScaling}%`,
+ transform: `scale(${this.contentScaling})`
+ }} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick}>
{this.pdfViewerDiv}
+ {this.overlayLayer}
{this.annotationLayer}
{this.standinViews}
<PdfViewerMarquee isMarqueeing={this.marqueeing} width={this.marqueeWidth} height={this.marqueeHeight} x={this.marqueeX} y={this.marqueeY} />
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index 17b2094ec..f50a3a0ef 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -172,7 +172,6 @@ export class PresElementBox extends DocComponent<FieldViewProps, PresDocument>(P
<ContentFittingDocumentView
fitToBox={StrCast(this.targetDoc.type).indexOf(DocumentType.COL) !== -1}
Document={this.targetDoc}
- fieldKey={this.props.fieldKey}
addDocument={returnFalse}
removeDocument={returnFalse}
ruleProvider={undefined}
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index b841190d4..62f3aba4c 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -33,7 +33,7 @@ export enum Keys {
export class FilterBox extends React.Component {
static Instance: FilterBox;
- public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
+ public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB, DocumentType.TEMPLATE];
//if true, any keywords can be used. if false, all keywords are required.
//this also serves as an indicator if the word status filter is applied
@@ -82,7 +82,7 @@ export class FilterBox extends React.Component {
var panel = this.nextElementSibling as HTMLElement;
if (panel.style.maxHeight) {
panel.style.overflow = "hidden";
- panel.style.maxHeight = null;
+ panel.style.maxHeight = "";
panel.style.opacity = "0";
} else {
setTimeout(() => {
@@ -114,7 +114,7 @@ export class FilterBox extends React.Component {
acc[i].classList.toggle("active");
var panel = acc[i].nextElementSibling as HTMLElement;
panel.style.overflow = "hidden";
- panel.style.maxHeight = null;
+ panel.style.maxHeight = "";
}
}
});
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 3bf1129b5..271b7cfd3 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -15,6 +15,7 @@ import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstruct
import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util";
import { intersectRect } from "../Utils";
import { UndoManager } from "../client/util/UndoManager";
+import { computedFn } from "mobx-utils";
export namespace Field {
export function toKeyValueString(doc: Doc, key: string): string {
@@ -473,8 +474,9 @@ export namespace Doc {
export function CreateDocumentExtensionForField(doc: Doc, fieldKey: string) {
let docExtensionForField = new Doc(doc[Id] + fieldKey, true);
- docExtensionForField.title = fieldKey + ".ext";
+ docExtensionForField.title = fieldKey + ".ext"; // courtesy field--- shouldn't be needed except maybe for debugging
docExtensionForField.extendsDoc = doc; // this is used by search to map field matches on the extension doc back to the document it extends.
+ docExtensionForField.extendsField = fieldKey; // this can be used by search to map matches on the extension doc back to the field that was extended.
docExtensionForField.type = DocumentType.EXTENSION;
let proto: Doc | undefined = doc;
while (proto && !Doc.IsPrototype(proto) && proto.proto) {
@@ -568,7 +570,7 @@ export namespace Doc {
let layoutCustomLayout = Doc.MakeDelegate(templateDoc);
titleTarget && (Doc.GetProto(target).title = titleTarget);
- target.type = DocumentType.TEMPLATE;
+ Doc.GetProto(target).type = DocumentType.TEMPLATE;
target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy]();
Doc.GetProto(target)[targetKey] = layoutCustomLayout;
@@ -636,13 +638,12 @@ export namespace Doc {
}
export class DocBrush {
- @observable BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
+ BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
}
const brushManager = new DocBrush();
export class DocData {
@observable _user_doc: Doc = undefined!;
- @observable BrushedDoc: ObservableMap<Doc, boolean> = new ObservableMap();
}
// the document containing the view layout information - will be the Document itself unless the Document has
@@ -653,10 +654,18 @@ export namespace Doc {
export function UserDoc(): Doc { return manager._user_doc; }
export function SetUserDoc(doc: Doc) { manager._user_doc = doc; }
export function IsBrushed(doc: Doc) {
- return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc));
+ return computedFn(function IsBrushed(doc: Doc) {
+ return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetDataDoc(doc));
+ })(doc);
+ }
+ // don't bother memoizing (caching) the result if called from a non-reactive context. (plus this avoids a warning message)
+ export function IsBrushedDegreeUnmemoized(doc: Doc) {
+ return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0;
}
export function IsBrushedDegree(doc: Doc) {
- return brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 2 : brushManager.BrushedDoc.has(doc) ? 1 : 0;
+ return computedFn(function IsBrushDegree(doc: Doc) {
+ return brushManager.BrushedDoc.has(doc) ? 2 : brushManager.BrushedDoc.has(Doc.GetDataDoc(doc)) ? 1 : 0;
+ })(doc);
}
export function BrushDoc(doc: Doc) {
brushManager.BrushedDoc.set(doc, true);
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index d94834e91..2d8bb582a 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -12,16 +12,12 @@ export enum InkTool {
Scrubber
}
-export interface StrokeData {
- pathData: Array<{ x: number, y: number }>;
- color: string;
- width: string;
- tool: InkTool;
- displayTimecode: number;
- creationTime: number;
+export interface PointData {
+ x: number;
+ y: number;
}
-export type InkData = Map<string, StrokeData>;
+export type InkData = Array<PointData>;
const pointSchema = createSimpleSchema({
x: true, y: true
@@ -34,16 +30,16 @@ const strokeDataSchema = createSimpleSchema({
@Deserializable("ink")
export class InkField extends ObjectField {
- @serializable(map(object(strokeDataSchema)))
+ @serializable(list(object(strokeDataSchema)))
readonly inkData: InkData;
- constructor(data?: InkData) {
+ constructor(data: InkData) {
super();
- this.inkData = data || new Map;
+ this.inkData = data;
}
[Copy]() {
- return new InkField(DeepCopy(this.inkData));
+ return new InkField(this.inkData);
}
[ToScriptString]() {
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index d2f76c969..fd5459876 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -10,17 +10,21 @@ export class RichTextField extends ObjectField {
@serializable(true)
readonly Data: string;
- constructor(data: string) {
+ @serializable(true)
+ readonly Text: string;
+
+ constructor(data: string, text: string = "") {
super();
this.Data = data;
+ this.Text = text;
}
[Copy]() {
- return new RichTextField(this.Data);
+ return new RichTextField(this.Data, this.Text);
}
[ToScriptString]() {
- return `new RichTextField("${this.Data}")`;
+ return `new RichTextField("${this.Data}", "${this.Text}")`;
}
} \ No newline at end of file
diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts
index 601939ed2..c2cca859c 100644
--- a/src/new_fields/RichTextUtils.ts
+++ b/src/new_fields/RichTextUtils.ts
@@ -52,7 +52,7 @@ export namespace RichTextUtils {
};
export const Synthesize = (plainText: string, oldState?: RichTextField) => {
- return new RichTextField(ToProsemirrorState(plainText, oldState));
+ return new RichTextField(ToProsemirrorState(plainText, oldState), plainText);
};
export const ToPlainText = (state: EditorState) => {
diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts
index 92d0aec9a..42a8485ac 100644
--- a/src/new_fields/SchemaHeaderField.ts
+++ b/src/new_fields/SchemaHeaderField.ts
@@ -41,6 +41,17 @@ export const PastelSchemaPalette = new Map<string, string>([
export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)];
+export const DarkPastelSchemaPalette = new Map<string, string>([
+ ["pink2", "#c932b0"],
+ ["purple4", "#913ad6"],
+ ["bluegreen1", "#3978ed"],
+ ["bluegreen7", "#2adb3e"],
+ ["bluegreen5", "#21b0eb"],
+ ["yellow4", "#edcc0c"],
+ ["red2", "#eb3636"],
+ ["orange1", "#f2740f"],
+]);
+
@scriptingGlobal
@Deserializable("schemaheader")
export class SchemaHeaderField extends ObjectField {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index e2730914f..4c2f061a6 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -31,7 +31,8 @@ export const documentSchema = createSchema({
summarizedDocs: listSpec(Doc), // documents that are summarized by this document (and which will typically be opened by clicking this document)
maximizedDocs: listSpec(Doc), // documents to maximize when clicking this document (generally this document will be an icon)
maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab)
- lockedPosition: "boolean", // whether the document can be spatially manipulated
+ lockedPosition: "boolean", // whether the document can be moved (dragged)
+ lockedTransform: "boolean", // whether the document can be panned/zoomed
inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently
borderRounding: "string", // border radius rounding of document
searchFields: "string", // the search fields to display when this document matches a search in its metadata
@@ -40,9 +41,11 @@ export const documentSchema = createSchema({
showTitle: "string", // whether an editable title banner is displayed at tht top of the document
isButton: "boolean", // whether document functions as a button (overiding native interactions of its content)
ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events)
- isAnimating: "boolean", // whether the document is in the midst of animating between two layouts (used by icons to de/iconify documents).
+ isAnimating: "string", // whether the document is in the midst of animating between two layouts (used by icons to de/iconify documents). value is undefined|"min"|"max"
animateToDimensions: listSpec("number"), // layout information about the target rectangle a document is animating towards
scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
+ strokeWidth: "number",
+ fontSize: "string"
});
export const positionSchema = createSchema({
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 04194509c..4147be278 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -4,7 +4,7 @@ import { SerializationHelper } from "../client/util/SerializationHelper";
import { ProxyField } from "./Proxy";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
-import { action } from "mobx";
+import { action, trace } from "mobx";
import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
import { DocServer } from "../client/DocServer";
@@ -12,6 +12,10 @@ function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
}
+export function TraceMobx() {
+ //trace();
+}
+
export interface GetterResult {
value: FieldResult;
shouldReturn?: boolean;
diff --git a/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud
deleted file mode 100644
index f71886d8c..000000000
--- a/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud
+++ /dev/null
Binary files differ
diff --git a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud
deleted file mode 100644
index 30ddb3091..000000000
--- a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud
+++ /dev/null
Binary files differ
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 833e44bf6..5b9bba47d 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -46,7 +46,7 @@ export class CurrentUserUtils {
let notes = CurrentUserUtils.setupNoteTypes(doc);
doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 });
doc.activePen = doc;
- let docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
+ let docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
{ title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' },
{ title: "todo item", icon: "check", ignoreClick: true, drag: 'getCopy(this.dragFactory, true)', dragFactory: notes[notes.length - 1] },
{ title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' },
@@ -55,16 +55,16 @@ export class CurrentUserUtils {
{ title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' },
{ title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })' },
{ title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' },
- { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
- { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc },
- { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
- { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
- { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this) && this.activePen.pen !== undefined`, backgroundColor: "white", activePen: doc },
+ { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+ { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+ { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc },
+ { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc },
+ { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc },
];
return docProtoData.map(data => Docs.Create.FontIconDocument({
nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen,
+ ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen,
backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
}));
}
@@ -82,7 +82,7 @@ export class CurrentUserUtils {
});
return Docs.Create.ButtonDocument({
- width: 35, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Tools", targetContainer: sidebarContainer,
+ width: 35, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Tools", fontSize: 10, targetContainer: sidebarContainer,
sourcePanel: Docs.Create.StackingDocument([dragCreators, color], {
width: 500, height: 800, lockedPosition: true, chromeStatus: "disabled", title: "tools stack"
}),
@@ -107,19 +107,19 @@ export class CurrentUserUtils {
});
return Docs.Create.ButtonDocument({
- width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library",
+ width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Library", fontSize: 10,
sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], {
title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true
}),
targetContainer: sidebarContainer,
- onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel")
+ onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
});
}
// setup the Search button which will display the search panel.
static setupSearchPanel(sidebarContainer: Doc) {
return Docs.Create.ButtonDocument({
- width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Search",
+ width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Search", fontSize: 10,
sourcePanel: Docs.Create.QueryDocument({
title: "search stack", ignoreClick: true
}),
@@ -185,6 +185,8 @@ export class CurrentUserUtils {
(doc.curPresentation === undefined) && CurrentUserUtils.setupDefaultPresentation(doc);
(doc.sidebarButtons === undefined) && CurrentUserUtils.setupSidebarButtons(doc);
+ // this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready
+ PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => recent && PromiseValue(recent.data).then(DocListCast));
// this is equivalent to using PrefetchProxies to make sure all the sidebarButtons and noteType internal Doc's have been retrieved.
PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast));
PromiseValue(Cast(doc.sidebarButtons, Doc)).then(stackingDoc => {
diff --git a/src/server/index.ts b/src/server/index.ts
index d5dbe8913..d96bd4d9a 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1173,6 +1173,7 @@ const suffixMap: { [type: string]: (string | [string, string | ((json: any) => a
"pdf": ["_t", "url"],
"audio": ["_t", "url"],
"web": ["_t", "url"],
+ "RichTextField": ["_t", value => value.Text],
"date": ["_d", value => new Date(value.date).toISOString()],
"proxy": ["_i", "fieldId"],
"list": ["_l", list => {