aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json13
-rw-r--r--package.json1
-rw-r--r--src/client/documents/Documents.ts6
-rw-r--r--src/client/util/InteractionUtils.ts10
-rw-r--r--src/client/views/CollectionLinearView.tsx4
-rw-r--r--src/client/views/GestureOverlay.scss8
-rw-r--r--src/client/views/GestureOverlay.tsx228
-rw-r--r--src/client/views/InkSelectDecorations.scss5
-rw-r--r--src/client/views/InkSelectDecorations.tsx55
-rw-r--r--src/client/views/InkingControl.tsx22
-rw-r--r--src/client/views/InkingStroke.tsx6
-rw-r--r--src/client/views/MainView.scss7
-rw-r--r--src/client/views/MainView.tsx11
-rw-r--r--src/client/views/Palette.scss20
-rw-r--r--src/client/views/Palette.tsx63
-rw-r--r--src/client/views/Touchable.tsx29
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss14
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx6
-rw-r--r--src/client/views/collections/CollectionSubView.tsx13
-rw-r--r--src/client/views/collections/CollectionTreeView.scss6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx225
-rw-r--r--src/client/views/nodes/DocumentView.tsx318
-rw-r--r--src/mobile/ImageUpload.tsx31
-rw-r--r--src/mobile/MobileInterface.tsx69
-rw-r--r--src/new_fields/InkField.ts3
-rw-r--r--src/new_fields/documentSchemas.ts2
-rw-r--r--src/pen-gestures/GestureUtils.ts29
-rw-r--r--src/server/authentication/models/current_user_utils.ts49
28 files changed, 878 insertions, 375 deletions
diff --git a/package-lock.json b/package-lock.json
index 44a40483b..da2648d9a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2040,6 +2040,11 @@
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
+ "avl": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/avl/-/avl-1.4.4.tgz",
+ "integrity": "sha512-vMjF4jhT2N0naO1fsBTA/lZ2AB70ECBv41na7oBe8B5PCgMVvNrWG+VRKMlbG5Mk5UGbMxzUD9fXK/IBsmmO5A=="
+ },
"awesome-typescript-loader": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.2.1.tgz",
@@ -2314,6 +2319,14 @@
"tweetnacl": "^0.14.3"
}
},
+ "bentley-ottman-sweepline": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/bentley-ottman-sweepline/-/bentley-ottman-sweepline-0.1.4.tgz",
+ "integrity": "sha512-Qptw9uoNLM0zLiJfA+9PqFrF5b6pMiJNyoWgqONq4/wVml4NC2N8S4RXYXn4xXR4JEBhHF+euVwwML7D9X1LqA==",
+ "requires": {
+ "avl": "^1.4.0"
+ }
+ },
"better-assert": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
diff --git a/package.json b/package.json
index ab273f352..f2ff96592 100644
--- a/package.json
+++ b/package.json
@@ -121,6 +121,7 @@
"async": "^2.6.2",
"babel-runtime": "^6.26.0",
"bcrypt-nodejs": "0.0.3",
+ "bentley-ottman-sweepline": "^0.1.4",
"bluebird": "^3.5.3",
"body-parser": "^1.18.3",
"bootstrap": "^4.3.1",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e149963b9..64abd4f57 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -29,7 +29,7 @@ import { listSpec } from "../../new_fields/Schema";
import { DocServer } from "../DocServer";
import { dropActionType } from "../util/DragManager";
import { DateField } from "../../new_fields/DateField";
-import { UndoManager } from "../util/UndoManager";
+import { UndoManager, undoBatch } from "../util/UndoManager";
import { YoutubeBox } from "../apis/youtube/YoutubeBox";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { LinkManager } from "../util/LinkManager";
@@ -103,6 +103,8 @@ export interface DocumentOptions {
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;
+ onPointerDown?: ScriptField;
+ onPointerUp?: ScriptField;
dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
icon?: string;
@@ -116,6 +118,7 @@ export interface DocumentOptions {
color?: string;
limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
// [key: string]: Opt<Field>;
+ pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
}
class EmptyBox {
@@ -730,6 +733,7 @@ export namespace DocUtils {
});
}
+ @undoBatch
export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) {
const sv = DocumentManager.Instance.getDocumentView(source.doc);
if (sv && sv.props.ContainingCollectionDoc === target.doc) return;
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
index 2e4e8c7ca..76b43da3c 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.ts
@@ -8,12 +8,14 @@ export namespace InteractionUtils {
const REACT_POINTER_PEN_BUTTON = 0;
const ERASER_BUTTON = 5;
- export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>): React.Touch[] {
+ export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
const myTouches = new Array<React.Touch>();
for (let i = 0; i < e.targetTouches.length; i++) {
- const pt = e.targetTouches.item(i);
+ const pt: any = e.targetTouches.item(i);
if (pt && prevPoints.has(pt.identifier)) {
- myTouches.push(pt);
+ if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
+ myTouches.push(pt);
+ }
}
}
return myTouches;
@@ -23,7 +25,7 @@ export namespace InteractionUtils {
switch (type) {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
case PENTYPE:
- return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? POINTER_PEN_BUTTON : REACT_POINTER_PEN_BUTTON);
+ return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
case ERASERTYPE:
return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
default:
diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx
index 5ca861f71..5d6a58656 100644
--- a/src/client/views/CollectionLinearView.tsx
+++ b/src/client/views/CollectionLinearView.tsx
@@ -36,7 +36,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
{ fireImmediately: true }
);
}
- protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this._dropDisposer && this._dropDisposer();
if (ele) {
this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
@@ -55,7 +55,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
render() {
const guid = Utils.GenerateGuid();
return <div className="collectionLinearView-outer">
- <div className="collectionLinearView" ref={this.createDropTarget} >
+ <div className="collectionLinearView" ref={this.createDropAndGestureTarget} >
<input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
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>
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
new file mode 100644
index 000000000..31601efd4
--- /dev/null
+++ b/src/client/views/GestureOverlay.scss
@@ -0,0 +1,8 @@
+.gestureOverlay-cont {
+ width: 100vw;
+ height: 100vh;
+ position: absolute;
+ top: 0;
+ left: 0;
+ touch-action: none;
+} \ No newline at end of file
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
new file mode 100644
index 000000000..830c06f1f
--- /dev/null
+++ b/src/client/views/GestureOverlay.tsx
@@ -0,0 +1,228 @@
+import React = require("react");
+import { Touchable } from "./Touchable";
+import { observer } from "mobx-react";
+import "./GestureOverlay.scss"
+import { computed, observable, action, runInAction } from "mobx";
+import { CreatePolyline } from "./InkingStroke";
+import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { InteractionUtils } from "../util/InteractionUtils";
+import { InkingControl } from "./InkingControl";
+import { InkTool } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { LinkManager } from "../util/LinkManager";
+import { DocUtils } from "../documents/Documents";
+import { undoBatch } from "../util/UndoManager";
+import { Scripting } from "../util/Scripting";
+import { FieldValue, Cast } from "../../new_fields/Types";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import Palette from "./Palette";
+
+@observer
+export default class GestureOverlay extends Touchable {
+ static Instance: GestureOverlay;
+
+ @observable private _points: { X: number, Y: number }[] = [];
+ @observable private _palette?: JSX.Element;
+ @observable public Color: string = "rgb(244, 67, 54)";
+ @observable public Width: number = 5;
+
+ private _d1: Doc | undefined;
+ private thumbIdentifier?: number;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ GestureOverlay.Instance = this;
+ }
+
+ @action
+ handleHandDown = (e: React.TouchEvent) => {
+ const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+ const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+ this.thumbIdentifier = thumb?.identifier;
+ const others = fingers.filter(f => f !== thumb);
+ const minX = Math.min(...others.map(f => f.clientX));
+ const minY = Math.min(...others.map(f => f.clientY));
+ // const t = this.getTransform().transformPoint(minX, minY);
+ // const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
+
+ const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+ if (thumbDoc) {
+ this._palette = <Palette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
+ }
+
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchmove", this.handleHandMove);
+ document.addEventListener("touchmove", this.handleHandMove);
+ document.removeEventListener("touchend", this.handleHandUp);
+ document.addEventListener("touchend", this.handleHandUp);
+ }
+
+ @action
+ handleHandMove = (e: TouchEvent) => {
+ for (let i = 0; i < e.changedTouches.length; i++) {
+ const pt = e.changedTouches.item(i);
+ if (pt?.identifier === this.thumbIdentifier) {
+ }
+ }
+ }
+
+ @action
+ handleHandUp = (e: TouchEvent) => {
+ this.onTouchEnd(e);
+ if (this.prevPoints.size < 3) {
+ this._palette = undefined;
+ document.removeEventListener("touchend", this.handleHandUp);
+ }
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent) => {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ this._points.push({ X: e.clientX, Y: e.clientY });
+ e.stopPropagation();
+ e.preventDefault();
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
+
+ @action
+ onPointerMove = (e: PointerEvent) => {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ this._points.push({ X: e.clientX, Y: e.clientY });
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ handleLineGesture = (): boolean => {
+ let actionPerformed = false;
+ const B = this.svgBounds;
+ const ep1 = this._points[0];
+ const ep2 = this._points[this._points.length - 1];
+
+ const target1 = document.elementFromPoint(ep1.X, ep1.Y);
+ const target2 = document.elementFromPoint(ep2.X, ep2.Y);
+ const callback = (doc: Doc) => {
+ if (!this._d1) {
+ this._d1 = doc;
+ }
+ else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) {
+ DocUtils.MakeLink({ doc: this._d1 }, { doc: doc });
+ actionPerformed = true;
+ }
+ }
+ const ge = new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+ {
+ bubbles: true,
+ detail: {
+ points: this._points,
+ gesture: GestureUtils.Gestures.Line,
+ bounds: B,
+ callbackFn: callback
+ }
+ });
+ target1?.dispatchEvent(ge);
+ target2?.dispatchEvent(ge);
+ return actionPerformed;
+ }
+
+
+
+ @action
+ onPointerUp = (e: PointerEvent) => {
+ if (this._points.length > 1) {
+ const B = this.svgBounds;
+ const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
+
+ const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
+ let actionPerformed = false;
+ if (result && result.Score > 0.7) {
+ switch (result.Name) {
+ case GestureUtils.Gestures.Box:
+ const target = document.elementFromPoint(this._points[0].X, this._points[0].Y);
+ target?.dispatchEvent(new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+ {
+ bubbles: true,
+ detail: {
+ points: this._points,
+ gesture: GestureUtils.Gestures.Box,
+ bounds: B
+ }
+ }));
+ actionPerformed = true;
+ break;
+ case GestureUtils.Gestures.Line:
+ actionPerformed = this.handleLineGesture();
+ break;
+ case GestureUtils.Gestures.Scribble:
+ console.log("scribble");
+ break;
+ }
+ if (actionPerformed) {
+ this._points = [];
+ }
+ }
+
+ if (!actionPerformed) {
+ const target = document.elementFromPoint(this._points[0].X, this._points[0].Y);
+ target?.dispatchEvent(
+ new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+ {
+ bubbles: true,
+ detail: {
+ points: this._points,
+ gesture: GestureUtils.Gestures.Stroke,
+ bounds: B
+ }
+ }
+ )
+ )
+ this._points = [];
+ }
+ }
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ @computed get svgBounds() {
+ const xs = this._points.map(p => p.X);
+ const ys = this._points.map(p => p.Y);
+ const right = Math.max(...xs);
+ const left = Math.min(...xs);
+ const bottom = Math.max(...ys);
+ const 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);
+ }
+
+ const B = this.svgBounds;
+
+ return (
+ <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
+ {CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
+ </svg>
+ );
+ }
+
+ render() {
+ return (
+ <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
+ {this.props.children}
+ {this._palette}
+ {this.currentStroke}
+ </div>);
+ }
+}
+
+Scripting.addGlobal("GestureOverlay", GestureOverlay);
+Scripting.addGlobal(function setPen(width: any, color: any) { runInAction(() => { GestureOverlay.Instance.Color = color; GestureOverlay.Instance.Width = width; }); });
+Scripting.addGlobal(function resetPen() { runInAction(() => { GestureOverlay.Instance.Color = "rgb(244, 67, 54)"; GestureOverlay.Instance.Width = 5; }); }); \ No newline at end of file
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss
deleted file mode 100644
index daff58fd6..000000000
--- a/src/client/views/InkSelectDecorations.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.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
deleted file mode 100644
index 3ad50762d..000000000
--- a/src/client/views/InkSelectDecorations.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-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 } {
- const left = Number.MAX_VALUE;
- const top = Number.MAX_VALUE;
- const right = -Number.MAX_VALUE;
- const 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() {
- const 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/InkingControl.tsx b/src/client/views/InkingControl.tsx
index e33f193b8..be07a9024 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -4,19 +4,19 @@ import { Doc } from "../../new_fields/Doc";
import { InkTool } from "../../new_fields/InkField";
import { List } from "../../new_fields/List";
import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types";
import { Utils } from "../../Utils";
import { Scripting } from "../util/Scripting";
import { SelectionManager } from "../util/SelectionManager";
import { undoBatch, UndoManager } from "../util/UndoManager";
import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
-
+import GestureOverlay from "./GestureOverlay";
export class InkingControl {
@observable static Instance: InkingControl;
- @observable private _selectedTool: InkTool = InkTool.None;
- @observable private _selectedColor: string = "rgb(244, 67, 54)";
- @observable private _selectedWidth: string = "5";
+ @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(CurrentUserUtils.UserDocument.inkTool)) ?? InkTool.None; }
+ @computed private get _selectedColor(): string { return GestureOverlay.Instance.Color ?? FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; }
+ @computed private get _selectedWidth(): string { return GestureOverlay.Instance.Width?.toString() ?? FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; }
@observable public _open: boolean = false;
constructor() {
@@ -24,7 +24,8 @@ export class InkingControl {
}
switchTool = action((tool: InkTool): void => {
- this._selectedTool = tool;
+ // this._selectedTool = tool;
+ CurrentUserUtils.UserDocument.inkTool = tool;
});
decimalToHexString(number: number) {
if (number < 0) {
@@ -36,7 +37,7 @@ export class InkingControl {
@undoBatch
switchColor = action((color: ColorState): void => {
- this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
+ CurrentUserUtils.UserDocument.inkColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
if (InkingControl.Instance.selectedTool === InkTool.None) {
const selected = SelectionManager.SelectedDocuments();
@@ -99,7 +100,8 @@ export class InkingControl {
});
@action
switchWidth = (width: string): void => {
- this._selectedWidth = width;
+ // this._selectedWidth = width;
+ CurrentUserUtils.UserDocument.inkWidth = width;
}
@computed
@@ -114,7 +116,8 @@ export class InkingControl {
@action
updateSelectedColor(value: string) {
- this._selectedColor = value;
+ // this._selectedColor = value;
+ CurrentUserUtils.UserDocument.inkColor = value;
}
@computed
@@ -127,6 +130,7 @@ Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { Ink
Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
Scripting.addGlobal(function activateScrubber(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Scrubber : InkTool.None); });
+Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); });
Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); }); \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index a413eebc9..8b346d5d9 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -9,6 +9,7 @@ import { InkingControl } from "./InkingControl";
import "./InkingStroke.scss";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
+import { TraceMobx } from "../../new_fields/util";
type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
@@ -35,6 +36,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
@computed get PanelHeight() { return this.props.PanelHeight(); }
render() {
+ TraceMobx();
const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
@@ -42,7 +44,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
const top = Math.min(...ys);
const right = Math.max(...xs);
const bottom = Math.max(...ys);
- const points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth);
+ const points = CreatePolyline(data, left, top, this.Document.color, this.Document.strokeWidth);
const width = right - left;
const height = bottom - top;
const scaleX = this.PanelWidth / width;
@@ -50,7 +52,7 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
return (
<svg width={width} height={height} style={{
transformOrigin: "top left",
- transform: `translate(${left}px, ${top}px) scale(${scaleX}, ${scaleY})`,
+ transform: `scale(${scaleX}, ${scaleY})`,
mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset",
pointerEvents: "all"
}}>
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index 4c8c95529..2a1cc3303 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -9,8 +9,8 @@
.mainContent-div {
position: relative;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
}
// add nodes menu. Note that the + button is actually an input label, not an actual button.
@@ -28,6 +28,7 @@
top: 0;
left: 0;
z-index: 1;
+ touch-action: none;
}
.mainView-mainContent {
@@ -66,7 +67,7 @@
.mainView-libraryFlyout {
height: 100%;
- width:100%;
+ width: 100%;
position: absolute;
display: flex;
flex-direction: column;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5bf5dbcc1..93fb0a07c 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, faCompressArrowsAlt
+ faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -36,7 +36,7 @@ import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
-import InkSelectDecorations from './InkSelectDecorations';
+import GestureOverlay from './GestureOverlay';
import { Scripting } from '../util/Scripting';
import { AudioBox } from './nodes/AudioBox';
import { TraceMobx } from '../../new_fields/util';
@@ -136,6 +136,8 @@ export class MainView extends React.Component {
library.add(faChevronRight);
library.add(faEllipsisV);
library.add(faMusic);
+ library.add(faPhone);
+ library.add(faStamp);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -511,8 +513,9 @@ export class MainView extends React.Component {
<SharingManager />
<GoogleAuthenticationManager />
<DocumentDecorations />
- <InkSelectDecorations />
- {this.mainContent}
+ <GestureOverlay>
+ {this.mainContent}
+ </GestureOverlay>
<PreviewCursor />
<ContextMenu />
<RadialMenu/>
diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss
new file mode 100644
index 000000000..2626774cb
--- /dev/null
+++ b/src/client/views/Palette.scss
@@ -0,0 +1,20 @@
+.palette-container {
+ .palette-thumb {
+ width: 300px;
+ height: 300px;
+ touch-action: pan-x;
+ overflow: scroll;
+ position: absolute;
+
+ .palette-thumbContent {
+ width: 100%;
+ height: 100%;
+ }
+
+ .palette-button {
+ width: 100px;
+ height: 100px;
+ background: blue;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
new file mode 100644
index 000000000..3649cccfe
--- /dev/null
+++ b/src/client/views/Palette.tsx
@@ -0,0 +1,63 @@
+import * as React from "react";
+import "./Palette.scss";
+import { PointData } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { Docs } from "../documents/Documents";
+import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
+import { List } from "../../new_fields/List";
+import { DocumentView } from "./nodes/DocumentView";
+import { emptyPath, returnFalse, emptyFunction, returnOne, returnEmptyString, returnTrue } from "../../Utils";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { Transform } from "../util/Transform";
+import { computed, action } from "mobx";
+import { FieldValue, Cast } from "../../new_fields/Types";
+import { observer } from "mobx-react";
+import { DocumentContentsView } from "./nodes/DocumentContentsView";
+import { CollectionStackingView } from "./collections/CollectionStackingView";
+import { CollectionView } from "./collections/CollectionView";
+
+export interface PaletteProps {
+ x: number;
+ y: number;
+ thumb: number[];
+ thumbDoc: Doc;
+}
+
+@observer
+export default class Palette extends React.Component<PaletteProps> {
+ render() {
+ return (
+ <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
+ <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
+ <div className="palette-thumbContent">
+ <DocumentView
+ Document={this.props.thumbDoc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ removeDocument={undefined}
+ ruleProvider={undefined}
+ onClick={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={() => window.screen.width}
+ PanelHeight={() => window.screen.height}
+ renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={returnEmptyString}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}>
+ </DocumentView>
+ </div>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 7d9184bea..2a7599fbf 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -1,6 +1,7 @@
import * as React from 'react';
import { action } from 'mobx';
import { InteractionUtils } from '../util/InteractionUtils';
+import { SelectionManager } from '../util/SelectionManager';
import { RadialMenu } from './nodes/RadialMenu';
const HOLD_DURATION = 1000;
@@ -22,25 +23,44 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
*/
@action
protected onTouchStart = (e: React.TouchEvent): void => {
+ const actualPts: React.Touch[] = [];
for (let i = 0; i < e.targetTouches.length; i++) {
const pt: any = e.targetTouches.item(i);
+ actualPts.push(pt);
// pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
// and this seems to be the only way of differentiating pen and touch on touch events
- if (pt.radiusX > 0.5 && pt.radiusY > 0.5) {
+ if (pt.radiusX > 1 && pt.radiusY > 1) {
this.prevPoints.set(pt.identifier, pt);
}
}
+ const ptsToDelete: number[] = [];
+ this.prevPoints.forEach(pt => {
+ if (!actualPts.includes(pt)) {
+ ptsToDelete.push(pt.identifier);
+ }
+ });
+
+ ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
+
if (this.prevPoints.size) {
switch (this.prevPoints.size) {
case 1:
this.handle1PointerDown(e);
e.persist();
+ // if (this.holdTimer) {
+ // clearTimeout(this.holdTimer)
+ // this.holdTimer = undefined;
+ // }
this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e), HOLD_DURATION);
+ // console.log(this.holdTimer);
break;
case 2:
this.handle2PointersDown(e);
break;
+ case 5:
+ this.handleHandDown(e);
+ break;
}
}
}
@@ -50,7 +70,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
*/
@action
protected onTouch = (e: TouchEvent): void => {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
// if we're not actually moving a lot, don't consider it as dragging yet
if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
@@ -58,6 +78,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
if (this.holdTimer) {
console.log("CLEAR")
clearTimeout(this.holdTimer);
+ // this.holdTimer = undefined;
}
switch (myTouches.length) {
case 1:
@@ -142,4 +163,8 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
document.removeEventListener("touchmove", this.onTouch);
}
+ handleHandDown = (e: React.TouchEvent) => {
+ // e.stopPropagation();
+ // e.preventDefault();
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index cb95dcbbc..8b3d332af 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -15,6 +15,11 @@
display: flex;
justify-content: space-between;
flex-wrap: nowrap;
+ touch-action: none;
+
+ div {
+ touch-action: none;
+ }
.collectionSchemaView-tableContainer {
@@ -49,7 +54,7 @@
.rt-table {
height: 100%;
display: -webkit-inline-box;
- direction: ltr;
+ direction: ltr;
overflow: visible;
}
@@ -202,7 +207,7 @@ button.add-column {
border-bottom: 1px solid lightgray;
&:first-child {
- padding-top : 0;
+ padding-top: 0;
}
&:last-child {
@@ -231,7 +236,7 @@ button.add-column {
transition: background-color 0.2s;
&:hover {
- background-color: $light-color-secondary;
+ background-color: $light-color-secondary;
}
&.active {
@@ -267,7 +272,7 @@ button.add-column {
overflow-y: scroll;
position: absolute;
top: 28px;
- box-shadow: 0 10px 16px rgba(0,0,0,0.1);
+ box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1);
.key-option {
background-color: $light-color;
@@ -380,6 +385,7 @@ button.add-column {
&.editing {
padding: 0;
+
input {
outline: 0;
border: none;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e71e11b48..992820fc7 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -153,7 +153,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
createRef = (ele: HTMLDivElement | null) => {
this._masonryGridRef = ele;
- this.createDropTarget(ele!); //so the whole grid is the drop target?
+ this.createDropAndGestureTarget(ele!); //so the whole grid is the drop target?
}
overlays = (doc: Doc) => {
@@ -309,7 +309,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
docList={docList}
parent={this}
type={type}
- createDropTarget={this.createDropTarget}
+ createDropTarget={this.createDropAndGestureTarget}
screenToLocalTransform={this.props.ScreenToLocalTransform}
/>;
}
@@ -342,7 +342,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
docList={docList}
parent={this}
type={type}
- createDropTarget={this.createDropTarget}
+ createDropTarget={this.createDropAndGestureTarget}
screenToLocalTransform={this.props.ScreenToLocalTransform}
setDocHeight={this.setDocHeight}
/>;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 062521690..73dc7edc6 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -23,6 +23,7 @@ import { basename } from 'path';
import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
import { ImageUtils } from "../../util/Import & Export/ImageUtils";
import { Networking } from "../../Network";
+import { GestureUtils } from "../../../pen-gestures/GestureUtils";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc) => boolean;
@@ -47,15 +48,18 @@ export interface SubCollectionViewProps extends CollectionViewProps {
export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
private dropDisposer?: DragManager.DragDropDisposer;
+ private gestureDisposer?: GestureUtils.GestureEventDisposer;
private _childLayoutDisposer?: IReactionDisposer;
- protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
this.dropDisposer && this.dropDisposer();
+ this.gestureDisposer && this.gestureDisposer();
if (ele) {
this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+ this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
}
}
protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view
- this.createDropTarget(ele);
+ this.createDropAndGestureTarget(ele);
}
componentDidMount() {
@@ -130,6 +134,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
@undoBatch
+ protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
+
+ }
+
+ @undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
const docDragData = de.complete.docDragData;
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 0b9dc2eb2..2fa6813d7 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -7,9 +7,9 @@
border-radius: inherit;
box-sizing: border-box;
height: 100%;
- width:100%;
+ width: 100%;
position: relative;
- top:0;
+ top: 0;
padding-left: 10px;
padding-right: 10px;
background: $light-color-secondary;
@@ -17,6 +17,7 @@
overflow: auto;
user-select: none;
cursor: default;
+ touch-action: pan-y;
ul {
list-style: none;
@@ -115,6 +116,7 @@
.treeViewItem-header {
border: transparent 1px solid;
display: flex;
+
.editableView-container-editing-oneLine {
min-width: 15px;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index eb5a074bb..84945c6e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
import { InkTool, InkField, InkData } 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 { BoolCast, Cast, DateCast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
import { DocServer } from "../../../DocServer";
@@ -44,6 +44,7 @@ import { TraceMobx } from "../../../../new_fields/util";
import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
import { LinkManager } from "../../../util/LinkManager";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import Palette from "../../Palette";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -272,11 +273,12 @@ 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;
+ if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ 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.props.active(true)) {
document.removeEventListener("pointermove", this.onPointerMove);
@@ -284,14 +286,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
// if physically using a pen or we're in pen or highlighter mode
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
- e.stopPropagation();
- e.preventDefault();
- const point = this.getTransform().transformPoint(e.pageX, e.pageY);
- this._points.push({ X: point[0], Y: point[1] });
- }
+ // if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // const point = this.getTransform().transformPoint(e.pageX, e.pageY);
+ // this._points.push({ X: point[0], Y: point[1] });
+ // }
// if not using a pen and in no ink mode
- else if (InkingControl.Instance.selectedTool === InkTool.None) {
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
this._lastX = e.pageX;
this._lastY = e.pageY;
}
@@ -332,13 +334,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
document.addEventListener("touchmove", this.onTouch);
document.removeEventListener("touchend", this.onTouchEnd);
document.addEventListener("touchend", this.onTouchEnd);
- if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
- e.stopPropagation();
- e.preventDefault();
- const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
- this._points.push({ X: point[0], Y: point[1] });
- }
- else if (InkingControl.Instance.selectedTool === InkTool.None) {
+ // if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) {
+ // e.stopPropagation();
+ // e.preventDefault();
+ // const point = this.getTransform().transformPoint(pt.pageX, pt.pageY);
+ // this._points.push({ X: point[0], Y: point[1] });
+ // }
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
this._lastX = pt.pageX;
this._lastY = pt.pageY;
e.stopPropagation();
@@ -352,72 +354,44 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
- @action
- onPointerUp = (e: PointerEvent): void => {
- if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && this._points.length <= 1) return;
-
- if (this._points.length > 1) {
- const B = this.svgBounds;
- const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
-
- const result = GestureUtils.GestureRecognizer.Recognize(new Array(points));
- let actionPerformed = false;
- if (result && result.Score > 0.7) {
- switch (result.Name) {
- case GestureUtils.Gestures.Box:
- const bounds = { x: Math.min(...this._points.map(p => p.X)), r: Math.max(...this._points.map(p => p.X)), y: Math.min(...this._points.map(p => p.Y)), b: Math.max(...this._points.map(p => p.Y)) };
- const sel = this.getActiveDocuments().filter(doc => {
- const l = NumCast(doc.x);
- const r = l + doc[WidthSym]();
- const t = NumCast(doc.y);
- const b = t + doc[HeightSym]();
- const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
- if (pass) {
- doc.x = l - B.left - B.width / 2;
- doc.y = t - B.top - B.height / 2;
- }
- return pass;
- });
- this.addDocument(Docs.Create.FreeformDocument(sel, { x: B.left, y: B.top, width: B.width, height: B.height, panX: 0, panY: 0 }));
- sel.forEach(d => this.props.removeDocument(d));
- actionPerformed = true;
- break;
- case GestureUtils.Gestures.Line:
- const ep1 = this._points[0];
- const ep2 = this._points[this._points.length - 1];
- let d1: Doc | undefined;
- let d2: Doc | undefined;
- this.getActiveDocuments().map(doc => {
- const l = NumCast(doc.x);
- const r = l + doc[WidthSym]();
- const t = NumCast(doc.y);
- const b = t + doc[HeightSym]();
- if (!d1 && l < ep1.X && r > ep1.X && t < ep1.Y && b > ep1.Y) {
- d1 = doc;
- }
- else if (!d2 && l < ep2.X && r > ep2.X && t < ep2.Y && b > ep2.Y) {
- d2 = doc;
- }
- });
- if (d1 && d2) {
- if (!LinkManager.Instance.doesLinkExist(d1, d2)) {
- DocUtils.MakeLink({ doc: d1 }, { doc: d2 });
- actionPerformed = true;
- }
- }
- break;
- }
- if (actionPerformed) {
- this._points = [];
- }
- }
-
- if (!actionPerformed) {
- const 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 });
+ @undoBatch
+ onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+ switch (ge.gesture) {
+ case GestureUtils.Gestures.Stroke:
+ const points = ge.points;
+ const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+ const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { x: B.x, y: B.y, width: B.width, height: B.height });
this.addDocument(inkDoc);
- this._points = [];
- }
+ e.stopPropagation();
+ break;
+ case GestureUtils.Gestures.Box:
+ const lt = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
+ const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
+ const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] };
+ const bWidth = bounds.r - bounds.x;
+ const bHeight = bounds.b - bounds.y;
+ const sel = this.getActiveDocuments().filter(doc => {
+ const l = NumCast(doc.x);
+ const r = l + doc[WidthSym]();
+ const t = NumCast(doc.y);
+ const b = t + doc[HeightSym]();
+ const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t);
+ if (pass) {
+ doc.x = l - bounds.x - bWidth / 2;
+ doc.y = t - bounds.y - bHeight / 2;
+ }
+ return pass;
+ });
+ this.addDocument(Docs.Create.FreeformDocument(sel, { x: bounds.x, y: bounds.y, width: bWidth, height: bHeight, panX: 0, panY: 0 }));
+ sel.forEach(d => this.props.removeDocument(d));
+ break;
+
}
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -471,13 +445,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
return;
}
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ return;
+ }
if (!e.cancelBubble) {
const selectedTool = InkingControl.Instance.selectedTool;
- if (selectedTool === InkTool.Highlighter || selectedTool === InkTool.Pen || InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- const point = this.getTransform().transformPoint(e.clientX, e.clientY);
- this._points.push({ X: point[0], Y: point[1] });
- }
- else if (selectedTool === InkTool.None) {
+ if (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();
@@ -495,7 +468,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
handle1PointerMove = (e: TouchEvent) => {
// panning a workspace
if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
const pt = myTouches[0];
if (pt) {
if (InkingControl.Instance.selectedTool === InkTool.None) {
@@ -508,10 +481,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
this.pan(pt);
}
- else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
- const point = this.getTransform().transformPoint(pt.clientX, pt.clientY);
- this._points.push({ X: point[0], Y: point[1] });
- }
}
e.stopPropagation();
e.preventDefault();
@@ -521,7 +490,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
handle2PointersMove = (e: TouchEvent) => {
// pinch zooming
if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+ const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
const pt1 = myTouches[0];
const pt2 = myTouches[1];
@@ -879,6 +848,49 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], inkData);
}
+ private thumbIdentifier?: number;
+
+ // @action
+ // handleHandDown = (e: React.TouchEvent) => {
+ // const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+ // const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+ // this.thumbIdentifier = thumb?.identifier;
+ // const others = fingers.filter(f => f !== thumb);
+ // const minX = Math.min(...others.map(f => f.clientX));
+ // const minY = Math.min(...others.map(f => f.clientY));
+ // const t = this.getTransform().transformPoint(minX, minY);
+ // const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
+
+ // const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+ // if (thumbDoc) {
+ // this._palette = <Palette x={t[0]} y={t[1]} thumb={th} thumbDoc={thumbDoc} />;
+ // }
+
+ // document.removeEventListener("touchmove", this.onTouch);
+ // document.removeEventListener("touchmove", this.handleHandMove);
+ // document.addEventListener("touchmove", this.handleHandMove);
+ // document.removeEventListener("touchend", this.handleHandUp);
+ // document.addEventListener("touchend", this.handleHandUp);
+ // }
+
+ // @action
+ // handleHandMove = (e: TouchEvent) => {
+ // for (let i = 0; i < e.changedTouches.length; i++) {
+ // const pt = e.changedTouches.item(i);
+ // if (pt?.identifier === this.thumbIdentifier) {
+ // }
+ // }
+ // }
+
+ // @action
+ // handleHandUp = (e: TouchEvent) => {
+ // this.onTouchEnd(e);
+ // if (this.prevPoints.size < 3) {
+ // this._palette = undefined;
+ // document.removeEventListener("touchend", this.handleHandUp);
+ // }
+ // }
+
onContextMenu = (e: React.MouseEvent) => {
const layoutItems: ContextMenuProps[] = [];
@@ -942,34 +954,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
];
}
- @computed get svgBounds() {
- const xs = this._points.map(p => p.X);
- const ys = this._points.map(p => p.Y);
- const right = Math.max(...xs);
- const left = Math.min(...xs);
- const bottom = Math.max(...ys);
- const 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);
- }
-
- const B = this.svgBounds;
-
- return (
- <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, position: "absolute", zIndex: 30000 }}>
- {CreatePolyline(this._points, B.left, B.top)}
- </svg>
- );
- }
+ // @observable private _palette?: JSX.Element;
children = () => {
const eles: JSX.Element[] = [];
this.extensionDoc && (eles.push(...this.childViews()));
- this.currentStroke && (eles.push(this.currentStroke));
+ // this._palette && (eles.push(this._palette));
+ // this.currentStroke && (eles.push(this.currentStroke));
eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
return eles;
}
@@ -998,7 +989,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
if (!this.extensionDoc) return (null);
// let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
- return <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ return <div className={"collectionfreeformview-container"} ref={this.createDropAndGestureTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
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} onTouchStart={this.onTouchStart}>
{!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e6c6aaa08..2e0ae09ba 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -45,6 +45,7 @@ import { InkTool } from '../../../new_fields/InkField';
import { TraceMobx } from '../../../new_fields/util';
import { List } from '../../../new_fields/List';
import { FormattedTextBoxComment } from './FormattedTextBoxComment';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
import { RadialMenu } from './RadialMenu';
import { RadialMenuProps } from './RadialMenuItem';
@@ -61,6 +62,8 @@ export interface DocumentViewProps {
LibraryPath: Doc[];
fitToBox?: boolean;
onClick?: ScriptField;
+ onPointerDown?: ScriptField;
+ onPointerUp?: ScriptField;
dragDivName?: string;
addDocument?: (doc: Doc) => boolean;
removeDocument?: (doc: Doc) => boolean;
@@ -97,6 +100,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
private _hitTemplateDrag = false;
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
+ private _gestureEventDisposer?: GestureUtils.GestureEventDisposer;
private _titleRef = React.createRef<EditableView>();
public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive
@@ -106,28 +110,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; }
@computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; }
@computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }
+ @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
+ @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
private _firstX: number = 0;
private _firstY: number = 0;
- handle1PointerHoldStart = (e: React.TouchEvent): any => {
- this.onRadialMenu(e);
- const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
- this._firstX = pt.pageX;
- this._firstY = pt.pageY;
- e.stopPropagation();
- e.preventDefault();
+ // handle1PointerHoldStart = (e: React.TouchEvent): any => {
+ // this.onRadialMenu(e);
+ // const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
+ // this._firstX = pt.pageX;
+ // this._firstY = pt.pageY;
+ // e.stopPropagation();
+ // e.preventDefault();
- document.removeEventListener("touchmove", this.onTouch);
- document.removeEventListener("touchmove", this.handle1PointerHoldMove);
- document.addEventListener("touchmove", this.handle1PointerHoldMove);
- document.removeEventListener("touchend", this.handle1PointerHoldEnd);
- document.addEventListener("touchend", this.handle1PointerHoldEnd);
- }
+ // document.removeEventListener("touchmove", this.onTouch);
+ // document.removeEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.addEventListener("touchmove", this.handle1PointerHoldMove);
+ // document.removeEventListener("touchend", this.handle1PointerHoldEnd);
+ // document.addEventListener("touchend", this.handle1PointerHoldEnd);
+ // }
handle1PointerHoldMove = (e: TouchEvent): void => {
- const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+ const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) {
this.handleRelease();
}
@@ -151,26 +157,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@action
onRadialMenu = (e: React.TouchEvent): void => {
- const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+ const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
RadialMenu.Instance.openMenu();
RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 });
RadialMenu.Instance.addItem({ description: "Delete this document", event: () => this.props.ContainingCollectionView?.removeDocument(this.props.Document), icon: "trash", selected: -1 });
- RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "tab", selected: -1 });
- RadialMenu.Instance.addItem({ description: "four", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group", selected: -1 });
- // !six?rm.addItem({ description: "six", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" ,selected:-1}):null;
- // !seven?rm.addItem({ description: "seven", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" ,selected:-1}):null;
+ RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, undefined, "onRight"), icon: "folder", selected: -1 });
+ RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 });
RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15);
if (!SelectionManager.IsSelected(this, true)) {
SelectionManager.SelectDoc(this, false);
}
+ e.stopPropagation();
}
@action
componentDidMount() {
this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this)));
+ this._mainCont.current && (this._gestureEventDisposer = GestureUtils.MakeGestureTarget(this._mainCont.current, this.onGesture.bind(this)));
!this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this);
}
@@ -241,7 +247,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- onClick = async (e: React.MouseEvent) => {
+ onClick = async (e: React.MouseEvent | React.PointerEvent) => {
if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
(Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
e.stopPropagation();
@@ -300,150 +306,147 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- // handle1PointerDown = (e: React.TouchEvent) => {
- // if (!e.nativeEvent.cancelBubble) {
- // const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
- // this._downX = touch.clientX;
- // this._downY = touch.clientY;
- // this._hitTemplateDrag = false;
- // for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
- // if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
- // this._hitTemplateDrag = true;
- // }
- // }
- // if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation();
- // document.removeEventListener("touchmove", this.onTouch);
- // document.addEventListener("touchmove", this.onTouch);
- // document.removeEventListener("touchend", this.onTouchEnd);
- // document.addEventListener("touchend", this.onTouchEnd);
- // if ((e.nativeEvent as any).formattedHandled) e.stopPropagation();
- // console.log("down")
- // }
- // }
+ handle1PointerDown = (e: React.TouchEvent) => {
+ if (this.Document.onPointerDown) return;
+ if (!e.nativeEvent.cancelBubble) {
+ const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
+ this._downX = touch.clientX;
+ this._downY = touch.clientY;
+ this._hitTemplateDrag = false;
+ for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) {
+ if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") {
+ this._hitTemplateDrag = true;
+ }
+ }
+ if ((this.active || this.Document.onDragStart || this.Document.onClick) && !e.ctrlKey && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation();
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ if ((e.nativeEvent as any).formattedHandled) e.stopPropagation();
+ }
+ }
- // handle1PointerMove = (e: TouchEvent) => {
- // if ((e as any).formattedHandled) { e.stopPropagation; return; }
- // if (e.cancelBubble && this.active) {
- // document.removeEventListener("touchmove", this.onTouch);
- // }
- // else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
- // const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
- // if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
- // if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
- // document.removeEventListener("touchmove", this.onTouch);
- // document.removeEventListener("touchend", this.onTouchEnd);
- // this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
- // }
- // }
- // e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- // e.preventDefault();
-
- // }
- // }
+ handle1PointerMove = (e: TouchEvent) => {
+ if ((e as any).formattedHandled) { e.stopPropagation; return; }
+ if (e.cancelBubble && this.active) {
+ document.removeEventListener("touchmove", this.onTouch);
+ }
+ else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
+ const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
+ if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
+ if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag);
+ }
+ }
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
- // handle2PointersDown = (e: React.TouchEvent) => {
- // if (!e.nativeEvent.cancelBubble && !this.isSelected()) {
- // e.stopPropagation();
- // e.preventDefault();
+ }
+ }
- // document.removeEventListener("touchmove", this.onTouch);
- // document.addEventListener("touchmove", this.onTouch);
- // document.removeEventListener("touchend", this.onTouchEnd);
- // document.addEventListener("touchend", this.onTouchEnd);
- // }
- // }
+ handle2PointersDown = (e: React.TouchEvent) => {
+ if (!e.nativeEvent.cancelBubble && !this.isSelected()) {
+ e.stopPropagation();
+ e.preventDefault();
- // @action
- // handle2PointersMove = (e: TouchEvent) => {
- // const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
- // const pt1 = myTouches[0];
- // const pt2 = myTouches[1];
- // const oldPoint1 = this.prevPoints.get(pt1.identifier);
- // const oldPoint2 = this.prevPoints.get(pt2.identifier);
- // const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
- // if (pinching !== 0 && oldPoint1 && oldPoint2) {
- // // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
- // // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
- // // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
- // // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
- // // let dW = -dX;
- // // let dH = -dY;
- // const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
- // const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
- // const dX = -1 * Math.sign(dW);
- // const dY = -1 * Math.sign(dH);
-
- // if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- // const doc = PositionDocument(this.props.Document);
- // const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
- // let nwidth = layoutDoc.nativeWidth || 0;
- // let nheight = layoutDoc.nativeHeight || 0;
- // const width = (layoutDoc.width || 0);
- // const height = (layoutDoc.height || (nheight / nwidth * width));
- // const scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling();
- // const actualdW = Math.max(width + (dW * scale), 20);
- // const actualdH = Math.max(height + (dH * scale), 20);
- // doc.x = (doc.x || 0) + dX * (actualdW - width);
- // doc.y = (doc.y || 0) + dY * (actualdH - height);
- // const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
- // if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
- // layoutDoc.ignoreAspect = false;
- // layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
- // layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
- // }
- // if (fixedAspect && (!nwidth || !nheight)) {
- // layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
- // layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
- // }
- // if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
- // if (Math.abs(dW) > Math.abs(dH)) {
- // if (!fixedAspect) {
- // layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
- // }
- // layoutDoc.width = actualdW;
- // if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
- // else layoutDoc.height = actualdH;
- // }
- // else {
- // if (!fixedAspect) {
- // layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
- // }
- // layoutDoc.height = actualdH;
- // if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
- // else layoutDoc.width = actualdW;
- // }
- // } else {
- // dW && (layoutDoc.width = actualdW);
- // dH && (layoutDoc.height = actualdH);
- // dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
- // }
- // }
- // // let newWidth = Math.max(Math.abs(oldPoint1!.clientX - oldPoint2!.clientX), Math.abs(pt1.clientX - pt2.clientX))
- // // this.props.Document.width = newWidth;
- // e.stopPropagation();
- // e.preventDefault();
- // }
- // }
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ }
+ }
- onPointerDown = (e: React.PointerEvent): void => {
+ @action
+ handle2PointersMove = (e: TouchEvent) => {
+ const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+ const pt1 = myTouches[0];
+ const pt2 = myTouches[1];
+ const oldPoint1 = this.prevPoints.get(pt1.identifier);
+ const oldPoint2 = this.prevPoints.get(pt2.identifier);
+ const pinching = InteractionUtils.Pinning(pt1, pt2, oldPoint1!, oldPoint2!);
+ if (pinching !== 0 && oldPoint1 && oldPoint2) {
+ // let dX = (Math.min(pt1.clientX, pt2.clientX) - Math.min(oldPoint1.clientX, oldPoint2.clientX));
+ // let dY = (Math.min(pt1.clientY, pt2.clientY) - Math.min(oldPoint1.clientY, oldPoint2.clientY));
+ // let dX = Math.sign(Math.abs(pt1.clientX - oldPoint1.clientX) - Math.abs(pt2.clientX - oldPoint2.clientX));
+ // let dY = Math.sign(Math.abs(pt1.clientY - oldPoint1.clientY) - Math.abs(pt2.clientY - oldPoint2.clientY));
+ // let dW = -dX;
+ // let dH = -dY;
+ const dW = (Math.abs(pt1.clientX - pt2.clientX) - Math.abs(oldPoint1.clientX - oldPoint2.clientX));
+ const dH = (Math.abs(pt1.clientY - pt2.clientY) - Math.abs(oldPoint1.clientY - oldPoint2.clientY));
+ const dX = -1 * Math.sign(dW);
+ const dY = -1 * Math.sign(dH);
+
+ if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
+ const doc = PositionDocument(this.props.Document);
+ const layoutDoc = PositionDocument(Doc.Layout(this.props.Document));
+ let nwidth = layoutDoc.nativeWidth || 0;
+ let nheight = layoutDoc.nativeHeight || 0;
+ const width = (layoutDoc.width || 0);
+ const height = (layoutDoc.height || (nheight / nwidth * width));
+ const scale = this.props.ScreenToLocalTransform().Scale * this.props.ContentScaling();
+ const actualdW = Math.max(width + (dW * scale), 20);
+ const actualdH = Math.max(height + (dH * scale), 20);
+ doc.x = (doc.x || 0) + dX * (actualdW - width);
+ doc.y = (doc.y || 0) + dY * (actualdH - height);
+ const fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight);
+ if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) {
+ layoutDoc.ignoreAspect = false;
+
+ layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+ layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+ }
+ if (fixedAspect && (!nwidth || !nheight)) {
+ layoutDoc.nativeWidth = nwidth = layoutDoc.width || 0;
+ layoutDoc.nativeHeight = nheight = layoutDoc.height || 0;
+ }
+ if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) {
+ if (Math.abs(dW) > Math.abs(dH)) {
+ if (!fixedAspect) {
+ layoutDoc.nativeWidth = actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0);
+ }
+ layoutDoc.width = actualdW;
+ if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width;
+ else layoutDoc.height = actualdH;
+ }
+ else {
+ if (!fixedAspect) {
+ layoutDoc.nativeHeight = actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0);
+ }
+ layoutDoc.height = actualdH;
+ if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height;
+ else layoutDoc.width = actualdW;
+ }
+ } else {
+ dW && (layoutDoc.width = actualdW);
+ dH && (layoutDoc.height = actualdH);
+ dH && layoutDoc.autoHeight && (layoutDoc.autoHeight = false);
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (this.onPointerDownHandler && this.onPointerDownHandler.script) {
+ this.onPointerDownHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ return;
+ }
// console.log(e.button)
// console.log(e.nativeEvent)
// continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
- if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) {
+ if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
}
return;
}
- if ((!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart)) {
- // if ((e.nativeEvent.cancelBubble && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)))
- // // return if we're inking, and not selecting a button document
- // || (InkingControl.Instance.selectedTool !== InkTool.None && !this.Document.onClick)
- // // return if using pen or eraser
- // || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) {
- // return;
- // }
+ if (!e.nativeEvent.cancelBubble || this.Document.onClick || this.Document.onDragStart) {
this._downX = e.clientX;
this._downY = e.clientY;
this._hitTemplateDrag = false;
@@ -467,6 +470,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onPointerMove = (e: PointerEvent): void => {
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
+ if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) 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)
}
@@ -484,12 +488,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
onPointerUp = (e: PointerEvent): void => {
+ if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ this.onPointerUpHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ return;
+ }
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
this._lastTap = Date.now();
}
+ onGesture = (e: Event, ge: GestureUtils.GestureEvent) => {
+ switch (ge.gesture) {
+ case GestureUtils.Gestures.Line:
+ ge.callbackFn && ge.callbackFn(this.props.Document);
+ e.stopPropagation();
+ break;
+ }
+ }
+
@undoBatch
deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index ec698b151..0b0280519 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -11,6 +11,8 @@ import { List } from '../new_fields/List';
import { observer } from 'mobx-react';
import { observable } from 'mobx';
import { Utils } from '../Utils';
+import MobileInterface from './MobileInterface';
+import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
@@ -104,10 +106,25 @@ class Uploader extends React.Component {
}
-DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
-
-ReactDOM.render((
- <Uploader />
-),
- document.getElementById('root')
-); \ No newline at end of file
+// DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
+(async () => {
+ const info = await CurrentUserUtils.loadCurrentUser();
+ DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + "mobile");
+ await Docs.Prototypes.initialize();
+ if (info.id !== "__guest__") {
+ // a guest will not have an id registered
+ await CurrentUserUtils.loadUserDocument(info);
+ }
+ document.getElementById('root')!.addEventListener('wheel', event => {
+ if (event.ctrlKey) {
+ event.preventDefault();
+ }
+ }, true);
+ ReactDOM.render((
+ // <Uploader />
+ <MobileInterface />
+ ),
+ document.getElementById('root')
+ );
+}
+)(); \ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
new file mode 100644
index 000000000..ea2fc917f
--- /dev/null
+++ b/src/mobile/MobileInterface.tsx
@@ -0,0 +1,69 @@
+import React = require('react');
+import { observer } from 'mobx-react';
+import { computed, action } from 'mobx';
+import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
+import { FieldValue, Cast } from '../new_fields/Types';
+import { Doc } from '../new_fields/Doc';
+import { Docs } from '../client/documents/Documents';
+import { CollectionView } from '../client/views/collections/CollectionView';
+import { DocumentView } from '../client/views/nodes/DocumentView';
+import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils';
+import { Transform } from '../client/util/Transform';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faPenNib, faHighlighter, faEraser, faMousePointer } from '@fortawesome/free-solid-svg-icons';
+
+@observer
+export default class MobileInterface extends React.Component {
+ @computed private get userDoc() { return CurrentUserUtils.UserDocument; }
+ @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
+
+ @action
+ componentDidMount = () => {
+ library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
+
+ if (this.userDoc && !this.mainContainer) {
+ const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
+ this.userDoc.activeMobile = doc;
+ }
+ }
+
+ @computed
+ get mainContent() {
+ if (this.mainContainer) {
+ return <DocumentView
+ Document={this.mainContainer}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={undefined}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ removeDocument={undefined}
+ ruleProvider={undefined}
+ onClick={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={() => window.screen.width}
+ PanelHeight={() => window.screen.height}
+ renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={returnEmptyString}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}>
+ </DocumentView>;
+ }
+ return "hello";
+ }
+
+ render() {
+ return (
+ <div className="mobile-container">
+ {this.mainContent}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 83d631958..20dbf4b17 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -9,7 +9,8 @@ export enum InkTool {
Pen,
Highlighter,
Eraser,
- Scrubber
+ Scrubber,
+ Stamp
}
export interface PointData {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 21e69fbed..1cba3cba9 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -20,6 +20,8 @@ export const documentSchema = createSchema({
dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy")
removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
+ onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
+ onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop)
onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped.
dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document
diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts
index 59a85b66b..062604458 100644
--- a/src/pen-gestures/GestureUtils.ts
+++ b/src/pen-gestures/GestureUtils.ts
@@ -1,19 +1,44 @@
import { NDollarRecognizer } from "./ndollar";
import { Type } from "typescript";
-import { InkField } from "../new_fields/InkField";
+import { InkField, PointData } from "../new_fields/InkField";
import { Docs } from "../client/documents/Documents";
import { Doc, WidthSym, HeightSym } from "../new_fields/Doc";
import { NumCast } from "../new_fields/Types";
import { CollectionFreeFormView } from "../client/views/collections/collectionFreeForm/CollectionFreeFormView";
+import { Rect } from "react-measure";
export namespace GestureUtils {
namespace GestureDataTypes {
export type BoxData = Array<Doc>;
}
+ export class GestureEvent {
+ constructor(
+ readonly gesture: Gestures,
+ readonly points: PointData[],
+ readonly bounds: Rect,
+ readonly callbackFn?: Function
+ ) { }
+ }
+
+ export interface GestureEventDisposer { (): void; }
+
+ export function MakeGestureTarget(
+ element: HTMLElement,
+ func: (e: Event, ge: GestureEvent) => void
+ ): GestureEventDisposer {
+ const handler = (e: Event) => func(e, (e as CustomEvent<GestureEvent>).detail);
+ element.addEventListener("dashOnGesture", handler);
+ return () => {
+ element.removeEventListener("dashOnGesture", handler);
+ }
+ }
+
export enum Gestures {
Box = "box",
- Line = "line"
+ Line = "line",
+ Stroke = "stroke",
+ Scribble = "scribble"
}
export const GestureRecognizer = new NDollarRecognizer(false);
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 220c37e2b..2ade6f102 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -28,6 +28,7 @@ export class CurrentUserUtils {
@observable public static GuestTarget: Doc | undefined;
@observable public static GuestWorkspace: Doc | undefined;
+ @observable public static GuestMobile: Doc | undefined;
// a default set of note types .. not being used yet...
static setupNoteTypes(doc: Doc) {
@@ -55,8 +56,10 @@ 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: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' },
{ 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 stamp", icon: "stamp", click: 'activateStamp(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this)', backgroundColor: "orange", 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 },
@@ -90,6 +93,52 @@ export class CurrentUserUtils {
}
}
+ static setupMobileButtons(doc: Doc, buttons?: string[]) {
+ const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
+ { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" },
+ { 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.filter(d => !buttons || !buttons.includes(d.title)).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,
+ ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen,
+ backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
+ }));
+ }
+
+ static setupThumbButtons(doc: Doc) {
+ const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
+ { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+ { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+ ];
+ return docProtoData.map(data => Docs.Create.FontIconDocument({
+ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
+ onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
+ onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
+ ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true,
+ backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
+ }));
+ }
+
+ static setupThumbDoc(userDoc: Doc) {
+ if (!userDoc.thumbDoc) {
+ return Docs.Create.MasonryDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
+ width: 300, columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
+ });
+ }
+ return userDoc.thumbDoc;
+ }
+
+ static setupMobileDoc(userDoc: Doc) {
+ return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
+ columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
+ });
+ }
+
// setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. when clicked, this panel will be displayed in the target container (ie, sidebarContainer)
static setupToolsPanel(sidebarContainer: Doc, doc: Doc) {
// setup a masonry view of all he creators