diff options
Diffstat (limited to 'src/client')
20 files changed, 691 insertions, 365 deletions
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); } |
