aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts39
-rw-r--r--src/client/util/DragManager.ts26
-rw-r--r--src/client/util/InteractionUtils.tsx17
-rw-r--r--src/client/views/GestureOverlay.tsx53
-rw-r--r--src/client/views/InkingStroke.tsx4
-rw-r--r--src/client/views/MainView.tsx13
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx1
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx1
-rw-r--r--src/client/views/collections/CollectionSubView.tsx14
-rw-r--r--src/client/views/collections/CollectionView.tsx2
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx16
-rw-r--r--src/client/views/nodes/DocumentView.tsx1
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx28
-rw-r--r--src/client/views/nodes/WebBox.scss15
-rw-r--r--src/client/views/nodes/WebBox.tsx136
-rw-r--r--src/mobile/ImageUpload.tsx9
-rw-r--r--src/mobile/MobileInkOverlay.scss39
-rw-r--r--src/mobile/MobileInkOverlay.tsx191
-rw-r--r--src/mobile/MobileInterface.scss18
-rw-r--r--src/mobile/MobileInterface.tsx294
-rw-r--r--src/server/ApiManagers/UploadManager.ts1
-rw-r--r--src/server/DashSession/DashSessionAgent.ts21
-rw-r--r--src/server/Message.ts31
-rw-r--r--src/server/Websocket/Websocket.ts22
-rw-r--r--src/server/authentication/models/current_user_utils.ts24
-rw-r--r--src/server/index.ts2
-rw-r--r--src/server/server_Initialization.ts24
29 files changed, 942 insertions, 106 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 5fcd2547e..33f7c1d35 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -1,10 +1,12 @@
import * as OpenSocket from 'socket.io-client';
-import { MessageStore, YoutubeQueryTypes } from "./../server/Message";
+import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message";
import { Opt, Doc } from '../new_fields/Doc';
import { Utils, emptyFunction } from '../Utils';
import { SerializationHelper } from './util/SerializationHelper';
import { RefField } from '../new_fields/RefField';
import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
+import GestureOverlay from './views/GestureOverlay';
+import MobileInkOverlay from '../mobile/MobileInkOverlay';
/**
* This class encapsulates the transfer and cross-client synchronization of
@@ -64,6 +66,26 @@ export namespace DocServer {
}
}
+ export namespace Mobile {
+
+ export function dispatchGesturePoints(content: GestureContent) {
+ Utils.Emit(_socket, MessageStore.GesturePoints, content);
+ }
+
+ export function dispatchOverlayTrigger(content: MobileInkOverlayContent) {
+ // _socket.emit("dispatchBoxTrigger");
+ Utils.Emit(_socket, MessageStore.MobileInkOverlayTrigger, content);
+ }
+
+ export function dispatchOverlayPositionUpdate(content: UpdateMobileInkOverlayPositionContent) {
+ Utils.Emit(_socket, MessageStore.UpdateMobileInkOverlayPosition, content);
+ }
+
+ export function dispatchMobileDocumentUpload(content: MobileDocumentUploadContent) {
+ Utils.Emit(_socket, MessageStore.MobileDocumentUpload, content);
+ }
+ }
+
const instructions = "This page will automatically refresh after this alert is closed. Expect to reconnect after about 30 seconds.";
function alertUser(connectionTerminationReason: string) {
switch (connectionTerminationReason) {
@@ -101,6 +123,21 @@ export namespace DocServer {
Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser);
+
+ // mobile ink overlay socket events to communicate between mobile view and desktop view
+ _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => {
+ MobileInkOverlay.Instance.drawStroke(content);
+ });
+ _socket.addEventListener("receiveOverlayTrigger", (content: MobileInkOverlayContent) => {
+ GestureOverlay.Instance.enableMobileInkOverlay(content);
+ MobileInkOverlay.Instance.initMobileInkOverlay(content);
+ });
+ _socket.addEventListener("receiveUpdateOverlayPosition", (content: UpdateMobileInkOverlayPositionContent) => {
+ MobileInkOverlay.Instance.updatePosition(content);
+ });
+ _socket.addEventListener("receiveMobileDocumentUpload", (content: MobileDocumentUploadContent) => {
+ MobileInkOverlay.Instance.uploadDocument(content);
+ });
}
function errorFunc(): never {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 5d2f1bc06..5d4b8fc8a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -180,7 +180,7 @@ export namespace DragManager {
);
}
element.dataset.canDrop = "true";
- const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
+ const handler = (e: Event) => { dropFunc(e, (e as CustomEvent<DropEvent>).detail); };
element.addEventListener("dashOnDrop", handler);
return () => {
element.removeEventListener("dashOnDrop", handler);
@@ -196,7 +196,7 @@ export namespace DragManager {
dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) :
dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d)
);
- e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) =>
+ e.docDragData ?.droppedDocuments.forEach((drop: Doc, i: number) =>
Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined));
};
dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded
@@ -266,6 +266,10 @@ export namespace DragManager {
StartDrag([ele], dragData, downX, downY, options);
}
+ export function StartImgDrag(ele: HTMLElement, downX: number, downY: number) {
+ StartDrag([ele], {}, downX, downY);
+ }
+
function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
eles = eles.filter(e => e);
if (!dragDiv) {
@@ -302,7 +306,7 @@ export namespace DragManager {
dragElement.style.transformOrigin = "0 0";
dragElement.style.borderRadius = getComputedStyle(ele).borderRadius;
dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
- dragElement.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`;
+ dragElement.style.transform = `translate(${rect.left + (options ?.offsetX || 0)}px, ${rect.top + (options ?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`;
dragElement.style.width = `${rect.width / scaleX}px`;
dragElement.style.height = `${rect.height / scaleY}px`;
@@ -331,8 +335,8 @@ export namespace DragManager {
return dragElement;
});
- const hideSource = options?.hideSource ? true : false;
- eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = hideSource) : (ele.hidden = hideSource));
+ const hideSource = options ?.hideSource ? true : false;
+ eles.map(ele => ele.parentElement && ele.parentElement ?.className === dragData.dragDivName ? (ele.parentElement.hidden = hideSource) : (ele.hidden = hideSource));
let lastX = downX;
let lastY = downY;
@@ -343,7 +347,7 @@ export namespace DragManager {
}
if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
AbortDrag();
- finishDrag?.(new DragCompleteEvent(true, dragData));
+ finishDrag ?.(new DragCompleteEvent(true, dragData));
CollectionDockingView.Instance.StartOtherDrag({
pageX: e.pageX,
pageY: e.pageY,
@@ -357,13 +361,13 @@ export namespace DragManager {
lastX = e.pageX;
lastY = e.pageY;
dragElements.map((dragElement, i) => (dragElement.style.transform =
- `translate(${(xs[i] += moveX) + (options?.offsetX || 0)}px, ${(ys[i] += moveY) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
+ `translate(${(xs[i] += moveX) + (options ?.offsetX || 0)}px, ${(ys[i] += moveY) + (options ?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
);
};
const hideDragShowOriginalElements = () => {
dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
- eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = false) : (ele.hidden = false));
+ eles.map(ele => ele.parentElement && ele.parentElement ?.className === dragData.dragDivName ? (ele.parentElement.hidden = false) : (ele.hidden = false));
};
const endDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
@@ -373,14 +377,14 @@ export namespace DragManager {
AbortDrag = () => {
hideDragShowOriginalElements();
SelectionManager.SetIsDragging(false);
- options?.dragComplete?.(new DragCompleteEvent(true, dragData));
+ options ?.dragComplete ?.(new DragCompleteEvent(true, dragData));
endDrag();
};
const upHandler = (e: PointerEvent) => {
hideDragShowOriginalElements();
dispatchDrag(eles, e, dragData, options, finishDrag);
SelectionManager.SetIsDragging(false);
- options?.dragComplete?.(new DragCompleteEvent(false, dragData));
+ options ?.dragComplete ?.(new DragCompleteEvent(false, dragData));
endDrag();
};
document.addEventListener("pointermove", moveHandler, true);
@@ -401,7 +405,7 @@ export namespace DragManager {
});
if (target) {
const complete = new DragCompleteEvent(false, dragData);
- finishDrag?.(complete);
+ finishDrag ?.(complete);
console.log(complete.aborted);
target.dispatchEvent(
new CustomEvent<DropEvent>("dashOnDrop", {
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index 184e37ba5..cf51b8e89 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,3 +1,5 @@
+import React = require("react");
+
export namespace InteractionUtils {
export const MOUSETYPE = "mouse";
export const TOUCHTYPE = "touch";
@@ -90,6 +92,21 @@ export namespace InteractionUtils {
return myTouches;
}
+ // TODO: find a way to reference this function from InkingStroke instead of copy pastign here. copied bc of weird error when on mobile view
+ export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number) {
+ const 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,
+ strokeWidth: width
+ }}
+ />
+ );
+ }
+
export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
console.log(e.button);
switch (type) {
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 9a601e07b..78a5389d2 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -27,6 +27,9 @@ import { listSpec } from "../../new_fields/Schema";
import { List } from "../../new_fields/List";
import { CollectionViewType } from "./collections/CollectionView";
import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu";
+import MobileInterface from "../../mobile/MobileInterface";
+import { MobileInkOverlayContent } from "../../server/Message";
+import MobileInkOverlay from "../../mobile/MobileInkOverlay";
import { RadialMenu } from "./nodes/RadialMenu";
import { SelectionManager } from "../util/SelectionManager";
@@ -56,6 +59,8 @@ export default class GestureOverlay extends Touchable {
@computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); }
@computed private get showBounds() { return this.Tool !== ToolglassTools.None; }
+ @observable private showMobileInkOverlay: boolean = false;
+
private _d1: Doc | undefined;
private _inkToTextDoc: Doc | undefined;
private _thumbDoc: Doc | undefined;
@@ -74,7 +79,7 @@ export default class GestureOverlay extends Touchable {
componentDidMount = () => {
this._thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
- this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc));
+ this._inkToTextDoc = FieldValue(Cast(this._thumbDoc ?.inkToTextDoc, Doc));
}
getNewTouches(e: React.TouchEvent | TouchEvent) {
@@ -141,9 +146,13 @@ export default class GestureOverlay extends Touchable {
const nts = this.getNewTouches(te);
if (nts.nt.length < 5) {
const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY);
+<<<<<<< HEAD
+ target ?.dispatchEvent(
+=======
te.changedTouches.item(0).identifier;
console.log(te.touches);
target?.dispatchEvent(
+>>>>>>> 33d5a12af14e1ed50e5c3164b363fbbc253506a0
new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchStart",
{
bubbles: true,
@@ -327,7 +336,7 @@ export default class GestureOverlay extends Touchable {
if (pt.radiusX > 1 && pt.radiusY > 1) {
for (let j = 0; j < e.targetTouches.length; j++) {
const tPt = e.targetTouches.item(j);
- if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
+ if (tPt ?.screenX === pt ?.screenX && tPt ?.screenY === pt ?.screenY) {
if (pt && this.prevPoints.has(pt.identifier)) {
fingers.push(pt);
}
@@ -350,9 +359,9 @@ export default class GestureOverlay extends Touchable {
else {
console.log("not hand");
}
- this.pointerIdentifier = pointer?.identifier;
+ this.pointerIdentifier = pointer ?.identifier;
runInAction(() => {
- this._pointerY = pointer?.clientY;
+ this._pointerY = pointer ?.clientY;
if (thumb.identifier === this.thumbIdentifier) {
this._thumbX = thumb.clientX;
this._thumbY = thumb.clientY;
@@ -361,7 +370,7 @@ export default class GestureOverlay extends Touchable {
}
});
- this.thumbIdentifier = thumb?.identifier;
+ this.thumbIdentifier = thumb ?.identifier;
this._hands.set(thumb.identifier, fingers);
const others = fingers.filter(f => f !== thumb);
const minX = Math.min(...others.map(f => f.clientX));
@@ -396,7 +405,7 @@ export default class GestureOverlay extends Touchable {
if (pt.radiusX > 1 && pt.radiusY > 1) {
for (let j = 0; j < e.targetTouches.length; j++) {
const tPt = e.targetTouches.item(j);
- if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
+ if (tPt ?.screenX === pt ?.screenX && tPt ?.screenY === pt ?.screenY) {
if (pt && this.prevPoints.has(pt.identifier)) {
this._hands.forEach(hand => hand.some(f => {
if (f.identifier === pt.identifier) {
@@ -409,7 +418,7 @@ export default class GestureOverlay extends Touchable {
}
}
const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
- if (thumb?.identifier && thumb?.identifier === this.thumbIdentifier) {
+ if (thumb ?.identifier && thumb ?.identifier === this.thumbIdentifier) {
this._hands.set(thumb.identifier, fingers);
}
@@ -459,7 +468,7 @@ export default class GestureOverlay extends Touchable {
this._thumbDoc = undefined;
let scriptWorked = false;
- if (NumCast(this._inkToTextDoc?.selectedIndex) > -1) {
+ if (NumCast(this._inkToTextDoc ?.selectedIndex) > -1) {
const selectedButton = this._possibilities[this._selectedIndex];
if (selectedButton) {
selectedButton.props.onClick();
@@ -547,8 +556,8 @@ export default class GestureOverlay extends Touchable {
callbackFn: callback
}
});
- target1?.dispatchEvent(ge);
- target2?.dispatchEvent(ge);
+ target1 ?.dispatchEvent(ge);
+ target2 ?.dispatchEvent(ge);
return actionPerformed;
}
@@ -558,6 +567,16 @@ export default class GestureOverlay extends Touchable {
const B = this.svgBounds;
const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top }));
+ if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) {
+ const { selectedColor, selectedWidth } = InkingControl.Instance;
+ DocServer.Mobile.dispatchGesturePoints({
+ points: this._points,
+ bounds: B,
+ color: selectedColor,
+ width: selectedWidth
+ });
+ }
+
const initialPoint = this._points[0.];
const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height);
const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER);
@@ -575,10 +594,10 @@ export default class GestureOverlay extends Touchable {
const possibilities: string[] = [];
for (const wR of wordResults) {
console.log(wR);
- if (wR?.recognizedText) {
- possibilities.push(wR?.recognizedText)
+ if (wR ?.recognizedText) {
+ possibilities.push(wR ?.recognizedText)
}
- possibilities.push(...wR?.alternates?.map((a: any) => a.recognizedString));
+ possibilities.push(...wR ?.alternates ?.map((a: any) => a.recognizedString));
}
console.log(possibilities);
const r = Math.max(this.svgBounds.right, ...this._strokes.map(s => this.getBounds(s).right));
@@ -637,7 +656,7 @@ export default class GestureOverlay extends Touchable {
dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => {
const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y);
- target?.dispatchEvent(
+ target ?.dispatchEvent(
new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
{
bubbles: true,
@@ -717,10 +736,16 @@ export default class GestureOverlay extends Touchable {
this._clipboardDoc = undefined;
}
+ @action
+ enableMobileInkOverlay = (content: MobileInkOverlayContent) => {
+ this.showMobileInkOverlay = content.enableOverlay;
+ }
+
render() {
trace();
return (
<div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}>
+ {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>}
{this.elements}
<div className="clipboardDoc-cont" style={{
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index f315ce12a..a791eed40 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -29,13 +29,13 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
@computed get PanelHeight() { return this.props.PanelHeight(); }
private analyzeStrokes = () => {
- const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
+ const data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? [];
CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.Document, ["inkAnalysis", "handwriting"], [data]);
}
render() {
TraceMobx();
- const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
+ const data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? [];
const xs = data.map(p => p.X);
const ys = data.map(p => p.Y);
const left = Math.min(...xs);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 997266266..ff6e79836 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -56,6 +56,7 @@ export class MainView extends React.Component {
private _flyoutSizeOnDown = 0;
private _urlState: HistoryUtil.DocUrl;
private _docBtnRef = React.createRef<HTMLDivElement>();
+ private _mainViewRef = React.createRef<HTMLDivElement>();
@observable private _panelWidth: number = 0;
@observable private _panelHeight: number = 0;
@@ -484,7 +485,7 @@ export class MainView extends React.Component {
return new Transform(-translateX, -translateY, 1 / scale);
}
@computed get docButtons() {
- if (CurrentUserUtils.UserDocument?.expandingButtons instanceof Doc) {
+ if (CurrentUserUtils.UserDocument ?.expandingButtons instanceof Doc) {
return <div className="mainView-docButtons" ref={this._docBtnRef}
style={{ height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} >
<MainViewNotifs />
@@ -519,8 +520,16 @@ export class MainView extends React.Component {
return (null);
}
+ get mainViewElement() {
+ return document.getElementById("mainView-container");
+ }
+
+ get mainViewRef() {
+ return this._mainViewRef;
+ }
+
render() {
- return (<div id="mainView-container">
+ return (<div id="mainView-container" ref={this._mainViewRef}>
<DictationOverlay />
<SharingManager />
<SettingsManager />
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index e25a2f5eb..e84b3b0dd 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -73,6 +73,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@undoBatch
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
+ console.log("masronry row drop");
this._createAliasSelected = false;
if (de.complete.docDragData) {
(this.props.parent.Document.dropConverter instanceof ScriptField) &&
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 91c7ca76e..7592712e4 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -227,6 +227,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
+ console.log("DROP STACKIN G2");
const where = [de.x, de.y];
let targInd = -1;
let plusOne = 0;
@@ -257,6 +258,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@undoBatch
@action
onDrop = async (e: React.DragEvent): Promise<void> => {
+ console.log("DROP STACKING");
const where = [e.clientX, e.clientY];
let targInd = -1;
this._docXfs.map((cd, i) => {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 2a9f903bb..c4680fc28 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -63,6 +63,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@undoBatch
columnDrop = action((e: Event, de: DragManager.DropEvent) => {
+ console.log("column drop stacking");
this._createAliasSelected = false;
if (de.complete.docDragData) {
const key = StrCast(this.props.parent.props.Document.sectionFilter);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 12906ee83..c5028d16e 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -6,7 +6,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
import { ScriptField } from "../../../new_fields/ScriptField";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, StrCast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
@@ -53,9 +53,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
private _childLayoutDisposer?: IReactionDisposer;
protected _mainCont?: HTMLDivElement;
protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
- this.dropDisposer?.();
- this.gestureDisposer?.();
- this.multiTouchDisposer?.();
+ this.dropDisposer ?.();
+ this.gestureDisposer ?.();
+ this.multiTouchDisposer ?.();
if (ele) {
this._mainCont = ele;
this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
@@ -68,7 +68,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
componentDidMount() {
- this._childLayoutDisposer = reaction(() => [this.childDocs, (Cast(this.props.Document.childLayout, Doc) as Doc)?.[Id]],
+ this._childLayoutDisposer = reaction(() => [this.childDocs, (Cast(this.props.Document.childLayout, Doc) as Doc) ?.[Id]],
(args) => {
const childLayout = Cast(this.props.Document.childLayout, Doc);
if (childLayout instanceof Doc) {
@@ -173,7 +173,6 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@undoBatch
protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {
-
}
@undoBatch
@@ -196,7 +195,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
const movedDocs = docDragData.draggedDocuments;
added = movedDocs.reduce((added: boolean, d, i) =>
docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) :
- docDragData.moveDocument?.(d, this.props.Document, this.props.addDocument) || added, false);
+ docDragData.moveDocument ?.(d, this.props.Document, this.props.addDocument) || added, false);
} else {
added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
}
@@ -219,6 +218,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
const html = e.dataTransfer.getData("text/html");
const text = e.dataTransfer.getData("text/plain");
+ console.log(html);
if (text && text.startsWith("<div")) {
return;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index dab0ce08e..ab6ac0eaf 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -145,7 +145,7 @@ export class CollectionView extends Touchable<FieldViewProps> {
let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1);
index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1);
- ContextMenu.Instance.clearItems();
+ ContextMenu.Instance && ContextMenu.Instance.clearItems();
if (index !== -1) {
value.splice(index, 1);
return true;
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 073a30330..0811654bf 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -166,7 +166,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
this._viewSpecsOpen = true;
//@ts-ignore
- if (!e.target?.classList[0]?.startsWith("qs")) {
+ if (!e.target ?.classList[0] ?.startsWith("qs")) {
this.closeDatePicker();
}
@@ -315,7 +315,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) {
- this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || []));
+ this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData ?.draggedDocuments || []));
e.stopPropagation();
}
return true;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b2ac94c1..2f50fd710 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -86,7 +86,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@observable private _pullCoords: number[] = [0, 0];
@observable private _pullDirection: string = "";
- public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
+ public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title ?.toString() + ")"; } // 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[])[] = [];
@@ -255,7 +255,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
docs.map(doc => this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc));
}
childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
- childLayouts.map(child => Doc.GetProto(child).clusterStr = child.cluster?.toString());
+ childLayouts.map(child => Doc.GetProto(child).clusterStr = child.cluster ?.toString());
}
}
@@ -434,9 +434,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
console.log("end");
if (this._inkToTextStartX && this._inkToTextStartY) {
const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
- const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color);
+ const setDocs = this.getActiveDocuments().filter(s => s.proto ?.type === "text" && s.color);
const sets = setDocs.map((sd) => {
- return Cast(sd.data, RichTextField)?.Text as string;
+ return Cast(sd.data, RichTextField) ?.Text as string;
});
if (sets.length && sets[0]) {
this._wordPalette.clear();
@@ -523,7 +523,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@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);
+ MarqueeOptionsMenu.Instance && MarqueeOptionsMenu.Instance.fadeOut(true);
let x = this.Document._panX || 0;
let y = this.Document._panY || 0;
@@ -883,8 +883,8 @@ 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 result = this.Document.arrangeScript?.script.run(params, console.log);
- if (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);
@@ -1114,7 +1114,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@computed get placeholder() {
return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}>
- <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
+ <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title ?.toString()}</span>
</div>;
}
@computed get marqueeView() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a69de60df..9182eb4c0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -475,6 +475,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
e.stopPropagation();
+ // TODO: check here for panning/inking
}
return;
}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0d97c3029..213af43c6 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -700,7 +700,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
setTimeout(async () => {
const targetField = Doc.LayoutFieldKey(pdfDoc);
const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations
- targetAnnotations?.push(pdfRegion);
+ targetAnnotations ?.push(pdfRegion);
});
const link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link");
@@ -742,14 +742,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
const rtfField = Cast(this.props.Document._textTemplate || this.dataDoc[fieldKey], RichTextField);
if (this.ProseRef) {
const self = this;
- this._editorView?.destroy();
+ this._editorView ?.destroy();
this._editorView = new EditorView(this.ProseRef, {
- state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
+ state: rtfField ?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config),
handleScrollToSelection: (editorView) => {
const ref = editorView.domAtPos(editorView.state.selection.from);
let refNode = ref.node as any;
while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement;
- const r1 = refNode?.getBoundingClientRect();
+ const r1 = refNode ?.getBoundingClientRect();
const r3 = self._ref.current!.getBoundingClientRect();
if (r1.top < r3.top || r1.top > r3.bottom) {
r1 && (self._scrollRef.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale);
@@ -848,11 +848,11 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
this.tryUpdateHeight();
// see if we need to preserve the insertion point
- const prosediv = this.ProseRef?.children?.[0] as any;
- const keeplocation = prosediv?.keeplocation;
+ const prosediv = this.ProseRef ?.children ?.[0] as any;
+ const keeplocation = prosediv ?.keeplocation;
prosediv && (prosediv.keeplocation = undefined);
- const pos = this._editorView?.state.selection.$from.pos || 1;
- keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+ const pos = this._editorView ?.state.selection.$from.pos || 1;
+ keeplocation && setTimeout(() => this._editorView ?.dispatch(this._editorView ?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
// jump rich text menu to this textbox
const { current } = this._ref;
@@ -876,13 +876,13 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text.
const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY });
const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text)
- if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) {
+ if (pcords && node ?.type === this._editorView!.state.schema.nodes.dashComment) {
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2)));
e.preventDefault();
}
if (!node && this.ProseRef) {
const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div
- if (e.clientY > lastNode?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
+ if (e.clientY > lastNode ?.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document
this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size)));
}
}
@@ -939,7 +939,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
for (let off = 1; off < 100; off++) {
const pos = this._editorView!.posAtCoords({ left: x + off, top: y });
const node = pos && this._editorView!.state.doc.nodeAt(pos.pos);
- if (node?.type === schema.nodes.list_item) {
+ if (node ?.type === schema.nodes.list_item) {
list_node = node;
break;
}
@@ -984,7 +984,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
const self = FormattedTextBox;
return new Plugin({
view(newView) {
- RichTextMenu.Instance.changeView(newView);
+ RichTextMenu.Instance && RichTextMenu.Instance.changeView(newView);
return RichTextMenu.Instance;
}
});
@@ -1021,7 +1021,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
}
if (e.key === "Escape") {
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
- (document.activeElement as any).blur?.();
+ (document.activeElement as any).blur ?.();
SelectionManager.DeselectAll();
}
e.stopPropagation();
@@ -1043,7 +1043,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
@action
tryUpdateHeight(limitHeight?: number) {
- let scrollHeight = this._ref.current?.scrollHeight;
+ let scrollHeight = this._ref.current ?.scrollHeight;
if (!this.layoutDoc.animateToPos && this.layoutDoc._autoHeight && scrollHeight &&
getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
if (limitHeight && scrollHeight > limitHeight) {
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index fbe9bf063..226103a53 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -90,4 +90,19 @@
width: 100%;
margin-right: 10px;
height: 100%;
+}
+
+.touch-iframe-overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ pointer-events: all;
+
+ .indicator {
+ position: absolute;
+
+ &.active {
+ background-color: rgba(0, 0, 0, 0.1);
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index a48dc286e..0b7c3eda9 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -21,6 +21,10 @@ import "./WebBox.scss";
import React = require("react");
import { DocAnnotatableComponent } from "../DocComponent";
import { documentSchema } from "../../../new_fields/documentSchemas";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { DragManager } from "../../util/DragManager";
+import { ImageUtils } from "../../util/Import & Export/ImageUtils";
+import { select } from "async";
library.add(faStickyNote);
@@ -34,6 +38,13 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
@observable private collapsed: boolean = true;
@observable private url: string = "";
+ private _longPressSecondsHack?: NodeJS.Timeout;
+ private _iframeRef = React.createRef<HTMLIFrameElement>();
+ private _iframeIndicatorRef = React.createRef<HTMLDivElement>();
+ private _iframeDragRef = React.createRef<HTMLDivElement>();
+ @observable private _pressX: number = 0;
+ @observable private _pressY: number = 0;
+
componentDidMount() {
const field = Cast(this.props.Document[this.props.fieldKey], WebField);
@@ -49,6 +60,14 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
}
this.setURL();
+
+ document.addEventListener("pointerup", this.onLongPressUp);
+ document.addEventListener("pointermove", this.onLongPressMove);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("pointerup", this.onLongPressUp);
+ document.removeEventListener("pointermove", this.onLongPressMove);
}
@action
@@ -164,6 +183,105 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
}
}
+ onLongPressDown = (e: React.PointerEvent) => {
+ this._pressX = e.clientX;
+ this._pressY = e.clientY;
+
+ // find the pressed element in the iframe (currently only works if its an img)
+ let pressedElement: HTMLElement | undefined;
+ let pressedBound: ClientRect | undefined;
+ let selectedText: string = "";
+ if (this._iframeRef.current) {
+ const B = this._iframeRef.current.getBoundingClientRect();
+ const iframeDoc = this._iframeRef.current.contentDocument;
+ if (B && iframeDoc) {
+ // check if there is selected text
+ const text = iframeDoc.getSelection();
+ if (text && text.toString().length > 0) {
+ selectedText = text.toString();
+
+ // get html of the selected text
+ const range = text.getRangeAt(0);
+ const contents = range.cloneContents();
+ const div = document.createElement("div");
+ div.appendChild(contents);
+ pressedElement = div;
+
+ pressedBound = range.getBoundingClientRect();
+ } else {
+ // TODO: this only works when scale = 1 as it is currently only inteded for mobile upload
+ const element = iframeDoc.elementFromPoint(this._pressX - B.left, this._pressY - B.top);
+ if (element && element.nodeName) {//} === "IMG") {
+ pressedBound = element.getBoundingClientRect();
+ pressedElement = element.cloneNode(true) as HTMLElement;
+ }
+ }
+ }
+ }
+
+ // mark the pressed element
+ if (pressedElement && pressedBound) {
+ if (this._iframeIndicatorRef.current) {
+ this._iframeIndicatorRef.current.style.top = pressedBound.top + "px";
+ this._iframeIndicatorRef.current.style.left = pressedBound.left + "px";
+ this._iframeIndicatorRef.current.style.width = pressedBound.width + "px";
+ this._iframeIndicatorRef.current.style.height = pressedBound.height + "px";
+ this._iframeIndicatorRef.current.classList.add("active");
+ }
+ }
+
+ // start dragging the pressed element if long pressed
+ this._longPressSecondsHack = setTimeout(() => {
+ if (selectedText && pressedBound && pressedElement) {
+ e.stopPropagation();
+ e.preventDefault();
+ // create doc with the selected text's html
+ const doc = Docs.Create.HtmlDocument(pressedElement.innerHTML);
+
+ // create dragging ghost with the selected text
+ if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement);
+
+ // start the drag
+ const dragData = new DragManager.DocumentDragData([doc]);
+ DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX - pressedBound.top, this._pressY - pressedBound.top, { hideSource: true });
+ } else if (pressedElement && pressedBound) {
+ e.stopPropagation();
+ e.preventDefault();
+ if (pressedElement.nodeName === "IMG") {
+ const src = pressedElement.getAttribute("src"); // TODO: may not always work
+ if (src) {
+ const doc = Docs.Create.ImageDocument(src);
+ ImageUtils.ExtractExif(doc);
+
+ // add clone to div so that dragging ghost is placed properly
+ if (this._iframeDragRef.current) this._iframeDragRef.current.appendChild(pressedElement);
+
+ const dragData = new DragManager.DocumentDragData([doc]);
+ DragManager.StartDocumentDrag([pressedElement], dragData, this._pressX, this._pressY, { hideSource: true });
+ }
+ }
+ }
+ }, 1500);
+ }
+
+ onLongPressMove = (e: PointerEvent) => {
+ // this._pressX = e.clientX;
+ // this._pressY = e.clientY;
+ }
+
+ onLongPressUp = (e: PointerEvent) => {
+ if (this._longPressSecondsHack) {
+ clearTimeout(this._longPressSecondsHack);
+ }
+ if (this._iframeIndicatorRef.current) {
+ this._iframeIndicatorRef.current.classList.remove("active");
+ }
+ if (this._iframeDragRef.current) {
+ while (this._iframeDragRef.current.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild);
+ }
+ }
+
+
@computed
get content() {
const field = this.dataDoc[this.props.fieldKey];
@@ -171,9 +289,9 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
if (field instanceof HtmlField) {
view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- view = <iframe src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+ view = <iframe ref={this._iframeRef} src={Utils.CorsProxy(field.url.href)} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
} else {
- view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
+ view = <iframe ref={this._iframeRef} src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%", top: 0 }} />;
}
const content =
<div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
@@ -181,15 +299,23 @@ export class WebBox extends DocAnnotatableComponent<FieldViewProps, WebDocument>
{view}
</div>;
- const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+ const decInteracting = DocumentDecorations.Instance && DocumentDecorations.Instance.Interacting;
+
+ const frozen = !this.props.isSelected() || decInteracting;
- const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+ const classname = "webBox-cont" + (this.props.isSelected() && InkingControl.Instance.selectedTool === InkTool.None && !decInteracting ? "-interactive" : "");
return (
<>
<div className={classname} >
{content}
</div>
- {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
+ {!frozen ? (null) :
+ <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer}>
+ <div className="touch-iframe-overlay" onPointerDown={this.onLongPressDown} >
+ <div className="indicator" ref={this._iframeIndicatorRef}></div>
+ <div className="dragger" ref={this._iframeDragRef}></div>
+ </div>
+ </div>}
</>);
}
render() {
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index 1583e3d5d..5903a2ce9 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -13,6 +13,7 @@ import { observable } from 'mobx';
import { Utils } from '../Utils';
import MobileInterface from './MobileInterface';
import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
+import { Scripting } from '../client/util/Scripting';
@@ -27,12 +28,11 @@ const inputRef = React.createRef<HTMLInputElement>();
@observer
class Uploader extends React.Component {
- @observable
- error: string = "";
- @observable
- status: string = "";
+ @observable error: string = "";
+ @observable status: string = "";
onClick = async () => {
+ console.log("uploader click");
try {
this.status = "initializing protos";
await Docs.Prototypes.initialize();
@@ -47,6 +47,7 @@ class Uploader extends React.Component {
const upload = window.location.origin + "/uploadFormData";
this.status = "uploading image";
+ console.log("uploading image", formData);
const res = await fetch(upload, {
method: 'POST',
body: formData
diff --git a/src/mobile/MobileInkOverlay.scss b/src/mobile/MobileInkOverlay.scss
new file mode 100644
index 000000000..b9c1fb146
--- /dev/null
+++ b/src/mobile/MobileInkOverlay.scss
@@ -0,0 +1,39 @@
+.mobileInkOverlay {
+ border: 10px dashed red;
+ background-color: rgba(0, 0, 0, .05);
+}
+
+.mobileInkOverlay-border {
+ // background-color: rgba(0, 255, 0, .4);
+ position: absolute;
+ pointer-events: auto;
+ cursor: pointer;
+
+ &.top {
+ width: calc(100% + 20px);
+ height: 10px;
+ top: -10px;
+ left: -10px;
+ }
+
+ &.left {
+ width: 10px;
+ height: calc(100% + 20px);
+ top: -10px;
+ left: -10px;
+ }
+
+ &.right {
+ width: 10px;
+ height: calc(100% + 20px);
+ top: -10px;
+ right: -10px;
+ }
+
+ &.bottom {
+ width: calc(100% + 20px);
+ height: 10px;
+ bottom: -10px;
+ left: -10px;
+ }
+} \ No newline at end of file
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
new file mode 100644
index 000000000..f00c77203
--- /dev/null
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -0,0 +1,191 @@
+import React = require('react');
+import { observer } from "mobx-react";
+import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../server/Message";
+import { observable, action } from "mobx";
+import { GestureUtils } from "../pen-gestures/GestureUtils";
+import "./MobileInkOverlay.scss";
+import { StrCast, Cast } from '../new_fields/Types';
+import { DragManager } from "../client/util/DragManager";
+import { DocServer } from '../client/DocServer';
+import { Doc, DocListCastAsync } from '../new_fields/Doc';
+import { listSpec } from '../new_fields/Schema';
+
+
+@observer
+export default class MobileInkOverlay extends React.Component {
+ public static Instance: MobileInkOverlay;
+
+ @observable private _scale: number = 1;
+ @observable private _width: number = 0;
+ @observable private _height: number = 0;
+ @observable private _x: number = -300;
+ @observable private _y: number = -300;
+ @observable private _text: string = "";
+
+ @observable private _offsetX: number = 0;
+ @observable private _offsetY: number = 0;
+ @observable private _isDragging: boolean = false;
+ private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ MobileInkOverlay.Instance = this;
+ }
+
+ initialSize(mobileWidth: number, mobileHeight: number) {
+ const maxWidth = window.innerWidth - 30;
+ const maxHeight = window.innerHeight - 30; // -30 for padding
+ if (mobileWidth > maxWidth || mobileHeight > maxHeight) {
+ const scale = Math.min(maxWidth / mobileWidth, maxHeight / mobileHeight);
+ return { width: mobileWidth * scale, height: mobileHeight * scale, scale: scale };
+ }
+ return { width: mobileWidth, height: mobileHeight, scale: 1 };
+ }
+
+ @action
+ initMobileInkOverlay(content: MobileInkOverlayContent) {
+ const { width, height, text } = content;
+ const scaledSize = this.initialSize(width ? width : 0, height ? height : 0);
+ this._width = scaledSize.width;
+ this._height = scaledSize.height;
+ this._scale = scaledSize.scale;
+ this._x = 300; // TODO: center on screen
+ this._y = 25; // TODO: center on screen
+ this._text = text ? text : "";
+ }
+
+ @action
+ updatePosition(content: UpdateMobileInkOverlayPositionContent) {
+ const { dx, dy, dsize } = content;
+ if (dx) this._x += dx;
+ if (dy) this._y += dy;
+ // TODO: scale dsize
+ }
+
+ drawStroke = (content: GestureContent) => {
+ // TODO: figure out why strokes drawn in corner of mobile interface dont get inserted
+
+ const { points, bounds } = content;
+ console.log("received points", points, bounds);
+
+ const B = {
+ right: (bounds.right * this._scale) + this._x,
+ left: (bounds.left * this._scale) + this._x, // TODO: scale
+ bottom: (bounds.bottom * this._scale) + this._y,
+ top: (bounds.top * this._scale) + this._y, // TODO: scale
+ width: bounds.width * this._scale,
+ height: bounds.height * this._scale,
+ };
+
+ const target = document.elementFromPoint(this._x + 10, this._y + 10);
+ target ?.dispatchEvent(
+ new CustomEvent<GestureUtils.GestureEvent>("dashOnGesture",
+ {
+ bubbles: true,
+ detail: {
+ points: points,
+ gesture: GestureUtils.Gestures.Stroke,
+ bounds: B
+ }
+ }
+ )
+ );
+ }
+
+ uploadDocument = async (content: MobileDocumentUploadContent) => {
+ const { docId } = content;
+ const doc = await DocServer.GetRefField(docId);
+
+ if (doc && doc instanceof Doc) {
+ const target = document.elementFromPoint(this._x + 10, this._y + 10);
+ const dragData = new DragManager.DocumentDragData([doc]);
+ const complete = new DragManager.DragCompleteEvent(false, dragData);
+
+ if (target) {
+ console.log("dispatching upload doc!!!!", target, doc);
+ target.dispatchEvent(
+ new CustomEvent<DragManager.DropEvent>("dashOnDrop",
+ {
+ bubbles: true,
+ detail: {
+ x: this._x,
+ y: this._y,
+ complete: complete,
+ altKey: false,
+ metaKey: false,
+ ctrlKey: false,
+ shiftKey: false
+ }
+ }
+ )
+ );
+ } else {
+ alert("TARGET IS UNDEFINED");
+ }
+ }
+ }
+
+ @action
+ dragStart = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragging);
+ document.removeEventListener("pointerup", this.dragEnd);
+ document.addEventListener("pointermove", this.dragging);
+ document.addEventListener("pointerup", this.dragEnd);
+
+ this._isDragging = true;
+ this._offsetX = e.pageX - this._mainCont.current!.getBoundingClientRect().left;
+ this._offsetY = e.pageY - this._mainCont.current!.getBoundingClientRect().top;
+
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ @action
+ dragging = (e: PointerEvent) => {
+ const x = e.pageX - this._offsetX;
+ const y = e.pageY - this._offsetY;
+
+ // TODO: don't allow drag over library?
+ this._x = Math.min(Math.max(x, 0), window.innerWidth - this._width);
+ this._y = Math.min(Math.max(y, 0), window.innerHeight - this._height);
+
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ @action
+ dragEnd = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragging);
+ document.removeEventListener("pointerup", this.dragEnd);
+
+ this._isDragging = false;
+
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ render() {
+
+ return (
+ <div className="mobileInkOverlay"
+ style={{
+ width: this._width,
+ height: this._height,
+ position: "absolute",
+ transform: `translate(${this._x}px, ${this._y}px)`,
+ zIndex: 30000,
+ pointerEvents: "none",
+ borderStyle: this._isDragging ? "solid" : "dashed",
+ }
+ }
+ ref={this._mainCont}
+ >
+ <p>{this._text}</p>
+ <div className="mobileInkOverlay-border top" onPointerDown={this.dragStart}></div>
+ <div className="mobileInkOverlay-border bottom" onPointerDown={this.dragStart}></div>
+ <div className="mobileInkOverlay-border left" onPointerDown={this.dragStart}></div>
+ <div className="mobileInkOverlay-border right" onPointerDown={this.dragStart}></div>
+ </div >
+ );
+ }
+} \ No newline at end of file
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
new file mode 100644
index 000000000..d0849dbc7
--- /dev/null
+++ b/src/mobile/MobileInterface.scss
@@ -0,0 +1,18 @@
+.mobileInterface-inkInterfaceButtons {
+ position: absolute;
+ top: 0px;
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ z-index: 9999;
+ height: 50px;
+
+ .mobileInterface-button {
+ height: 100%;
+ }
+}
+
+.mobileInterface-container {
+ height: 100%;
+ position: relative;
+} \ No newline at end of file
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index b1eaeaa0a..0c2ed8156 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,40 +1,126 @@
import React = require('react');
import { observer } from 'mobx-react';
-import { computed, action } from 'mobx';
+import { computed, action, observable } 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 { FieldValue, Cast, StrCast } from '../new_fields/Types';
+import { Doc, DocListCast } 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';
+import { faPenNib, faHighlighter, faEraser, faMousePointer, faBreadSlice, faTrash, faCheck, faLongArrowAltLeft } from '@fortawesome/free-solid-svg-icons';
+import { Scripting } from '../client/util/Scripting';
+import { CollectionFreeFormView } from '../client/views/collections/collectionFreeForm/CollectionFreeFormView';
+import GestureOverlay from '../client/views/GestureOverlay';
+import { InkingControl } from '../client/views/InkingControl';
+import { InkTool } from '../new_fields/InkField';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import "./MobileInterface.scss";
+import { SelectionManager } from '../client/util/SelectionManager';
+import { DateField } from '../new_fields/DateField';
+import { GestureUtils } from '../pen-gestures/GestureUtils';
+import { DocServer } from '../client/DocServer';
+import { DocumentDecorations } from '../client/views/DocumentDecorations';
+import { OverlayView } from '../client/views/OverlayView';
+import { DictationOverlay } from '../client/views/DictationOverlay';
+import SharingManager from '../client/util/SharingManager';
+import { PreviewCursor } from '../client/views/PreviewCursor';
+import { ContextMenu } from '../client/views/ContextMenu';
+import { RadialMenu } from '../client/views/nodes/RadialMenu';
+import PDFMenu from '../client/views/pdf/PDFMenu';
+import MarqueeOptionsMenu from '../client/views/collections/collectionFreeForm/MarqueeOptionsMenu';
+import GoogleAuthenticationManager from '../client/apis/GoogleAuthenticationManager';
+import { listSpec } from '../new_fields/Schema';
+import { Id } from '../new_fields/FieldSymbols';
+import { DocumentManager } from '../client/util/DocumentManager';
+import RichTextMenu from '../client/util/RichTextMenu';
+import { WebField } from "../new_fields/URLField";
+import { FieldResult } from "../new_fields/Doc";
+
+library.add(faLongArrowAltLeft);
@observer
export default class MobileInterface extends React.Component {
+ @observable static Instance: MobileInterface;
@computed private get userDoc() { return CurrentUserUtils.UserDocument; }
@computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
+ // @observable private currentView: "main" | "ink" | "upload" = "main";
+ private mainDoc: any = CurrentUserUtils.setupMobileDoc(this.userDoc);
+ @observable private renderView?: () => JSX.Element;
+
+ // private inkDoc?: Doc;
+ public drawingInk: boolean = false;
+
+ // private uploadDoc?: Doc;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ MobileInterface.Instance = this;
+ }
@action
componentDidMount = () => {
library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
if (this.userDoc && !this.mainContainer) {
- const doc = CurrentUserUtils.setupMobileDoc(this.userDoc);
- this.userDoc.activeMobile = doc;
+ this.userDoc.activeMobile = this.mainDoc;
+ }
+ }
+
+ @action
+ switchCurrentView = (doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => {
+ if (!this.userDoc) return;
+
+ this.userDoc.activeMobile = doc(this.userDoc);
+ onSwitch && onSwitch();
+
+ this.renderView = renderView;
+ }
+
+ onSwitchInking = () => {
+ InkingControl.Instance.switchTool(InkTool.Pen);
+ MobileInterface.Instance.drawingInk = true;
+
+ DocServer.Mobile.dispatchOverlayTrigger({
+ enableOverlay: true,
+ width: window.innerWidth,
+ height: window.innerHeight
+ });
+ }
+
+ onSwitchUpload = async () => {
+ let width = 300;
+ let height = 300;
+
+ // get width and height of the collection doc
+ if (this.mainContainer) {
+ const data = Cast(this.mainContainer.data, listSpec(Doc));
+ if (data) {
+ const collectionDoc = await data[1]; // this should be the collection doc since the positions should be locked
+ const docView = DocumentManager.Instance.getDocumentView(collectionDoc);
+ if (docView) {
+ width = docView.nativeWidth ? docView.nativeWidth : 300;
+ height = docView.nativeHeight ? docView.nativeHeight : 300;
+ }
+ }
}
+ DocServer.Mobile.dispatchOverlayTrigger({
+ enableOverlay: true,
+ width: width,
+ height: height,
+ text: "Documents uploaded from mobile will show here",
+ });
}
- @computed
- get mainContent() {
+ renderDefaultContent = () => {
if (this.mainContainer) {
return <DocumentView
Document={this.mainContainer}
DataDoc={undefined}
LibraryPath={emptyPath}
- addDocument={undefined}
+ addDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={emptyFunction}
removeDocument={undefined}
@@ -58,11 +144,195 @@ export default class MobileInterface extends React.Component {
return "hello";
}
+ onBack = (e: React.MouseEvent) => {
+ this.switchCurrentView((userDoc: Doc) => this.mainDoc);
+ InkingControl.Instance.switchTool(InkTool.None); // TODO: switch to previous tool
+
+ DocServer.Mobile.dispatchOverlayTrigger({
+ enableOverlay: false,
+ width: window.innerWidth,
+ height: window.innerHeight
+ });
+
+ // this.inkDoc = undefined;
+ this.drawingInk = false;
+ }
+
+ shiftLeft = (e: React.MouseEvent) => {
+ DocServer.Mobile.dispatchOverlayPositionUpdate({
+ dx: -10
+ });
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ shiftRight = (e: React.MouseEvent) => {
+ DocServer.Mobile.dispatchOverlayPositionUpdate({
+ dx: 10
+ });
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ renderInkingContent = () => {
+ console.log("rendering inking content");
+ // TODO: support panning and zooming
+ // TODO: handle moving of ink strokes
+ if (this.mainContainer) {
+ return (
+ <div className="mobileInterface">
+ <div className="mobileInterface-inkInterfaceButtons">
+ <div className="navButtons">
+ <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing">BACK</button>
+ </div>
+ <div className="inkSettingButtons">
+ <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing"><FontAwesomeIcon icon="long-arrow-alt-left" /></button>
+ </div>
+ <div className="navButtons">
+ <button className="mobileInterface-button" onClick={this.shiftLeft} title="Shift left">left</button>
+ <button className="mobileInterface-button" onClick={this.shiftRight} title="Shift right">right</button>
+ </div>
+ </div>
+ <CollectionView
+ Document={this.mainContainer}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ fieldKey={""}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ PanelHeight={() => window.innerHeight}
+ PanelWidth={() => window.innerWidth}
+ focus={emptyFunction}
+ isSelected={returnFalse}
+ select={emptyFunction}
+ active={returnFalse}
+ ContentScaling={returnOne}
+ whenActiveChanged={returnFalse}
+ ScreenToLocalTransform={Transform.Identity}
+ renderDepth={0}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}>
+ </CollectionView>
+ </div>
+ );
+ }
+ }
+
+ upload = async (e: React.MouseEvent) => {
+ if (this.mainContainer) {
+ const data = Cast(this.mainContainer.data, listSpec(Doc));
+ if (data) {
+ const collectionDoc = await data[1]; // this should be the collection doc since the positions should be locked
+ const children = DocListCast(collectionDoc.data);
+ const uploadDoc = children.length === 1 ? children[0] : Docs.Create.StackingDocument(children, {
+ title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true, _width: 300, _height: 300
+ });
+ if (uploadDoc) {
+ DocServer.Mobile.dispatchMobileDocumentUpload({
+ docId: uploadDoc[Id],
+ });
+ }
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ addWebToCollection = async () => {
+ let url = "https://en.wikipedia.org/wiki/Hedgehog";
+ if (this.mainContainer) {
+ const data = Cast(this.mainContainer.data, listSpec(Doc));
+ if (data) {
+ const webDoc = await data[0];
+ const urlField: FieldResult<WebField> = Cast(webDoc.data, WebField);
+ url = urlField ? urlField.url.toString() : "https://en.wikipedia.org/wiki/Hedgehog";
+
+ }
+ }
+ Docs.Create.WebDocument(url, { _width: 300, _height: 300, title: "Mobile Upload Web Doc" });
+ }
+
+ renderUploadContent() {
+ if (this.mainContainer) {
+ return (
+ <div className="mobileInterface" onDragOver={this.onDragOver}>
+ <div className="mobileInterface-inkInterfaceButtons">
+ <div className="navButtons">
+ <button className="mobileInterface-button cancel" onClick={this.onBack} title="Back">BACK</button>
+ </div>
+ <div className="uploadSettings">
+ {/* <button className="mobileInterface-button" onClick={this.addWeb} title="Add Web Doc to Upload Collection"></button> */}
+ <button className="mobileInterface-button" onClick={this.upload} title="Upload">UPLOAD</button>
+ </div>
+ </div>
+ <DocumentView
+ Document={this.mainContainer}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ removeDocument={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>
+ );
+ }
+ }
+
+ onDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
render() {
+ // const content = this.currentView === "main" ? this.mainContent :
+ // this.currentView === "ink" ? this.inkContent :
+ // this.currentView === "upload" ? this.uploadContent : <></>;
return (
- <div className="mobile-container">
- {this.mainContent}
+ <div className="mobileInterface-container" onDragOver={this.onDragOver}>
+ {/* <DocumentDecorations />
+ <GestureOverlay>
+ {this.renderView ? this.renderView() : this.renderDefaultContent()}
+ </GestureOverlay> */}
+
+ {/* <DictationOverlay />
+ <SharingManager />
+ <GoogleAuthenticationManager /> */}
+ <DocumentDecorations />
+ <GestureOverlay>
+ {this.renderView ? this.renderView() : this.renderDefaultContent()}
+ </GestureOverlay>
+ <PreviewCursor />
+ {/* <ContextMenu /> */}
+ <RadialMenu />
+ <RichTextMenu />
+ {/* <PDFMenu />
+ <MarqueeOptionsMenu />
+ <OverlayView /> */}
</div>
);
}
-} \ No newline at end of file
+}
+
+Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); });
+Scripting.addGlobal(function onSwitchMobileInking() { return MobileInterface.Instance.onSwitchInking(); });
+Scripting.addGlobal(function renderMobileInking() { return MobileInterface.Instance.renderInkingContent(); });
+Scripting.addGlobal(function onSwitchMobileUpload() { return MobileInterface.Instance.onSwitchUpload(); });
+Scripting.addGlobal(function renderMobileUpload() { return MobileInterface.Instance.renderUploadContent(); });
+Scripting.addGlobal(function addWebToMobileUpload() { return MobileInterface.Instance.addWebToCollection(); });
+
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index a92b613b7..e18b6826e 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -42,6 +42,7 @@ export default class UploadManager extends ApiManager {
method: Method.POST,
subscription: "/uploadFormData",
secureHandler: async ({ req, res }) => {
+ console.log("/upload register");
const form = new formidable.IncomingForm();
form.uploadDir = pathToDirectory(Directory.parsed_files);
form.keepExtensions = true;
diff --git a/src/server/DashSession/DashSessionAgent.ts b/src/server/DashSession/DashSessionAgent.ts
index c55e01243..44f77c049 100644
--- a/src/server/DashSession/DashSessionAgent.ts
+++ b/src/server/DashSession/DashSessionAgent.ts
@@ -25,15 +25,18 @@ export class DashSessionAgent extends AppliedSessionAgent {
* The core method invoked when the single master thread is initialized.
* Installs event hooks, repl commands and additional IPC listeners.
*/
- protected async initializeMonitor(monitor: Monitor, sessionKey: string): Promise<void> {
- await this.dispatchSessionPassword(sessionKey);
- monitor.addReplCommand("pull", [], () => monitor.exec("git pull"));
- monitor.addReplCommand("solr", [/start|stop|index/], this.executeSolrCommand);
- monitor.addReplCommand("backup", [], this.backup);
- monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to));
- monitor.on("backup", this.backup);
- monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to));
- monitor.coreHooks.onCrashDetected(this.dispatchCrashReport);
+ // protected async initializeMonitor(monitor: Monitor, sessionKey: string): Promise<void> {
+ protected async initializeMonitor(monitor: Monitor): Promise<string> {
+
+ // await this.dispatchSessionPassword(sessionKey);
+ // monitor.addReplCommand("pull", [], () => monitor.exec("git pull"));
+ // monitor.addReplCommand("solr", [/start|stop|index/], this.executeSolrCommand);
+ // monitor.addReplCommand("backup", [], this.backup);
+ // monitor.addReplCommand("debug", [/\S+\@\S+/], async ([to]) => this.dispatchZippedDebugBackup(to));
+ // monitor.on("backup", this.backup);
+ // monitor.on("debug", async ({ to }) => this.dispatchZippedDebugBackup(to));
+ // monitor.coreHooks.onCrashDetected(this.dispatchCrashReport);
+ return "";
}
/**
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 22d2fa8a8..02ca2ceda 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -1,4 +1,6 @@
import { Utils } from "../Utils";
+import { Point } from "../pen-gestures/ndollar";
+import { Doc } from "../new_fields/Doc";
import { Image } from "canvas";
export class Message<T> {
@@ -43,6 +45,30 @@ export interface Diff extends Reference {
readonly diff: any;
}
+export interface GestureContent {
+ readonly points: Array<Point>;
+ readonly bounds: { right: number, left: number, bottom: number, top: number, width: number, height: number };
+ readonly width?: string;
+ readonly color?: string;
+}
+
+export interface MobileInkOverlayContent {
+ readonly enableOverlay: boolean;
+ readonly width?: number;
+ readonly height?: number;
+ readonly text?: string;
+}
+
+export interface UpdateMobileInkOverlayPositionContent {
+ readonly dx?: number;
+ readonly dy?: number;
+ readonly dsize?: number;
+}
+
+export interface MobileDocumentUploadContent {
+ readonly docId: string;
+}
+
export namespace MessageStore {
export const Foo = new Message<string>("Foo");
export const Bar = new Message<string>("Bar");
@@ -53,6 +79,11 @@ export namespace MessageStore {
export const DeleteAll = new Message<any>("Delete All");
export const ConnectionTerminated = new Message<string>("Connection Terminated");
+ export const GesturePoints = new Message<GestureContent>("Gesture Points");
+ export const MobileInkOverlayTrigger = new Message<MobileInkOverlayContent>("Trigger Mobile Ink Overlay");
+ export const UpdateMobileInkOverlayPosition = new Message<UpdateMobileInkOverlayPositionContent>("Update Mobile Ink Overlay Position");
+ export const MobileDocumentUpload = new Message<MobileDocumentUploadContent>("Upload Document From Mobile");
+
export const GetRefField = new Message<string>("Get Ref Field");
export const GetRefFields = new Message<string[]>("Get Ref Fields");
export const UpdateField = new Message<Diff>("Update Ref Field");
diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts
index f485e1dcd..66f7019a4 100644
--- a/src/server/Websocket/Websocket.ts
+++ b/src/server/Websocket/Websocket.ts
@@ -1,5 +1,5 @@
import { Utils } from "../../Utils";
-import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes } from "../Message";
+import { MessageStore, Transferable, Types, Diff, YoutubeQueryInput, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "../Message";
import { Client } from "../Client";
import { Socket } from "socket.io";
import { Database } from "../database";
@@ -65,6 +65,10 @@ export namespace WebSocket {
Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
Utils.AddServerHandler(socket, MessageStore.DeleteField, id => DeleteField(socket, id));
Utils.AddServerHandler(socket, MessageStore.DeleteFields, ids => DeleteFields(socket, ids));
+ Utils.AddServerHandler(socket, MessageStore.GesturePoints, content => processGesturePoints(socket, content));
+ Utils.AddServerHandler(socket, MessageStore.MobileInkOverlayTrigger, content => processOverlayTrigger(socket, content));
+ Utils.AddServerHandler(socket, MessageStore.UpdateMobileInkOverlayPosition, content => processUpdateOverlayPosition(socket, content));
+ Utils.AddServerHandler(socket, MessageStore.MobileDocumentUpload, content => processMobileDocumentUpload(socket, content));
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
@@ -79,6 +83,22 @@ export namespace WebSocket {
logPort("websocket", socketPort);
}
+ function processGesturePoints(socket: Socket, content: GestureContent) {
+ socket.broadcast.emit("receiveGesturePoints", content);
+ }
+
+ function processOverlayTrigger(socket: Socket, content: MobileInkOverlayContent) {
+ socket.broadcast.emit("receiveOverlayTrigger", content);
+ }
+
+ function processUpdateOverlayPosition(socket: Socket, content: UpdateMobileInkOverlayPositionContent) {
+ socket.broadcast.emit("receiveUpdateOverlayPosition", content);
+ }
+
+ function processMobileDocumentUpload(socket: Socket, content: MobileDocumentUploadContent) {
+ socket.broadcast.emit("receiveMobileDocumentUpload", content);
+ }
+
async function RecognizeImage([query, callback]: [string, (result: any) => any]) {
const path = serverPathToFile(Directory.images, "handwriting.jpg");
imageDataUri.outputFile(query, path).then((savedName: string) => {
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index b0ea2f9ad..a7cc6d3e9 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -14,6 +14,7 @@ import { Utils } from "../../../Utils";
import { nullAudio } from "../../../new_fields/URLField";
import { DragManager } from "../../../client/util/DragManager";
import { InkingControl } from "../../../client/views/InkingControl";
+import { Scripting } from "../../../client/util/Scripting";
import { CollectionViewType } from "../../../client/views/collections/CollectionView";
export class CurrentUserUtils {
@@ -102,6 +103,9 @@ export class CurrentUserUtils {
{ 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 },
+ // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "red", activePen: doc },
+ { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" },
+ // { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" },
];
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,
@@ -146,6 +150,23 @@ export class CurrentUserUtils {
});
}
+ static setupMobileInkingDoc(userDoc: Doc) {
+ return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" });
+ }
+
+ static setupMobileUploadDoc(userDoc: Doc) {
+ // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
+ const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
+ title: "Upload Images From the Web", _chromeStatus: "enabled", lockedPosition: true
+ });
+ const uploadDoc = Docs.Create.StackingDocument([], {
+ title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true
+ });
+ return Docs.Create.StackingDocument([webDoc, uploadDoc], {
+ _width: screen.width, lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray"
+ });
+ }
+
// 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
@@ -383,3 +404,6 @@ export class CurrentUserUtils {
return recurs([] as Attribute[], schema ? schema.rootAttributeGroup : undefined);
}
}
+
+Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); });
+Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); });
diff --git a/src/server/index.ts b/src/server/index.ts
index 313a2f0e2..55ba71dba 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -141,7 +141,7 @@ export async function launchServer() {
* So, the 'else' clause is exactly what we've always run when executing npm start.
*/
if (process.env.RELEASE) {
- (sessionAgent = new DashSessionAgent()).launch();
+ // (sessionAgent = new DashSessionAgent()).launch();
} else {
launchServer();
}
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 9f67c1dda..bb661a124 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -42,18 +42,18 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
}
};
app.use(cors(corsOptions));
- app.use("*", ({ user, originalUrl }, res, next) => {
- if (user && !originalUrl.includes("Heartbeat")) {
- const userEmail = (user as any).email;
- if (userEmail) {
- timeMap[userEmail] = Date.now();
- }
- }
- if (!user && originalUrl === "/") {
- return res.redirect("/login");
- }
- next();
- });
+ // app.use("*", ({ user, originalUrl }, res, next) => {
+ // if (user && !originalUrl.includes("Heartbeat")) {
+ // const userEmail = (user as any).email;
+ // if (userEmail) {
+ // timeMap[userEmail] = Date.now();
+ // }
+ // }
+ // if (!user && originalUrl === "/") {
+ // return res.redirect("/login");
+ // }
+ // next();
+ // });
app.use(wdm(compiler, { publicPath: config.output.publicPath }));
app.use(whm(compiler));