From cef6852d597ce67637466afb36c3498dc84211f6 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Wed, 8 Jan 2020 19:48:28 -0500 Subject: gestures are now overlayed and can span collections/panes! --- .../views/collections/CollectionStackingView.tsx | 6 +- src/client/views/collections/CollectionSubView.tsx | 13 +- .../views/collections/CollectionTreeView.scss | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 171 +++++++-------------- 4 files changed, 74 insertions(+), 122 deletions(-) (limited to 'src/client/views/collections') 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(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent(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() { @@ -129,6 +133,11 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { } } + @undoBatch + protected onGesture(e: Event, ge: GestureUtils.GestureEvent) { + + } + @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { 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..9656debf3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -272,11 +272,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 +285,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 +333,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 +353,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); @@ -473,11 +446,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } 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(); @@ -508,10 +477,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(); @@ -942,34 +907,10 @@ 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 ( - - {CreatePolyline(this._points, B.left, B.top)} - - ); - } - children = () => { const eles: JSX.Element[] = []; this.extensionDoc && (eles.push(...this.childViews())); - this.currentStroke && (eles.push(this.currentStroke)); + // this.currentStroke && (eles.push(this.currentStroke)); eles.push(); return eles; } @@ -998,7 +939,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
{!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) ? -- cgit v1.2.3-70-g09d2 From 36eed6cc3a3387a1f7755a848aea4e19e2645e14 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Thu, 9 Jan 2020 19:55:52 -0500 Subject: some stuff that ive played around with today. mobile interface, five fingers, other stuff --- package-lock.json | 27 +++++++++ package.json | 1 + src/client/views/GestureOverlay.scss | 2 +- src/client/views/GestureOverlay.tsx | 69 +++++++++++++--------- src/client/views/InkingControl.tsx | 21 ++++--- src/client/views/MainView.tsx | 4 +- src/client/views/Palette.scss | 18 ++++++ src/client/views/Palette.tsx | 25 ++++++++ src/client/views/Touchable.tsx | 18 ++++++ .../views/collections/CollectionSchemaView.scss | 14 +++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 54 +++++++++++++++++ src/client/views/nodes/DocumentView.tsx | 17 +----- src/mobile/ImageUpload.tsx | 31 +++++++--- src/mobile/MobileInterface.tsx | 69 ++++++++++++++++++++++ src/new_fields/InkField.ts | 3 +- src/pen-gestures/GestureUtils.ts | 3 +- .../authentication/models/current_user_utils.ts | 26 ++++++++ 17 files changed, 335 insertions(+), 67 deletions(-) create mode 100644 src/client/views/Palette.scss create mode 100644 src/client/views/Palette.tsx create mode 100644 src/mobile/MobileInterface.tsx (limited to 'src/client/views/collections') diff --git a/package-lock.json b/package-lock.json index d807d75a1..244b2e7e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2040,6 +2040,11 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "avl": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/avl/-/avl-1.4.4.tgz", + "integrity": "sha512-vMjF4jhT2N0naO1fsBTA/lZ2AB70ECBv41na7oBe8B5PCgMVvNrWG+VRKMlbG5Mk5UGbMxzUD9fXK/IBsmmO5A==" + }, "awesome-typescript-loader": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.2.1.tgz", @@ -2314,6 +2319,14 @@ "tweetnacl": "^0.14.3" } }, + "bentley-ottman-sweepline": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/bentley-ottman-sweepline/-/bentley-ottman-sweepline-0.1.4.tgz", + "integrity": "sha512-Qptw9uoNLM0zLiJfA+9PqFrF5b6pMiJNyoWgqONq4/wVml4NC2N8S4RXYXn4xXR4JEBhHF+euVwwML7D9X1LqA==", + "requires": { + "avl": "^1.4.0" + } + }, "better-assert": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", @@ -4316,6 +4329,14 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emit-logger": { + "version": "github:chocolateboy/emit-logger#b9d25a2d939e42f29c940861e9648bd0fb810070", + "from": "github:chocolateboy/emit-logger#better-emitter-name", + "requires": { + "chalk": "^1.1.1", + "moment": "^2.10.6" + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -7065,6 +7086,7 @@ "babel-runtime": "^6.22.0", "bluebird": "^3.4.7", "boolify-string": "^2.0.2", + "emit-logger": "github:chocolateboy/emit-logger#better-emitter-name", "lodash": "^4.17.4", "semver": "^5.0.3", "source-map-support": "^0.5.9" @@ -8545,6 +8567,11 @@ } } }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "mongodb": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.2.7.tgz", diff --git a/package.json b/package.json index 9bd2273f8..2a72fac80 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "async": "^2.6.2", "babel-runtime": "^6.26.0", "bcrypt-nodejs": "0.0.3", + "bentley-ottman-sweepline": "^0.1.4", "bluebird": "^3.5.3", "body-parser": "^1.18.3", "bootstrap": "^4.3.1", diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index cbc1b6e7d..31601efd4 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -1,6 +1,6 @@ .gestureOverlay-cont { width: 100vw; - height: 100vw; + height: 100vh; position: absolute; top: 0; left: 0; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index c88e3f7ae..848927912 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -43,9 +43,43 @@ export default class GestureOverlay extends Touchable { @action onPointerMove = (e: PointerEvent) => { - this._points.push({ X: e.clientX, Y: e.clientY }); - e.stopPropagation(); - e.preventDefault(); + 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("dashOnGesture", + { + bubbles: true, + detail: { + points: this._points, + gesture: GestureUtils.Gestures.Line, + bounds: B, + callbackFn: callback + } + }); + target1?.dispatchEvent(ge); + target2?.dispatchEvent(ge); + return actionPerformed; } @action @@ -72,31 +106,10 @@ export default class GestureOverlay extends Touchable { actionPerformed = true; break; case GestureUtils.Gestures.Line: - 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("dashOnGesture", - { - bubbles: true, - detail: { - points: this._points, - gesture: GestureUtils.Gestures.Line, - bounds: B, - callbackFn: callback - } - }) - target1?.dispatchEvent(ge); - target2?.dispatchEvent(ge); + actionPerformed = this.handleLineGesture(); + break; + case GestureUtils.Gestures.Scribble: + console.log("scribble"); break; } if (actionPerformed) { diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index e33f193b8..243123352 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -4,19 +4,18 @@ 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"; - 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 FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; } + @computed private get _selectedWidth(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; } @observable public _open: boolean = false; constructor() { @@ -24,7 +23,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 +36,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 +99,8 @@ export class InkingControl { }); @action switchWidth = (width: string): void => { - this._selectedWidth = width; + // this._selectedWidth = width; + CurrentUserUtils.UserDocument.inkWidth = width; } @computed @@ -114,7 +115,8 @@ export class InkingControl { @action updateSelectedColor(value: string) { - this._selectedColor = value; + // this._selectedColor = value; + CurrentUserUtils.UserDocument.inkColor = value; } @computed @@ -127,6 +129,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/MainView.tsx b/src/client/views/MainView.tsx index 14c1803c0..f463ca306 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'; @@ -135,6 +135,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(); } diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss new file mode 100644 index 000000000..60004c81f --- /dev/null +++ b/src/client/views/Palette.scss @@ -0,0 +1,18 @@ +.palette-container { + .palette-thumb { + width: 300px; + height: 300px; + touch-action: pan-x; + overflow: scroll; + + .palette-thumbContent { + width: 100%; + height: 100%; + + .palette-button { + width: 100px; + height: 100px; + } + } + } +} \ 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..390b7e2c2 --- /dev/null +++ b/src/client/views/Palette.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import "./Palette.scss"; +import { PointData } from "../../new_fields/InkField"; + +export interface PaletteProps { + x: number; + y: number; + thumb: number[]; +} + +export default class Palette extends React.Component { + render() { + return ( +
+
+
+
console.log("hi")}>1
+
2
+
3
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 251cd41e5..3eb66ff72 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'; const HOLD_DURATION = 1000; @@ -34,11 +35,19 @@ export abstract class Touchable extends React.Component { 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; } } } @@ -54,7 +63,9 @@ export abstract class Touchable extends React.Component { if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; this._touchDrag = true; if (this.holdTimer) { + console.log("clear"); clearTimeout(this.holdTimer); + this.holdTimer = undefined; } switch (myTouches.length) { case 1: @@ -88,7 +99,9 @@ export abstract class Touchable extends React.Component { } } if (this.holdTimer) { + console.log("clear"); clearTimeout(this.holdTimer); + this.holdTimer = undefined; } this._touchDrag = false; e.stopPropagation(); @@ -137,4 +150,9 @@ export abstract class Touchable extends React.Component { e.stopPropagation(); e.preventDefault(); } + + 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/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9656debf3..d7cccc036 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -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); @@ -444,6 +445,9 @@ 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.None) { @@ -844,6 +848,53 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], inkData); } + private thumbIdentifier?: number; + private hand?: React.Touch[]; + + @action + handleHandDown = (e: React.TouchEvent) => { + const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); + this.hand = fingers; + 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); + this._palette = ; + + 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) => { + console.log(e.changedTouches.length); + this.onTouchEnd(e); + if (this.prevPoints.size < 3) { + if (this.hand) { + for (const h of this.hand) { + this.prevPoints.has(h.identifier) && this.prevPoints.delete(h.identifier); + } + } + this._palette = undefined; + document.removeEventListener("touchend", this.handleHandUp); + } + } + onContextMenu = (e: React.MouseEvent) => { const layoutItems: ContextMenuProps[] = []; @@ -907,9 +958,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ]; } + @observable private _palette?: JSX.Element; + children = () => { const eles: JSX.Element[] = []; this.extensionDoc && (eles.push(...this.childViews())); + this._palette && (eles.push(this._palette)); // this.currentStroke && (eles.push(this.currentStroke)); eles.push(); return eles; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7d6b8bf01..415d0e2ef 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -256,7 +256,6 @@ export class DocumentView extends DocComponent(Docu document.removeEventListener("touchend", this.onTouchEnd); document.addEventListener("touchend", this.onTouchEnd); if ((e.nativeEvent as any).formattedHandled) e.stopPropagation(); - console.log("down") } } @@ -327,6 +326,7 @@ export class DocumentView extends DocComponent(Docu 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; } @@ -357,8 +357,6 @@ export class DocumentView extends DocComponent(Docu 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(); } @@ -374,19 +372,7 @@ export class DocumentView extends DocComponent(Docu } return; } - // if (!InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE)) { - // 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; - // } - this._downX = e.clientX; this._downY = e.clientY; this._hitTemplateDrag = false; @@ -408,6 +394,7 @@ export class DocumentView extends DocComponent(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) } diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index ec698b151..0b0280519 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -11,6 +11,8 @@ import { List } from '../new_fields/List'; import { observer } from 'mobx-react'; import { observable } from 'mobx'; import { Utils } from '../Utils'; +import MobileInterface from './MobileInterface'; +import { CurrentUserUtils } from '../server/authentication/models/current_user_utils'; @@ -104,10 +106,25 @@ class Uploader extends React.Component { } -DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload"); - -ReactDOM.render(( - -), - document.getElementById('root') -); \ No newline at end of file +// DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload"); +(async () => { + const info = await CurrentUserUtils.loadCurrentUser(); + DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + "mobile"); + await Docs.Prototypes.initialize(); + if (info.id !== "__guest__") { + // a guest will not have an id registered + await CurrentUserUtils.loadUserDocument(info); + } + document.getElementById('root')!.addEventListener('wheel', event => { + if (event.ctrlKey) { + event.preventDefault(); + } + }, true); + ReactDOM.render(( + // + + ), + document.getElementById('root') + ); +} +)(); \ No newline at end of file diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx new file mode 100644 index 000000000..ea2fc917f --- /dev/null +++ b/src/mobile/MobileInterface.tsx @@ -0,0 +1,69 @@ +import React = require('react'); +import { observer } from 'mobx-react'; +import { computed, action } from 'mobx'; +import { CurrentUserUtils } from '../server/authentication/models/current_user_utils'; +import { FieldValue, Cast } from '../new_fields/Types'; +import { Doc } from '../new_fields/Doc'; +import { Docs } from '../client/documents/Documents'; +import { CollectionView } from '../client/views/collections/CollectionView'; +import { DocumentView } from '../client/views/nodes/DocumentView'; +import { emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from '../Utils'; +import { Transform } from '../client/util/Transform'; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faPenNib, faHighlighter, faEraser, faMousePointer } from '@fortawesome/free-solid-svg-icons'; + +@observer +export default class MobileInterface extends React.Component { + @computed private get userDoc() { return CurrentUserUtils.UserDocument; } + @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; } + + @action + componentDidMount = () => { + library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]); + + if (this.userDoc && !this.mainContainer) { + const doc = CurrentUserUtils.setupMobileDoc(this.userDoc); + this.userDoc.activeMobile = doc; + } + } + + @computed + get mainContent() { + if (this.mainContainer) { + return 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}> + ; + } + return "hello"; + } + + render() { + return ( +
+ {this.mainContent} +
+ ); + } +} \ No newline at end of file diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts index 83d631958..20dbf4b17 100644 --- a/src/new_fields/InkField.ts +++ b/src/new_fields/InkField.ts @@ -9,7 +9,8 @@ export enum InkTool { Pen, Highlighter, Eraser, - Scrubber + Scrubber, + Stamp } export interface PointData { diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 4edcfa623..062604458 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -37,7 +37,8 @@ export namespace GestureUtils { export enum Gestures { Box = "box", Line = "line", - Stroke = "stroke" + Stroke = "stroke", + Scribble = "scribble" } export const GestureRecognizer = new NDollarRecognizer(false); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 220c37e2b..71dd34e68 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -28,6 +28,7 @@ export class CurrentUserUtils { @observable public static GuestTarget: Doc | undefined; @observable public static GuestWorkspace: Doc | undefined; + @observable public static GuestMobile: Doc | undefined; // a default set of note types .. not being used yet... static setupNoteTypes(doc: Doc) { @@ -55,8 +56,10 @@ export class CurrentUserUtils { { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' }, { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })' }, { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' }, + { title: "mobile view", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' }, { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc }, { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc }, { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc }, @@ -90,6 +93,29 @@ export class CurrentUserUtils { } } + static setupMobileButtons(doc: Doc, buttons?: string[]) { + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ + { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" }, + { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc }, + { title: "use scrubber", icon: "eraser", click: 'activateScrubber(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "green", activePen: doc }, + { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', ischecked: `sameDocs(this.activePen.pen, this)`, backgroundColor: "white", activePen: doc }, + ]; + return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({ + nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, + onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, + ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, + backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, + })); + } + + static setupMobileDoc(userDoc: Doc) { + return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { + columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5 + }); + } + // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. when clicked, this panel will be displayed in the target container (ie, sidebarContainer) static setupToolsPanel(sidebarContainer: Doc, doc: Doc) { // setup a masonry view of all he creators -- cgit v1.2.3-70-g09d2 From d86b4db095379d473820271868e8f7cd5830d502 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Mon, 13 Jan 2020 15:25:07 -0500 Subject: palette events are now on gestureOverlay, but cffv and dv is still capturing one and two finger events, which makes it impossible for the gestureoverlay to capture 5 fingers spread across different targets --- src/client/documents/Documents.ts | 3 + src/client/util/InteractionUtils.ts | 10 ++- src/client/views/GestureOverlay.tsx | 64 ++++++++++++++- src/client/views/InkingControl.tsx | 5 +- src/client/views/Palette.scss | 10 ++- src/client/views/Palette.tsx | 44 +++++++++- src/client/views/Touchable.tsx | 21 +++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 94 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 24 +++++- src/new_fields/documentSchemas.ts | 2 + .../authentication/models/current_user_utils.ts | 23 ++++++ 11 files changed, 224 insertions(+), 76 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1853be529..64abd4f57 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -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; + pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown } class EmptyBox { 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): React.Touch[] { + export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { const myTouches = new Array(); 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/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 848927912..830c06f1f 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; import "./GestureOverlay.scss" -import { computed, observable, action } from "mobx"; +import { computed, observable, action, runInAction } from "mobx"; import { CreatePolyline } from "./InkingStroke"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; @@ -12,14 +12,22 @@ 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); @@ -27,6 +35,47 @@ export default class GestureOverlay extends Touchable { 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 = ; + } + + 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)) { @@ -82,6 +131,8 @@ export default class GestureOverlay extends Touchable { return actionPerformed; } + + @action onPointerUp = (e: PointerEvent) => { if (this._points.length > 1) { @@ -157,16 +208,21 @@ export default class GestureOverlay extends Touchable { return ( - {CreatePolyline(this._points, B.left, B.top)} + {CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} ); } render() { return ( -
+
{this.props.children} + {this._palette} {this.currentStroke}
); } -} \ No newline at end of file +} + +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/InkingControl.tsx b/src/client/views/InkingControl.tsx index 243123352..be07a9024 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -10,12 +10,13 @@ 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; @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(CurrentUserUtils.UserDocument.inkTool)) ?? InkTool.None; } - @computed private get _selectedColor(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; } - @computed private get _selectedWidth(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; } + @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() { diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss index 60004c81f..2626774cb 100644 --- a/src/client/views/Palette.scss +++ b/src/client/views/Palette.scss @@ -4,15 +4,17 @@ height: 300px; touch-action: pan-x; overflow: scroll; + position: absolute; .palette-thumbContent { width: 100%; height: 100%; + } - .palette-button { - width: 100px; - height: 100px; - } + .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 index 390b7e2c2..3649cccfe 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -1,22 +1,60 @@ 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 { render() { return (
-
console.log("hi")}>1
-
2
-
3
+ 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}> +
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 3eb66ff72..24ea801a0 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -21,15 +21,26 @@ export abstract class Touchable extends React.Component { */ @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: @@ -57,13 +68,12 @@ export abstract class Touchable extends React.Component { */ @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; this._touchDrag = true; if (this.holdTimer) { - console.log("clear"); clearTimeout(this.holdTimer); this.holdTimer = undefined; } @@ -99,7 +109,6 @@ export abstract class Touchable extends React.Component { } } if (this.holdTimer) { - console.log("clear"); clearTimeout(this.holdTimer); this.holdTimer = undefined; } @@ -152,7 +161,7 @@ export abstract class Touchable extends React.Component { } handleHandDown = (e: React.TouchEvent) => { - e.stopPropagation(); - e.preventDefault(); + // e.stopPropagation(); + // e.preventDefault(); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d7cccc036..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"; @@ -468,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) { @@ -490,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]; @@ -849,51 +849,47 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } private thumbIdentifier?: number; - private hand?: React.Touch[]; - @action - handleHandDown = (e: React.TouchEvent) => { - const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); - this.hand = fingers; - 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); - this._palette = ; - - 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) => { - console.log(e.changedTouches.length); - this.onTouchEnd(e); - if (this.prevPoints.size < 3) { - if (this.hand) { - for (const h of this.hand) { - this.prevPoints.has(h.identifier) && this.prevPoints.delete(h.identifier); - } - } - this._palette = undefined; - document.removeEventListener("touchend", this.handleHandUp); - } - } + // @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 = ; + // } + + // 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[] = []; @@ -958,12 +954,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ]; } - @observable private _palette?: JSX.Element; + // @observable private _palette?: JSX.Element; children = () => { const eles: JSX.Element[] = []; this.extensionDoc && (eles.push(...this.childViews())); - this._palette && (eles.push(this._palette)); + // this._palette && (eles.push(this._palette)); // this.currentStroke && (eles.push(this.currentStroke)); eles.push(); return eles; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 415d0e2ef..bdec94eb3 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -59,6 +59,8 @@ export interface DocumentViewProps { LibraryPath: Doc[]; fitToBox?: boolean; onClick?: ScriptField; + onPointerDown?: ScriptField; + onPointerUp?: ScriptField; dragDivName?: string; addDocument?: (doc: Doc) => boolean; removeDocument?: (doc: Doc) => boolean; @@ -105,6 +107,8 @@ export class DocumentView extends DocComponent(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; } @action componentDidMount() { @@ -180,7 +184,7 @@ export class DocumentView extends DocComponent(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(); @@ -240,8 +244,9 @@ export class DocumentView extends DocComponent(Docu } handle1PointerDown = (e: React.TouchEvent) => { + if (this.Document.onPointerDown) return; if (!e.nativeEvent.cancelBubble) { - const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0]; + const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0]; this._downX = touch.clientX; this._downY = touch.clientY; this._hitTemplateDrag = false; @@ -265,7 +270,7 @@ export class DocumentView extends DocComponent(Docu 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]; + 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); @@ -293,7 +298,7 @@ export class DocumentView extends DocComponent(Docu @action handle2PointersMove = (e: TouchEvent) => { - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); + const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); const pt1 = myTouches[0]; const pt2 = myTouches[1]; const oldPoint1 = this.prevPoints.get(pt1.identifier); @@ -363,6 +368,12 @@ export class DocumentView extends DocComponent(Docu } 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) @@ -412,6 +423,11 @@ export class DocumentView extends DocComponent(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); diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index 21e69fbed..1cba3cba9 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -20,6 +20,8 @@ export const documentSchema = createSchema({ dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 71dd34e68..2ade6f102 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -110,6 +110,29 @@ export class CurrentUserUtils { })); } + static setupThumbButtons(doc: Doc) { + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ + { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + ]; + return docProtoData.map(data => Docs.Create.FontIconDocument({ + nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, + onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, + onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, + ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true, + backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, + })); + } + + static setupThumbDoc(userDoc: Doc) { + if (!userDoc.thumbDoc) { + return Docs.Create.MasonryDocument(CurrentUserUtils.setupThumbButtons(userDoc), { + width: 300, columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5 + }); + } + return userDoc.thumbDoc; + } + static setupMobileDoc(userDoc: Doc) { return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5 -- cgit v1.2.3-70-g09d2 From de0c00ff0bb1f58a0736da24acc984f5a090d009 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 14 Jan 2020 18:35:28 -0500 Subject: ok we now use custom events for touch. im struggling with figuring out how to ignore a hand lol --- src/client/util/InteractionUtils.ts | 50 ++++++- src/client/views/CollectionLinearView.tsx | 4 +- src/client/views/GestureOverlay.tsx | 160 +++++++++++++++++++-- src/client/views/Touchable.tsx | 114 ++++++++++----- .../views/collections/CollectionStackingView.tsx | 6 +- src/client/views/collections/CollectionSubView.tsx | 8 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 80 ++++++----- src/client/views/nodes/DocumentView.tsx | 32 +++-- src/pen-gestures/GestureUtils.ts | 2 +- 9 files changed, 344 insertions(+), 112 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts index 76b43da3c..63ee2fb92 100644 --- a/src/client/util/InteractionUtils.ts +++ b/src/client/util/InteractionUtils.ts @@ -8,13 +8,53 @@ export namespace InteractionUtils { const REACT_POINTER_PEN_BUTTON = 0; const ERASER_BUTTON = 5; + export class MultiTouchEvent { + constructor( + readonly fingers: number, + readonly points: T extends React.TouchEvent ? React.TouchList : TouchList, + readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent + ) { } + } + + export interface MultiTouchEventDisposer { (): void; } + + export function MakeMultiTouchTarget( + element: HTMLElement, + startFunc: (e: Event, me: MultiTouchEvent) => void, + ): MultiTouchEventDisposer { + const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent>).detail); + // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent>).detail) : undefined; + // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent>).detail) : undefined; + element.addEventListener("dashOnTouchStart", onMultiTouchStartHandler); + // if (onMultiTouchMoveHandler) { + // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler); + // } + // if (onMultiTouchEndHandler) { + // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler); + // } + return () => { + element.removeEventListener("dashOnTouchStart", onMultiTouchStartHandler); + // if (onMultiTouchMoveHandler) { + // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler); + // } + // if (onMultiTouchEndHandler) { + // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler); + // } + }; + } + export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { const myTouches = new Array(); - for (let i = 0; i < e.targetTouches.length; i++) { - const pt: any = e.targetTouches.item(i); - if (pt && prevPoints.has(pt.identifier)) { - if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { - myTouches.push(pt); + for (let i = 0; i < e.touches.length; i++) { + const pt: any = e.touches.item(i); + if (ignorePen || (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 (pt && prevPoints.has(pt.identifier)) { + myTouches.push(pt); + } + } } } } diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 5d6a58656..38f2decdd 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 createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (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
-
+
this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} /> diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 830c06f1f..a5fba362c 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,7 +1,7 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; -import "./GestureOverlay.scss" +import "./GestureOverlay.scss"; import { computed, observable, action, runInAction } from "mobx"; import { CreatePolyline } from "./InkingStroke"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; @@ -16,6 +16,7 @@ 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"; +import { Utils } from "../../Utils"; @observer export default class GestureOverlay extends Touchable { @@ -28,6 +29,9 @@ export default class GestureOverlay extends Touchable { private _d1: Doc | undefined; private thumbIdentifier?: number; + private _hands: (React.Touch[])[] = []; + + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; constructor(props: Readonly<{}>) { super(props); @@ -35,11 +39,143 @@ export default class GestureOverlay extends Touchable { GestureOverlay.Instance = this; } - @action + getNewTouches(e: React.TouchEvent | TouchEvent) { + const ntt: (React.Touch | Touch)[] = []; + const nct: (React.Touch | Touch)[] = []; + const nt: (React.Touch | Touch)[] = []; + this._hands.forEach((hand) => { + for (let i = 0; i < e.targetTouches.length; i++) { + const pt = e.targetTouches.item(i); + if (pt && !hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + ntt.push(pt); + } + } + + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.changedTouches.item(i); + if (pt && !hand.some((finger: React.Touch) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + nct.push(pt); + } + } + + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.touches.item(i); + if (pt && !hand.some((finger: React.Touch) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + nt.push(pt); + } + } + }); + return { ntt, nct, nt }; + } + + onReactTouchStart = (te: React.TouchEvent) => { + const actualPts: React.Touch[] = []; + for (let i = 0; i < te.touches.length; i++) { + const pt: any = te.touches.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 > 1 && pt.radiusY > 1) { + // if (typeof pt.identifier !== "string") { + // pt.identifier = Utils.GenerateGuid(); + // } + 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 && this.prevPoints.size < 5) { + const nts: any = this.getNewTouches(te); + const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); + target?.dispatchEvent( + new CustomEvent>("dashOnTouchStart", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + points: te.targetTouches, + touchEvent: new React.TouchEvent("start", { + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct + }); + } + } + ) + ); + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + document.addEventListener("touchmove", this.onReactTouchMove); + document.addEventListener("touchend", this.onReactTouchEnd); + } + else { + this.handleHandDown(te); + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + } + } + + onReactTouchMove = (e: TouchEvent) => { + const nts: any = this.getNewTouches(e); + document.dispatchEvent( + new CustomEvent>("dashOnTouchMove", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + points: e.touches, + touchEvent: new TouchEvent("move", { + targetTouches: nts.ntt, + changedTouches: nts.nct, + touches: nts.nt, + }) + } + }) + ); + } + + onReactTouchEnd = (e: TouchEvent) => { + document.dispatchEvent( + new CustomEvent>("dashOnTouchEnd", + { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + points: e.touches, + touchEvent: e + } + }) + ); + for (let i = 0; i < e.changedTouches.length; i++) { + const pt = e.changedTouches.item(i); + if (pt) { + if (this.prevPoints.has(pt.identifier)) { + this.prevPoints.delete(pt.identifier); + } + } + } + + if (this.prevPoints.size === 0) { + document.removeEventListener("touchmove", this.onReactTouchMove); + document.removeEventListener("touchend", this.onReactTouchEnd); + } + e.stopPropagation(); + } + 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; + fingers.forEach((f) => this.prevPoints.delete(f.identifier)); + this._hands.push(fingers); 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)); @@ -48,10 +184,12 @@ export default class GestureOverlay extends Touchable { const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc)); if (thumbDoc) { - this._palette = ; + runInAction(() => { + this._palette = ; + }); } - document.removeEventListener("touchmove", this.onTouch); + this.removeMoveListeners(); document.removeEventListener("touchmove", this.handleHandMove); document.addEventListener("touchmove", this.handleHandMove); document.removeEventListener("touchend", this.handleHandUp); @@ -69,11 +207,9 @@ export default class GestureOverlay extends Touchable { @action handleHandUp = (e: TouchEvent) => { - this.onTouchEnd(e); - if (this.prevPoints.size < 3) { - this._palette = undefined; - document.removeEventListener("touchend", this.handleHandUp); - } + // this.onTouchEnd(e); + this._palette = undefined; + document.removeEventListener("touchend", this.handleHandUp); } @action @@ -115,7 +251,7 @@ export default class GestureOverlay extends Touchable { DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }); actionPerformed = true; } - } + }; const ge = new CustomEvent("dashOnGesture", { bubbles: true, @@ -181,7 +317,7 @@ export default class GestureOverlay extends Touchable { } } ) - ) + ); this._points = []; } } @@ -215,7 +351,7 @@ export default class GestureOverlay extends Touchable { render() { return ( -
+
{this.props.children} {this._palette} {this.currentStroke} diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 2a7599fbf..a71015b05 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -8,8 +8,11 @@ const HOLD_DURATION = 1000; export abstract class Touchable extends React.Component { //private holdTimer: NodeJS.Timeout | undefined; - holdTimer: NodeJS.Timeout | undefined; + private holdTimer: NodeJS.Timeout | undefined; + private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; + private endDisposer?: InteractionUtils.MultiTouchEventDisposer; + protected abstract multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; protected _touchDrag: boolean = false; protected prevPoints: Map = new Map(); @@ -22,16 +25,27 @@ export abstract class Touchable extends React.Component { * When a touch even starts, we keep track of each touch that is associated with that event */ @action - protected onTouchStart = (e: React.TouchEvent): void => { + protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { const actualPts: React.Touch[] = []; - for (let i = 0; i < e.targetTouches.length; i++) { - const pt: any = e.targetTouches.item(i); + const te = me.touchEvent; + // loop through all touches on screen + for (let i = 0; i < te.touches.length; i++) { + const pt: any = te.touches.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 > 1 && pt.radiusY > 1) { + if (this.prevPoints.has(pt.identifier)) { this.prevPoints.set(pt.identifier, pt); } + // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives + for (let j = 0; j < te.changedTouches.length; j++) { + const tPt = te.changedTouches.item(j); + if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) { + // 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 > 1 && pt.radiusY > 1) { + this.prevPoints.set(pt.identifier, pt); + } + } + } } const ptsToDelete: number[] = []; @@ -41,26 +55,30 @@ export abstract class Touchable extends React.Component { } }); + // console.log(ptsToDelete.length); ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); + // console.log(this.prevPoints.size); if (this.prevPoints.size) { switch (this.prevPoints.size) { case 1: - this.handle1PointerDown(e); - e.persist(); + this.handle1PointerDown(te); + te.persist(); // if (this.holdTimer) { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; // } - this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(e), HOLD_DURATION); + this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te), HOLD_DURATION); + // e.stopPropagation(); // console.log(this.holdTimer); break; case 2: - this.handle2PointersDown(e); - break; - case 5: - this.handleHandDown(e); + this.handle2PointersDown(te); + // e.stopPropagation(); break; + // case 5: + // this.handleHandDown(te); + // break; } } } @@ -69,28 +87,30 @@ export abstract class Touchable extends React.Component { * Handle touch move event */ @action - protected onTouch = (e: TouchEvent): void => { - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { + const te = me.touchEvent; + const myTouches = InteractionUtils.GetMyTargetTouches(te, 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; this._touchDrag = true; if (this.holdTimer) { - console.log("CLEAR") + console.log("CLEAR"); clearTimeout(this.holdTimer); // this.holdTimer = undefined; } + // console.log(myTouches.length); switch (myTouches.length) { case 1: - this.handle1PointerMove(e); + this.handle1PointerMove(te); break; case 2: - this.handle2PointersMove(e); + this.handle2PointersMove(te); break; } - for (let i = 0; i < e.targetTouches.length; i++) { - const pt = e.targetTouches.item(i); + for (let i = 0; i < te.touches.length; i++) { + const pt = te.touches.item(i); if (pt) { if (this.prevPoints.has(pt.identifier)) { this.prevPoints.set(pt.identifier, pt); @@ -100,11 +120,12 @@ export abstract class Touchable extends React.Component { } @action - protected onTouchEnd = (e: TouchEvent): void => { + protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { // console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up"); // remove all the touches associated with the event - for (let i = 0; i < e.changedTouches.length; i++) { - const pt = e.changedTouches.item(i); + const te = me.touchEvent; + for (let i = 0; i < te.changedTouches.length; i++) { + const pt = te.changedTouches.item(i); if (pt) { if (this.prevPoints.has(pt.identifier)) { this.prevPoints.delete(pt.identifier); @@ -116,7 +137,7 @@ export abstract class Touchable extends React.Component { console.log("clear"); } this._touchDrag = false; - e.stopPropagation(); + te.stopPropagation(); // if (e.targetTouches.length === 0) { @@ -126,11 +147,12 @@ export abstract class Touchable extends React.Component { if (this.prevPoints.size === 0) { this.cleanUpInteractions(); } + e.stopPropagation(); } cleanUpInteractions = (): void => { - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.removeEndListeners(); } handle1PointerMove = (e: TouchEvent): any => { @@ -144,23 +166,43 @@ export abstract class Touchable extends React.Component { } handle1PointerDown = (e: React.TouchEvent): any => { - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); } handle2PointersDown = (e: React.TouchEvent): any => { - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); } handle1PointerHoldStart = (e: React.TouchEvent): any => { e.stopPropagation(); e.preventDefault(); - document.removeEventListener("touchmove", this.onTouch); + this.removeMoveListeners(); + } + + addMoveListeners = () => { + const handler = (e: Event) => this.onTouch(e, (e as CustomEvent>).detail); + document.addEventListener("dashOnTouchMove", handler); + this.moveDisposer = () => document.removeEventListener("dashOnTouchMove", handler); + } + + removeMoveListeners = () => { + this.moveDisposer && this.moveDisposer(); + } + + addEndListeners = () => { + const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent>).detail); + document.addEventListener("dashOnTouchEnd", handler); + this.endDisposer = () => document.removeEventListener("dashOnTouchEnd", handler); + } + + removeEndListeners = () => { + this.endDisposer && this.endDisposer(); } handleHandDown = (e: React.TouchEvent) => { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 992820fc7..b8423af20 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.createDropAndGestureTarget(ele!); //so the whole grid is the drop target? + this.createDashEventsTarget(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.createDropAndGestureTarget} + createDropTarget={this.createDashEventsTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} />; } @@ -342,7 +342,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { docList={docList} parent={this} type={type} - createDropTarget={this.createDropAndGestureTarget} + createDropTarget={this.createDashEventsTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} setDocHeight={this.setDocHeight} />; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 73dc7edc6..4133956fc 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -24,6 +24,7 @@ import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { Networking } from "../../Network"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; +import { InteractionUtils } from "../../util/InteractionUtils"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc) => boolean; @@ -49,17 +50,20 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; private gestureDisposer?: GestureUtils.GestureEventDisposer; + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; private _childLayoutDisposer?: IReactionDisposer; - protected createDropAndGestureTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer && this.dropDisposer(); this.gestureDisposer && this.gestureDisposer(); + this.multiTouchDisposer && this.multiTouchDisposer(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); + this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this)); } } protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view - this.createDropAndGestureTarget(ele); + this.createDashEventsTarget(ele); } componentDidMount() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 84945c6e6..3894e9d63 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -326,29 +326,30 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action handle1PointerDown = (e: React.TouchEvent) => { - const pt = e.targetTouches.item(0); - if (pt) { - this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; - if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { - document.removeEventListener("touchmove", this.onTouch); - 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] }); - // } - if (InkingControl.Instance.selectedTool === InkTool.None) { - this._lastX = pt.pageX; - this._lastY = pt.pageY; - e.stopPropagation(); - e.preventDefault(); - } - else { - e.stopPropagation(); - e.preventDefault(); + if (!e.nativeEvent.cancelBubble) { + // const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const pt = e.changedTouches.item(0); + if (pt) { + this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; + if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); + // 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.preventDefault(); + } + else { + e.preventDefault(); + } } } } @@ -395,8 +396,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.removeEndListeners(); } @action @@ -482,7 +483,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.pan(pt); } } - e.stopPropagation(); + // e.stopPropagation(); e.preventDefault(); } } @@ -527,7 +528,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } } - e.stopPropagation(); + // e.stopPropagation(); e.preventDefault(); } } @@ -535,18 +536,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action handle2PointersDown = (e: React.TouchEvent) => { if (!e.nativeEvent.cancelBubble && this.props.active(true)) { - const pt1: React.Touch | null = e.targetTouches.item(0); - const pt2: React.Touch | null = e.targetTouches.item(1); - if (!pt1 || !pt2) return; + // const pt1: React.Touch | null = e.targetTouches.item(0); + // const pt2: React.Touch | null = e.targetTouches.item(1); + // // if (!pt1 || !pt2) return; + const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const pt1 = myTouches[0]; + const pt2 = myTouches[1]; const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; this._lastX = centerX; this._lastY = centerY; - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); e.stopPropagation(); } } @@ -554,8 +558,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { cleanUpInteractions = () => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - document.removeEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.removeEndListeners(); } @action @@ -989,9 +993,9 @@ 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
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> {!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) ? this.placeholder : this.marqueeView} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2e0ae09ba..b33bebe7d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -103,6 +103,8 @@ export class DocumentView extends DocComponent(Docu private _gestureEventDisposer?: GestureUtils.GestureEventDisposer; private _titleRef = React.createRef(); + protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } @computed get active() { return SelectionManager.IsSelected(this, true) || this.props.parentActive(true); } @@ -177,6 +179,7 @@ export class DocumentView extends DocComponent(Docu 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._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @@ -184,7 +187,11 @@ export class DocumentView extends DocComponent(Docu @action componentDidUpdate() { this._dropDisposer && this._dropDisposer(); + this._gestureEventDisposer && this._gestureEventDisposer(); + this.multiTouchDisposer && this.multiTouchDisposer(); 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._mainCont.current && (this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this))); } @action @@ -319,25 +326,24 @@ export class DocumentView extends DocComponent(Docu } } 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(); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); + e.stopPropagation(); } } handle1PointerMove = (e: TouchEvent) => { if ((e as any).formattedHandled) { e.stopPropagation; return; } if (e.cancelBubble && this.active) { - document.removeEventListener("touchmove", this.onTouch); + this.removeMoveListeners(); } 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.cleanUpInteractions(); this.startDragging(this._downX, this._downY, this.Document.dropAction ? this.Document.dropAction as any : e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); } } @@ -352,10 +358,10 @@ export class DocumentView extends DocComponent(Docu e.stopPropagation(); e.preventDefault(); - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); } } @@ -970,7 +976,7 @@ export class DocumentView extends DocComponent(Docu width: animwidth, height: animheight, opacity: this.Document.opacity - }} onTouchStart={this.onTouchStart}> + }}> {this.innards}
; } diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 062604458..4b5ad6684 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -31,7 +31,7 @@ export namespace GestureUtils { element.addEventListener("dashOnGesture", handler); return () => { element.removeEventListener("dashOnGesture", handler); - } + }; } export enum Gestures { -- cgit v1.2.3-70-g09d2 From 19be14b5807b117f477b57b9ecc7b96247bf4fef Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Wed, 15 Jan 2020 13:30:08 -0500 Subject: the quick brown fox jumped over the lazy dog --- src/client/util/InteractionUtils.ts | 13 ++-- src/client/views/CollectionLinearView.tsx | 14 +++- src/client/views/GestureOverlay.tsx | 50 ++++++------- src/client/views/Palette.tsx | 4 ++ src/client/views/Touchable.tsx | 35 ++++----- .../collectionFreeForm/CollectionFreeFormView.tsx | 41 +++++------ src/client/views/nodes/DocumentView.tsx | 82 +++++++++++----------- .../authentication/models/current_user_utils.ts | 2 +- 8 files changed, 126 insertions(+), 115 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts index 63ee2fb92..da42bdc93 100644 --- a/src/client/util/InteractionUtils.ts +++ b/src/client/util/InteractionUtils.ts @@ -11,7 +11,10 @@ export namespace InteractionUtils { export class MultiTouchEvent { constructor( readonly fingers: number, - readonly points: T extends React.TouchEvent ? React.TouchList : TouchList, + // readonly points: T extends React.TouchEvent ? React.TouchList : TouchList, + readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], + readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[], + readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent ) { } } @@ -43,13 +46,11 @@ export namespace InteractionUtils { }; } - export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { + export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { const myTouches = new Array(); - for (let i = 0; i < e.touches.length; i++) { - const pt: any = e.touches.item(i); + for (const pt of mte.touches) { if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { - for (let j = 0; j < e.targetTouches.length; j++) { - const tPt = e.targetTouches.item(j); + for (const tPt of mte.targetTouches) { if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { if (pt && prevPoints.has(pt.identifier)) { myTouches.push(pt); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 38f2decdd..2262a3c0c 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -1,4 +1,4 @@ -import { action, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, HeightSym, WidthSym } from '../../new_fields/Doc'; @@ -21,12 +21,15 @@ const LinearDocument = makeInterface(documentSchema); @observer export class CollectionLinearView extends CollectionSubView(LinearDocument) { @observable public addMenuToggle = React.createRef(); + @observable private _selectedIndex = -1; private _dropDisposer?: DragManager.DragDropDisposer; private _widthDisposer?: IReactionDisposer; + private _selectedDisposer?: IReactionDisposer; componentWillUnmount() { this._dropDisposer && this._dropDisposer(); this._widthDisposer && this._widthDisposer(); + this._selectedDisposer && this._selectedDisposer(); } componentDidMount() { @@ -35,6 +38,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { () => this.props.Document.width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), { fireImmediately: true } ); + + this._selectedDisposer = reaction( + () => NumCast(this.props.Document.selectedIndex), + (i) => runInAction(() => this._selectedIndex = i), + { fireImmediately: true } + ); } protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); @@ -61,11 +70,12 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
- {this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => { + {this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => { const nested = pair.layout.viewType === CollectionViewType.Linear; const dref = React.createRef(); const nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension()); const deltaSize = nativeWidth * .15 / 2; + const isSelected = this._selectedIndex === ind; return
{ for (let i = 0; i < e.targetTouches.length; i++) { const pt = e.targetTouches.item(i); - if (pt && !hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { - ntt.push(pt); + if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + ntt.splice(ntt.indexOf(pt)); } } for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); - if (pt && !hand.some((finger: React.Touch) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { - nct.push(pt); + if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + nct.splice(nct.indexOf(pt)); } } - for (let i = 0; i < e.changedTouches.length; i++) { + for (let i = 0; i < e.touches.length; i++) { const pt = e.touches.item(i); - if (pt && !hand.some((finger: React.Touch) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { - nt.push(pt); + if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { + nt.splice(ntt.indexOf(pt)); } } }); @@ -93,7 +93,7 @@ export default class GestureOverlay extends Touchable { ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); if (this.prevPoints.size && this.prevPoints.size < 5) { - const nts: any = this.getNewTouches(te); + const nts = this.getNewTouches(te); const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); target?.dispatchEvent( new CustomEvent>("dashOnTouchStart", @@ -101,12 +101,10 @@ export default class GestureOverlay extends Touchable { bubbles: true, detail: { fingers: this.prevPoints.size, - points: te.targetTouches, - touchEvent: new React.TouchEvent("start", { - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct - }); + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: te } } ) @@ -131,25 +129,26 @@ export default class GestureOverlay extends Touchable { bubbles: true, detail: { fingers: this.prevPoints.size, - points: e.touches, - touchEvent: new TouchEvent("move", { - targetTouches: nts.ntt, - changedTouches: nts.nct, - touches: nts.nt, - }) + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e } }) ); } onReactTouchEnd = (e: TouchEvent) => { + const nts: any = this.getNewTouches(e); document.dispatchEvent( new CustomEvent>("dashOnTouchEnd", { bubbles: true, detail: { fingers: this.prevPoints.size, - points: e.touches, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, touchEvent: e } }) @@ -171,7 +170,7 @@ export default class GestureOverlay extends Touchable { } handleHandDown = (e: React.TouchEvent) => { - const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const fingers = Array.from(e.touches); const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); this.thumbIdentifier = thumb?.identifier; fingers.forEach((f) => this.prevPoints.delete(f.identifier)); @@ -201,6 +200,7 @@ export default class GestureOverlay extends Touchable { for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); if (pt?.identifier === this.thumbIdentifier) { + console.log("thumby movey") } } } diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index 3649cccfe..aca1811a4 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -15,6 +15,9 @@ import { observer } from "mobx-react"; import { DocumentContentsView } from "./nodes/DocumentContentsView"; import { CollectionStackingView } from "./collections/CollectionStackingView"; import { CollectionView } from "./collections/CollectionView"; +import { CollectionSubView, SubCollectionViewProps } from "./collections/CollectionSubView"; +import { makeInterface } from "../../new_fields/Schema"; +import { documentSchema } from "../../new_fields/documentSchemas"; export interface PaletteProps { x: number; @@ -25,6 +28,7 @@ export interface PaletteProps { @observer export default class Palette extends React.Component { + render() { return (
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index a71015b05..7800c4019 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -29,15 +29,13 @@ export abstract class Touchable extends React.Component { const actualPts: React.Touch[] = []; const te = me.touchEvent; // loop through all touches on screen - for (let i = 0; i < te.touches.length; i++) { - const pt: any = te.touches.item(i); + for (const pt of me.touches) { actualPts.push(pt); if (this.prevPoints.has(pt.identifier)) { this.prevPoints.set(pt.identifier, pt); } // only add the ones that are targeted on "this" element, but with the identifier that the screen touch gives - for (let j = 0; j < te.changedTouches.length; j++) { - const tPt = te.changedTouches.item(j); + for (const tPt of me.changedTouches) { if (pt.clientX === tPt.clientX && pt.clientY === tPt.clientY) { // 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 @@ -58,22 +56,21 @@ export abstract class Touchable extends React.Component { // console.log(ptsToDelete.length); ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); - // console.log(this.prevPoints.size); if (this.prevPoints.size) { switch (this.prevPoints.size) { case 1: - this.handle1PointerDown(te); + this.handle1PointerDown(te, me); te.persist(); // if (this.holdTimer) { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; // } - this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te), HOLD_DURATION); + this.holdTimer = setTimeout(() => this.handle1PointerHoldStart(te, me), HOLD_DURATION); // e.stopPropagation(); // console.log(this.holdTimer); break; case 2: - this.handle2PointersDown(te); + this.handle2PointersDown(te, me); // e.stopPropagation(); break; // case 5: @@ -89,7 +86,7 @@ export abstract class Touchable extends React.Component { @action protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { const te = me.touchEvent; - const myTouches = InteractionUtils.GetMyTargetTouches(te, this.prevPoints, true); + const myTouches = InteractionUtils.GetMyTargetTouches(me, 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; @@ -102,15 +99,14 @@ export abstract class Touchable extends React.Component { // console.log(myTouches.length); switch (myTouches.length) { case 1: - this.handle1PointerMove(te); + this.handle1PointerMove(te, me); break; case 2: - this.handle2PointersMove(te); + this.handle2PointersMove(te, me); break; } - for (let i = 0; i < te.touches.length; i++) { - const pt = te.touches.item(i); + for (const pt of me.touches) { if (pt) { if (this.prevPoints.has(pt.identifier)) { this.prevPoints.set(pt.identifier, pt); @@ -124,8 +120,7 @@ export abstract class Touchable extends React.Component { // console.log(InteractionUtils.GetMyTargetTouches(e, this.prevPoints).length + " up"); // remove all the touches associated with the event const te = me.touchEvent; - for (let i = 0; i < te.changedTouches.length; i++) { - const pt = te.changedTouches.item(i); + for (const pt of me.changedTouches) { if (pt) { if (this.prevPoints.has(pt.identifier)) { this.prevPoints.delete(pt.identifier); @@ -155,31 +150,31 @@ export abstract class Touchable extends React.Component { this.removeEndListeners(); } - handle1PointerMove = (e: TouchEvent): any => { + handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); e.preventDefault(); } - handle2PointersMove = (e: TouchEvent): any => { + handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); e.preventDefault(); } - handle1PointerDown = (e: React.TouchEvent): any => { + handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); } - handle2PointersDown = (e: React.TouchEvent): any => { + handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); } - handle1PointerHoldStart = (e: React.TouchEvent): any => { + handle1PointerHoldStart = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); e.preventDefault(); this.removeMoveListeners(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3894e9d63..aaa585b55 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -44,7 +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"; +import CollectionPaletteVIew from "../../Palette"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -276,7 +276,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerDown = (e: React.PointerEvent): void => { - if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) { + if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || 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; @@ -325,10 +325,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - handle1PointerDown = (e: React.TouchEvent) => { + handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (!e.nativeEvent.cancelBubble) { // const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); - const pt = e.changedTouches.item(0); + const pt = me.changedTouches[0]; if (pt) { this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { @@ -466,10 +466,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } - handle1PointerMove = (e: TouchEvent) => { + handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { // panning a workspace if (!e.cancelBubble) { - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt = myTouches[0]; if (pt) { if (InkingControl.Instance.selectedTool === InkTool.None) { @@ -488,10 +488,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } - handle2PointersMove = (e: TouchEvent) => { + handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { // pinch zooming if (!e.cancelBubble) { - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt1 = myTouches[0]; const pt2 = myTouches[1]; @@ -534,24 +534,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - handle2PointersDown = (e: React.TouchEvent) => { + handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (!e.nativeEvent.cancelBubble && this.props.active(true)) { // const pt1: React.Touch | null = e.targetTouches.item(0); // const pt2: React.Touch | null = e.targetTouches.item(1); // // if (!pt1 || !pt2) return; - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt1 = myTouches[0]; const pt2 = myTouches[1]; - - const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - this._lastX = centerX; - this._lastY = centerY; - this.removeMoveListeners(); - this.addMoveListeners(); - this.removeEndListeners(); - this.addEndListeners(); - e.stopPropagation(); + if (pt1 && pt2) { + const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; + this._lastX = centerX; + this._lastY = centerY; + this.removeMoveListeners(); + this.addMoveListeners(); + this.removeEndListeners(); + this.addEndListeners(); + e.stopPropagation(); + } } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b33bebe7d..0b6a284d6 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -134,46 +134,46 @@ export class DocumentView extends DocComponent(Docu // document.addEventListener("touchend", this.handle1PointerHoldEnd); // } - handle1PointerHoldMove = (e: TouchEvent): void => { - 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(); - } - 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(me, this.prevPoints, true)[0]; + // if (Math.abs(pt.pageX - this._firstX) > 150 || Math.abs(pt.pageY - this._firstY) > 150) { + // this.handleRelease(); + // } + // document.removeEventListener("touchmove", this.handle1PointerHoldMove); + // document.addEventListener("touchmove", this.handle1PointerHoldMove); + // document.removeEventListener("touchend", this.handle1PointerHoldEnd); + // document.addEventListener("touchend", this.handle1PointerHoldEnd); + // } - handleRelease() { - RadialMenu.Instance.closeMenu(); - document.removeEventListener("touchmove", this.handle1PointerHoldMove); - document.removeEventListener("touchend", this.handle1PointerHoldEnd); - } + // handleRelease() { + // RadialMenu.Instance.closeMenu(); + // document.removeEventListener("touchmove", this.handle1PointerHoldMove); + // document.removeEventListener("touchend", this.handle1PointerHoldEnd); + // } - handle1PointerHoldEnd = (e: TouchEvent): void => { - RadialMenu.Instance.closeMenu(); - document.removeEventListener("touchmove", this.handle1PointerHoldMove); - document.removeEventListener("touchend", this.handle1PointerHoldEnd); - } + // handle1PointerHoldEnd = (e: TouchEvent): void => { + // RadialMenu.Instance.closeMenu(); + // document.removeEventListener("touchmove", this.handle1PointerHoldMove); + // document.removeEventListener("touchend", this.handle1PointerHoldEnd); + // } - @action - onRadialMenu = (e: React.TouchEvent): void => { - const pt = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0]; + // @action + // onRadialMenu = (e: React.TouchEvent): void => { + // const pt = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; - RadialMenu.Instance.openMenu(); + // 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: "folder", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); + // 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: "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(); - } + // RadialMenu.Instance.displayMenu(pt.pageX - 15, pt.pageY - 15); + // if (!SelectionManager.IsSelected(this, true)) { + // SelectionManager.SelectDoc(this, false); + // } + // e.stopPropagation(); + // } @action componentDidMount() { @@ -313,10 +313,10 @@ export class DocumentView extends DocComponent(Docu } } - handle1PointerDown = (e: React.TouchEvent) => { + handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (this.Document.onPointerDown) return; if (!e.nativeEvent.cancelBubble) { - const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0]; + const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; this._downX = touch.clientX; this._downY = touch.clientY; this._hitTemplateDrag = false; @@ -334,13 +334,13 @@ export class DocumentView extends DocComponent(Docu } } - handle1PointerMove = (e: TouchEvent) => { + handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if ((e as any).formattedHandled) { e.stopPropagation; return; } if (e.cancelBubble && this.active) { this.removeMoveListeners(); } 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]; + const touch = InteractionUtils.GetMyTargetTouches(me, 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)) { this.cleanUpInteractions(); @@ -353,7 +353,7 @@ export class DocumentView extends DocComponent(Docu } } - handle2PointersDown = (e: React.TouchEvent) => { + handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (!e.nativeEvent.cancelBubble && !this.isSelected()) { e.stopPropagation(); e.preventDefault(); @@ -366,8 +366,8 @@ export class DocumentView extends DocComponent(Docu } @action - handle2PointersMove = (e: TouchEvent) => { - const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { + const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt1 = myTouches[0]; const pt2 = myTouches[1]; const oldPoint1 = this.prevPoints.get(pt1.identifier); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 2ade6f102..a850b0f9f 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -126,7 +126,7 @@ export class CurrentUserUtils { static setupThumbDoc(userDoc: Doc) { if (!userDoc.thumbDoc) { - return Docs.Create.MasonryDocument(CurrentUserUtils.setupThumbButtons(userDoc), { + return Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { width: 300, columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5 }); } -- cgit v1.2.3-70-g09d2 From 899efe656c8e16fd5845e437af375b1ecb02fb7f Mon Sep 17 00:00:00 2001 From: kimdahey Date: Fri, 17 Jan 2020 14:51:51 -0500 Subject: added temporary golden layout functions --- .../views/collections/CollectionDockingView.tsx | 79 +++++++++++++++++++++- src/client/views/nodes/DocumentView.tsx | 2 + 2 files changed, 80 insertions(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 022eccc13..9fa6edf8a 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -174,7 +174,7 @@ export class CollectionDockingView extends React.Component { return MainView.Instance.openWorkspace(doc); } else if (location === "onRight") { return CollectionDockingView.AddRightSplit(doc, dataDoc, libraryPath); + } else if (location === "onTop") { + return CollectionDockingView.AddTopSplit(doc, dataDoc, libraryPath); + } else if (location === "onBottom") { + return CollectionDockingView.AddBottomSplit(doc, dataDoc, libraryPath); } else if (location === "close") { return CollectionDockingView.CloseRightSplit(doc); } else { @@ -754,3 +829,5 @@ export class DockedFrameRenderer extends React.Component { } Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc, undefined); }); Scripting.addGlobal(function useRightSplit(doc: any) { CollectionDockingView.UseRightSplit(doc, undefined); }); +Scripting.addGlobal(function openOnTop(doc: any) { CollectionDockingView.AddTopSplit(doc, undefined); }); // TEMPORARY didnt really know what this did but it seems important so +Scripting.addGlobal(function openOnBottom(doc: any) { CollectionDockingView.AddBottomSplit(doc, undefined); }); // TEMPORARY didnt really know what this did but it seems important so diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dabe5a7aa..845a2d3ab 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -601,6 +601,8 @@ export class DocumentView extends DocComponent(Docu subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this, this.props.LibraryPath), icon: "desktop" }); subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab", this.props.LibraryPath), icon: "folder" }); subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" }); + subitems.push({ description: "Open Top ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onTop", this.props.LibraryPath), icon: "caret-square-right" }); // TEMPORARY for gestures + subitems.push({ description: "Open Bottom ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onBottom", this.props.LibraryPath), icon: "caret-square-right" }); // TEMPORARY for gestures subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" }); subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" }); -- cgit v1.2.3-70-g09d2 From 4b0a056f1afaf20dea4be64c7b238748d99ad12e Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Fri, 17 Jan 2020 15:36:22 -0500 Subject: palette now doesn't interfere with touch --- src/client/documents/Documents.ts | 1 + src/client/util/InteractionUtils.ts | 2 +- src/client/views/GestureOverlay.tsx | 82 +++++++++++++++++----- src/client/views/MainView.tsx | 5 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../authentication/models/current_user_utils.ts | 1 + 6 files changed, 70 insertions(+), 23 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 64abd4f57..c49642de0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -119,6 +119,7 @@ export interface DocumentOptions { limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents // [key: string]: Opt; pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown + isExpanded?: boolean; // is linear view expanded } class EmptyBox { diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts index da42bdc93..3e6d27242 100644 --- a/src/client/util/InteractionUtils.ts +++ b/src/client/util/InteractionUtils.ts @@ -49,7 +49,7 @@ export namespace InteractionUtils { export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { const myTouches = new Array(); for (const pt of mte.touches) { - if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { + if (!ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { for (const tPt of mte.targetTouches) { if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { if (pt && prevPoints.has(pt.identifier)) { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 96c1513ff..854dff0e2 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -16,7 +16,8 @@ import { Scripting } from "../util/Scripting"; import { FieldValue, Cast, NumCast } from "../../new_fields/Types"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import Palette from "./Palette"; -import { Utils } from "../../Utils"; +import { Utils, emptyPath, emptyFunction } from "../../Utils"; +import { DocumentView } from "./nodes/DocumentView"; @observer export default class GestureOverlay extends Touchable { @@ -24,15 +25,15 @@ export default class GestureOverlay extends Touchable { @observable private _points: { X: number, Y: number }[] = []; @observable private _palette?: JSX.Element; + @observable private _elements: JSX.Element[]; @observable public Color: string = "rgb(244, 67, 54)"; @observable public Width: number = 5; private _d1: Doc | undefined; private _thumbDoc: Doc | undefined; private _thumbX?: number; - private _thumbY?: number; private thumbIdentifier?: number; - private _hands: (React.Touch[])[] = []; + private _hands: Map = new Map(); protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -49,22 +50,22 @@ export default class GestureOverlay extends Touchable { this._hands.forEach((hand) => { for (let i = 0; i < e.targetTouches.length; i++) { const pt = e.targetTouches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { - ntt.splice(ntt.indexOf(pt)); + if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { + ntt.splice(ntt.indexOf(pt), 1); } } for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { - nct.splice(nct.indexOf(pt)); + if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { + nct.splice(nct.indexOf(pt), 1); } } for (let i = 0; i < e.touches.length; i++) { const pt = e.touches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt?.screenX && finger.screenY === pt.screenY)) { - nt.splice(ntt.indexOf(pt)); + if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { + nt.splice(nt.indexOf(pt), 1); } } }); @@ -94,9 +95,10 @@ export default class GestureOverlay extends Touchable { }); ptsToDelete.forEach(pt => this.prevPoints.delete(pt)); + const nts = this.getNewTouches(te); + console.log(nts.nt.length); - if (this.prevPoints.size && this.prevPoints.size < 5) { - const nts = this.getNewTouches(te); + if (nts.nt.length < 5) { const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); target?.dispatchEvent( new CustomEvent>("dashOnTouchStart", @@ -190,12 +192,12 @@ export default class GestureOverlay extends Touchable { const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); if (thumb.identifier === this.thumbIdentifier) { this._thumbX = thumb.clientX; - this._thumbY = thumb.clientY; + this._hands.set(thumb.identifier, fingers); return; } this.thumbIdentifier = thumb?.identifier; - fingers.forEach((f) => this.prevPoints.delete(f.identifier)); - this._hands.push(fingers); + // fingers.forEach((f) => this.prevPoints.delete(f.identifier)); + this._hands.set(thumb.identifier, fingers); 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)); @@ -207,7 +209,6 @@ export default class GestureOverlay extends Touchable { runInAction(() => { this._thumbDoc = thumbDoc; this._thumbX = thumb.clientX; - this._thumbY = thumb.clientY; this._palette = ; }); } @@ -221,6 +222,29 @@ export default class GestureOverlay extends Touchable { @action handleHandMove = (e: TouchEvent) => { + const fingers = new Array(); + for (let i = 0; i < e.touches.length; i++) { + const pt: any = e.touches.item(i); + 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 (pt && this.prevPoints.has(pt.identifier)) { + this._hands.forEach(hand => hand.some(f => { + if (f.identifier === pt.identifier) { + fingers.push(pt); + } + })); + } + } + } + } + } + const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); + if (thumb?.identifier === this.thumbIdentifier) { + this._hands.set(thumb.identifier, fingers); + } + for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); if (pt && pt.identifier === this.thumbIdentifier && this._thumbX && this._thumbDoc) { @@ -236,6 +260,7 @@ export default class GestureOverlay extends Touchable { handleHandUp = (e: TouchEvent) => { if (e.touches.length < 3) { // this.onTouchEnd(e); + if (this.thumbIdentifier) this._hands.delete(this.thumbIdentifier); this._palette = undefined; this.thumbIdentifier = undefined; this._thumbDoc = undefined; @@ -380,16 +405,35 @@ export default class GestureOverlay extends Touchable { ); } + @computed get elements() { + return [ + this.props.children, + this._elements, + this._palette, + this.currentStroke + ] + } + + @action + openFloatingDoc = (doc: Doc) => { + // this._elements.push( + // + // ) + } + render() { return (
- {this.props.children} - {this._palette} - {this.currentStroke} + {this.elements}
); } } 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 +Scripting.addGlobal(function resetPen() { runInAction(() => { runInAction(() => { GestureOverlay.Instance.Color = "rgb(244, 67, 54)"; GestureOverlay.Instance.Width = 5; })); }); \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 93fb0a07c..b8b956270 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, faPhone, faStamp + faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt, faPhone, faStamp, faClipboard } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; @@ -137,6 +137,7 @@ export class MainView extends React.Component { library.add(faEllipsisV); library.add(faMusic); library.add(faPhone); + library.add(faClipboard); library.add(faStamp); this.initEventListeners(); this.initAuthenticationRouters(); @@ -518,7 +519,7 @@ export class MainView extends React.Component { - + diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aaa585b55..b302bb1dc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -327,7 +327,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (!e.nativeEvent.cancelBubble) { - // const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true); + // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt = me.changedTouches[0]; if (pt) { this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 8b8b1e029..eb48b28f2 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -114,6 +114,7 @@ export class CurrentUserUtils { const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, + // { title: "notepad", icon: "clipboard", pointerUp: "closeFloatingDoc(this.clipboard)", pointerDown: 'openFloatingDoc(this.clipboard)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ nativeWidth: 10, nativeHeight: 10, width: 10, height: 10, dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, -- cgit v1.2.3-70-g09d2 From bd1282ff8609c46c4ce813cdea7b3dcdc04f59d1 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Fri, 17 Jan 2020 15:36:23 -0500 Subject: removed golden layout stuff --- .../views/collections/CollectionDockingView.tsx | 76 ---------------------- src/client/views/nodes/DocumentView.tsx | 2 - 2 files changed, 78 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 9fa6edf8a..5f9b4e0c7 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -208,76 +208,6 @@ export class CollectionDockingView extends React.Component { return MainView.Instance.openWorkspace(doc); } else if (location === "onRight") { return CollectionDockingView.AddRightSplit(doc, dataDoc, libraryPath); - } else if (location === "onTop") { - return CollectionDockingView.AddTopSplit(doc, dataDoc, libraryPath); - } else if (location === "onBottom") { - return CollectionDockingView.AddBottomSplit(doc, dataDoc, libraryPath); } else if (location === "close") { return CollectionDockingView.CloseRightSplit(doc); } else { @@ -829,5 +755,3 @@ export class DockedFrameRenderer extends React.Component { } Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddRightSplit(doc, undefined); }); Scripting.addGlobal(function useRightSplit(doc: any) { CollectionDockingView.UseRightSplit(doc, undefined); }); -Scripting.addGlobal(function openOnTop(doc: any) { CollectionDockingView.AddTopSplit(doc, undefined); }); // TEMPORARY didnt really know what this did but it seems important so -Scripting.addGlobal(function openOnBottom(doc: any) { CollectionDockingView.AddBottomSplit(doc, undefined); }); // TEMPORARY didnt really know what this did but it seems important so diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 845a2d3ab..dabe5a7aa 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -601,8 +601,6 @@ export class DocumentView extends DocComponent(Docu subitems.push({ description: "Open Full Screen", event: () => CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(this, this.props.LibraryPath), icon: "desktop" }); subitems.push({ description: "Open Tab ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab", this.props.LibraryPath), icon: "folder" }); subitems.push({ description: "Open Right ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onRight", this.props.LibraryPath), icon: "caret-square-right" }); - subitems.push({ description: "Open Top ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onTop", this.props.LibraryPath), icon: "caret-square-right" }); // TEMPORARY for gestures - subitems.push({ description: "Open Bottom ", event: () => this.props.addDocTab(this.props.Document, this.props.DataDoc, "onBottom", this.props.LibraryPath), icon: "caret-square-right" }); // TEMPORARY for gestures subitems.push({ description: "Open Alias Tab ", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "inTab"), icon: "folder" }); subitems.push({ description: "Open Alias Right", event: () => this.props.addDocTab(Doc.MakeAlias(this.props.Document), this.props.DataDoc, "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), undefined, "onRight"), icon: "layer-group" }); -- cgit v1.2.3-70-g09d2 From 3bb4d8324b6101a82122ecb31f025c2e0420df89 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Fri, 17 Jan 2020 16:22:38 -0500 Subject: moving around the createPolyline function --- src/client/util/InteractionUtils.ts | 206 ------------------- src/client/util/InteractionUtils.tsx | 220 +++++++++++++++++++++ src/client/views/GestureOverlay.tsx | 3 +- src/client/views/InkingStroke.tsx | 17 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - 5 files changed, 223 insertions(+), 224 deletions(-) delete mode 100644 src/client/util/InteractionUtils.ts create mode 100644 src/client/util/InteractionUtils.tsx (limited to 'src/client/views/collections') diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts deleted file mode 100644 index 3e6d27242..000000000 --- a/src/client/util/InteractionUtils.ts +++ /dev/null @@ -1,206 +0,0 @@ -export namespace InteractionUtils { - export const MOUSETYPE = "mouse"; - export const TOUCHTYPE = "touch"; - export const PENTYPE = "pen"; - export const ERASERTYPE = "eraser"; - - const POINTER_PEN_BUTTON = -1; - const REACT_POINTER_PEN_BUTTON = 0; - const ERASER_BUTTON = 5; - - export class MultiTouchEvent { - constructor( - readonly fingers: number, - // readonly points: T extends React.TouchEvent ? React.TouchList : TouchList, - readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], - readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[], - readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], - readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent - ) { } - } - - export interface MultiTouchEventDisposer { (): void; } - - export function MakeMultiTouchTarget( - element: HTMLElement, - startFunc: (e: Event, me: MultiTouchEvent) => void, - ): MultiTouchEventDisposer { - const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent>).detail); - // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent>).detail) : undefined; - // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent>).detail) : undefined; - element.addEventListener("dashOnTouchStart", onMultiTouchStartHandler); - // if (onMultiTouchMoveHandler) { - // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler); - // } - // if (onMultiTouchEndHandler) { - // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler); - // } - return () => { - element.removeEventListener("dashOnTouchStart", onMultiTouchStartHandler); - // if (onMultiTouchMoveHandler) { - // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler); - // } - // if (onMultiTouchEndHandler) { - // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler); - // } - }; - } - - export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { - const myTouches = new Array(); - for (const pt of mte.touches) { - if (!ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { - for (const tPt of mte.targetTouches) { - if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { - if (pt && prevPoints.has(pt.identifier)) { - myTouches.push(pt); - } - } - } - } - } - return myTouches; - } - - export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { - 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 === -1 || e.button === 0); - case ERASERTYPE: - return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); - default: - return e.pointerType === type; - } - } - - export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number { - return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2)); - } - - /** - * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point) - * @param pts - n-arbitrary long list of points - */ - export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } { - const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length; - const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length; - return { X: centerX, Y: centerY }; - } - - /** - * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in - * @param pt1 - new point that corresponds to oldPoint1 - * @param pt2 - new point that corresponds to oldPoint2 - * @param oldPoint1 - previous point 1 - * @param oldPoint2 - previous point 2 - */ - export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { - const threshold = 4; - const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2); - const newDist = TwoPointEuclidist(pt1, pt2); - - /** if they have the same sign, then we are either pinching in or out. - * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch) - * so that it can still pan without freaking out - */ - if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) { - return Math.sign(oldDist - newDist); - } - return 0; - } - - /** - * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in - * @param pt1 - new point that corresponds to oldPoint1 - * @param pt2 - new point that corresponds to oldPoint2 - * @param oldPoint1 - previous point 1 - * @param oldPoint2 - previous point 2 - */ - export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { - const threshold = 4; - - const pt1Dist = TwoPointEuclidist(oldPoint1, pt1); - const pt2Dist = TwoPointEuclidist(oldPoint2, pt2); - - const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2); - - if (pinching !== 0) { - if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) { - return pinching; - } - } - return 0; - } - - export function IsDragging(oldTouches: Map, newTouches: React.Touch[], leniency: number): boolean { - for (const touch of newTouches) { - if (touch) { - const oldTouch = oldTouches.get(touch.identifier); - if (oldTouch) { - if (TwoPointEuclidist(touch, oldTouch) >= leniency) { - return true; - } - } - } - } - return false; - } - - // These might not be very useful anymore, but I'll leave them here for now -syip2 - { - - - /** - * Returns the type of Touch Interaction from a list of points. - * Also returns any data that is associated with a Touch Interaction - * @param pts - List of points - */ - // export function InterpretPointers(pts: React.Touch[]): { type: Opt, data?: any } { - // const leniency = 200; - // switch (pts.length) { - // case 1: - // return { type: OneFinger }; - // case 2: - // return { type: TwoSeperateFingers }; - // case 3: - // let pt1 = pts[0]; - // let pt2 = pts[1]; - // let pt3 = pts[2]; - // if (pt1 && pt2 && pt3) { - // let dist12 = TwoPointEuclidist(pt1, pt2); - // let dist23 = TwoPointEuclidist(pt2, pt3); - // let dist13 = TwoPointEuclidist(pt1, pt3); - // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`); - // let dist12close = dist12 < leniency; - // let dist23close = dist23 < leniency; - // let dist13close = dist13 < leniency; - // let xor2313 = dist23close ? !dist13close : dist13close; - // let xor = dist12close ? !xor2313 : xor2313; - // // three input xor because javascript doesn't have logical xor's - // if (xor) { - // let points: number[] = []; - // let min = Math.min(dist12, dist23, dist13); - // switch (min) { - // case dist12: - // points = [0, 1, 2]; - // break; - // case dist23: - // points = [1, 2, 0]; - // break; - // case dist13: - // points = [0, 2, 1]; - // break; - // } - // return { type: TwoToOneFingers, data: points }; - // } - // else { - // return { type: ThreeSeperateFingers, data: null }; - // } - // } - // default: - // return { type: undefined }; - // } - // } - } -} \ No newline at end of file diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx new file mode 100644 index 000000000..1fe95474c --- /dev/null +++ b/src/client/util/InteractionUtils.tsx @@ -0,0 +1,220 @@ +export namespace InteractionUtils { + export const MOUSETYPE = "mouse"; + export const TOUCHTYPE = "touch"; + export const PENTYPE = "pen"; + export const ERASERTYPE = "eraser"; + + const POINTER_PEN_BUTTON = -1; + const REACT_POINTER_PEN_BUTTON = 0; + const ERASER_BUTTON = 5; + + 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 ( + + ); + } + + export class MultiTouchEvent { + constructor( + readonly fingers: number, + // readonly points: T extends React.TouchEvent ? React.TouchList : TouchList, + readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], + readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[], + readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[], + readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent + ) { } + } + + export interface MultiTouchEventDisposer { (): void; } + + export function MakeMultiTouchTarget( + element: HTMLElement, + startFunc: (e: Event, me: MultiTouchEvent) => void, + ): MultiTouchEventDisposer { + const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent>).detail); + // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent>).detail) : undefined; + // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent>).detail) : undefined; + element.addEventListener("dashOnTouchStart", onMultiTouchStartHandler); + // if (onMultiTouchMoveHandler) { + // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler); + // } + // if (onMultiTouchEndHandler) { + // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler); + // } + return () => { + element.removeEventListener("dashOnTouchStart", onMultiTouchStartHandler); + // if (onMultiTouchMoveHandler) { + // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler); + // } + // if (onMultiTouchEndHandler) { + // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler); + // } + }; + } + + export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent, prevPoints: Map, ignorePen: boolean): React.Touch[] { + const myTouches = new Array(); + for (const pt of mte.touches) { + if (!ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) { + for (const tPt of mte.targetTouches) { + if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { + if (pt && prevPoints.has(pt.identifier)) { + myTouches.push(pt); + } + } + } + } + } + return myTouches; + } + + export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { + 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 === -1 || e.button === 0); + case ERASERTYPE: + return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); + default: + return e.pointerType === type; + } + } + + export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number { + return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2)); + } + + /** + * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point) + * @param pts - n-arbitrary long list of points + */ + export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } { + const centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length; + const centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length; + return { X: centerX, Y: centerY }; + } + + /** + * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in + * @param pt1 - new point that corresponds to oldPoint1 + * @param pt2 - new point that corresponds to oldPoint2 + * @param oldPoint1 - previous point 1 + * @param oldPoint2 - previous point 2 + */ + export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { + const threshold = 4; + const oldDist = TwoPointEuclidist(oldPoint1, oldPoint2); + const newDist = TwoPointEuclidist(pt1, pt2); + + /** if they have the same sign, then we are either pinching in or out. + * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch) + * so that it can still pan without freaking out + */ + if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) { + return Math.sign(oldDist - newDist); + } + return 0; + } + + /** + * Returns -1 if pinning and pinching out, 0 if not pinning, and 1 if pinching in + * @param pt1 - new point that corresponds to oldPoint1 + * @param pt2 - new point that corresponds to oldPoint2 + * @param oldPoint1 - previous point 1 + * @param oldPoint2 - previous point 2 + */ + export function Pinning(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { + const threshold = 4; + + const pt1Dist = TwoPointEuclidist(oldPoint1, pt1); + const pt2Dist = TwoPointEuclidist(oldPoint2, pt2); + + const pinching = Pinching(pt1, pt2, oldPoint1, oldPoint2); + + if (pinching !== 0) { + if ((pt1Dist < threshold && pt2Dist > threshold) || (pt1Dist > threshold && pt2Dist < threshold)) { + return pinching; + } + } + return 0; + } + + export function IsDragging(oldTouches: Map, newTouches: React.Touch[], leniency: number): boolean { + for (const touch of newTouches) { + if (touch) { + const oldTouch = oldTouches.get(touch.identifier); + if (oldTouch) { + if (TwoPointEuclidist(touch, oldTouch) >= leniency) { + return true; + } + } + } + } + return false; + } + + // These might not be very useful anymore, but I'll leave them here for now -syip2 + { + + + /** + * Returns the type of Touch Interaction from a list of points. + * Also returns any data that is associated with a Touch Interaction + * @param pts - List of points + */ + // export function InterpretPointers(pts: React.Touch[]): { type: Opt, data?: any } { + // const leniency = 200; + // switch (pts.length) { + // case 1: + // return { type: OneFinger }; + // case 2: + // return { type: TwoSeperateFingers }; + // case 3: + // let pt1 = pts[0]; + // let pt2 = pts[1]; + // let pt3 = pts[2]; + // if (pt1 && pt2 && pt3) { + // let dist12 = TwoPointEuclidist(pt1, pt2); + // let dist23 = TwoPointEuclidist(pt2, pt3); + // let dist13 = TwoPointEuclidist(pt1, pt3); + // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`); + // let dist12close = dist12 < leniency; + // let dist23close = dist23 < leniency; + // let dist13close = dist13 < leniency; + // let xor2313 = dist23close ? !dist13close : dist13close; + // let xor = dist12close ? !xor2313 : xor2313; + // // three input xor because javascript doesn't have logical xor's + // if (xor) { + // let points: number[] = []; + // let min = Math.min(dist12, dist23, dist13); + // switch (min) { + // case dist12: + // points = [0, 1, 2]; + // break; + // case dist23: + // points = [1, 2, 0]; + // break; + // case dist13: + // points = [0, 2, 1]; + // break; + // } + // return { type: TwoToOneFingers, data: points }; + // } + // else { + // return { type: ThreeSeperateFingers, data: null }; + // } + // } + // default: + // return { type: undefined }; + // } + // } + } +} \ No newline at end of file diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 3de901ff5..ab26687d7 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -3,7 +3,6 @@ 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"; @@ -400,7 +399,7 @@ export default class GestureOverlay extends Touchable { return ( - {CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} + {InteractionUtils.CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)} ); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 8b346d5d9..aca507147 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -10,24 +10,11 @@ import "./InkingStroke.scss"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import React = require("react"); import { TraceMobx } from "../../new_fields/util"; +import { InteractionUtils } from "../util/InteractionUtils"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); -export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color?: string, width?: number) { - const pts = points.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${pt.X - left},${pt.Y - top} `, ""); - return ( - - ); -} - @observer export class InkingStroke extends DocExtendableComponent(InkDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } @@ -44,7 +31,7 @@ export class InkingStroke extends DocExtendableComponent Date: Mon, 20 Jan 2020 00:34:45 -0500 Subject: close to buxton's initial workflow --- src/client/documents/Documents.ts | 4 ++++ src/client/util/DropConverter.ts | 3 +-- src/client/views/collections/CollectionPivotView.tsx | 2 +- src/client/views/collections/CollectionTreeView.tsx | 17 +++++++++++++++++ src/client/views/nodes/DocumentView.tsx | 2 +- 5 files changed, 24 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c3f03aede..4fe13fa78 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -518,6 +518,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Stacking }); } + export function MulticolumnDocument(documents: Array, options: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Multicolumn }); + } + export function MasonryDocument(documents: Array, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Masonry }); } diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index dc66bceee..3e2cc6a2e 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -7,8 +7,7 @@ import { StrCast } from "../../new_fields/Types"; import { Docs } from "../documents/Documents"; import { ScriptField } from "../../new_fields/ScriptField"; - -function makeTemplate(doc: Doc): boolean { +export function makeTemplate(doc: Doc): boolean { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0]; const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, ""); diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 6af7cce70..02cd2e2c9 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -34,7 +34,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { facetCollection.onCheckedClick = new ScriptField(script); } - const openDocText = "const alias = getAlias(this); alias.layoutKey = 'layoutCustom'; useRightSplit(alias); "; + const openDocText = "const alias = getAlias(this); alias.layoutKey = 'detailedDeviceView'; useRightSplit(alias); "; const openDocScript = CompileScript(openDocText, { params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name }, typecheck: false, diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 70860b6bd..239e6caa6 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -29,6 +29,8 @@ import "./CollectionTreeView.scss"; import React = require("react"); import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; import { ScriptBox } from '../ScriptBox'; +import { ImageBox } from '../nodes/ImageBox'; +import { makeTemplate } from '../../util/DropConverter'; export interface TreeViewProps { @@ -628,6 +630,21 @@ export class CollectionTreeView extends CollectionSubView(Document) { layoutItems.push({ description: (this.props.Document.hideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.props.Document.hideHeaderFields = !this.props.Document.hideHeaderFields, icon: "paint-brush" }); ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" }); } + ContextMenu.Instance.addItem({ + description: "Buxton Layout", icon: "eye", event: () => { + // const [first, second, third] = new Array(3).map(() => Docs.Create.MulticolumnDocument([], {})); + const year = Docs.Create.FreeformDocument([], { title: "year" }); + const wrapper = Docs.Create.FreeformDocument([year], {}); + makeTemplate(wrapper); + const detailedLayout = Doc.MakeAlias(wrapper); + const cardLayout = ImageBox.LayoutString("hero"); + this.childLayoutPairs.forEach(({ layout }) => { + layout.layout = cardLayout; + layout.detailedDeviceView = detailedLayout; + // Doc.ApplyTemplateTo(wrapper, layout, "detailedDeviceView"); + }); + } + }); const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean" }) }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 60dc253f7..8c7361673 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -562,7 +562,7 @@ export class DocumentView extends DocComponent(Docu this.props.Document.viewType = CollectionViewType.Stacking; this.props.Document.layoutKey = "layout_narrative"; } else { - DocumentView.makeNativeViewClicked(this.props.Document) + DocumentView.makeNativeViewClicked(this.props.Document); } } -- cgit v1.2.3-70-g09d2 From e2d6f1d930f0f092d975bd221c45d07a8620efa7 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 20 Jan 2020 09:20:42 -0500 Subject: nothing changes. --- src/client/views/collections/CollectionPivotView.tsx | 2 +- src/client/views/collections/CollectionTreeView.tsx | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 02cd2e2c9..bada8ff28 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -34,7 +34,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { facetCollection.onCheckedClick = new ScriptField(script); } - const openDocText = "const alias = getAlias(this); alias.layoutKey = 'detailedDeviceView'; useRightSplit(alias); "; + const openDocText = "const alias = getAlias(this); alias.layoutKey = 'layout_detailed'; useRightSplit(alias); "; const openDocScript = CompileScript(openDocText, { params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name }, typecheck: false, diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 239e6caa6..abc902491 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -633,15 +633,16 @@ export class CollectionTreeView extends CollectionSubView(Document) { ContextMenu.Instance.addItem({ description: "Buxton Layout", icon: "eye", event: () => { // const [first, second, third] = new Array(3).map(() => Docs.Create.MulticolumnDocument([], {})); - const year = Docs.Create.FreeformDocument([], { title: "year" }); + const year = Docs.Create.TextDocument({ title: "year" }); const wrapper = Docs.Create.FreeformDocument([year], {}); + wrapper.disableLOD = true; makeTemplate(wrapper); const detailedLayout = Doc.MakeAlias(wrapper); const cardLayout = ImageBox.LayoutString("hero"); this.childLayoutPairs.forEach(({ layout }) => { - layout.layout = cardLayout; - layout.detailedDeviceView = detailedLayout; - // Doc.ApplyTemplateTo(wrapper, layout, "detailedDeviceView"); + Doc.GetProto(layout).layout = cardLayout; + Doc.GetProto(layout).layout_detailed = detailedLayout; + // Doc.ApplyTemplateTo(wrapper, layout, "layout_detailed"); }); } }); -- cgit v1.2.3-70-g09d2 From 48e4149457f1075748397d4804db9e0c198b5e2e Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 20 Jan 2020 09:58:02 -0500 Subject: templating fixes --- src/client/views/collections/CollectionTreeView.tsx | 7 ++++++- src/client/views/nodes/DocumentView.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index abc902491..0f61756f4 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -31,6 +31,7 @@ import { CurrentUserUtils } from '../../../server/authentication/models/current_ import { ScriptBox } from '../ScriptBox'; import { ImageBox } from '../nodes/ImageBox'; import { makeTemplate } from '../../util/DropConverter'; +import { CollectionDockingView } from './CollectionDockingView'; export interface TreeViewProps { @@ -634,9 +635,12 @@ export class CollectionTreeView extends CollectionSubView(Document) { description: "Buxton Layout", icon: "eye", event: () => { // const [first, second, third] = new Array(3).map(() => Docs.Create.MulticolumnDocument([], {})); const year = Docs.Create.TextDocument({ title: "year" }); - const wrapper = Docs.Create.FreeformDocument([year], {}); + const wrapper = Docs.Create.StackingDocument([year], { autoHeight: true, chromeStatus: "disabled" }); wrapper.disableLOD = true; makeTemplate(wrapper); + delete Doc.GetProto(year).showTitle; + delete year.showTitle; + const detailedLayout = Doc.MakeAlias(wrapper); const cardLayout = ImageBox.LayoutString("hero"); this.childLayoutPairs.forEach(({ layout }) => { @@ -644,6 +648,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { Doc.GetProto(layout).layout_detailed = detailedLayout; // Doc.ApplyTemplateTo(wrapper, layout, "layout_detailed"); }); + CollectionDockingView.AddRightSplit(wrapper, undefined); } }); const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8c7361673..29f658eca 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -759,7 +759,7 @@ export class DocumentView extends DocComponent(Docu return (showTitle && !showTitleHover ? 0 : 0) + 1; } - @computed get finalLayoutKey() { return this.props.layoutKey || StrCast(this.props.Document.layoutKey, "layout"); } + @computed get finalLayoutKey() { return this.props.layoutKey || "layout"; } childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); @computed get contents() { TraceMobx(); -- cgit v1.2.3-70-g09d2 From cdde5624bac14ba6e0520a2d30d8d3926f2cc27f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 20 Jan 2020 12:19:40 -0500 Subject: buxton layout improvements --- src/client/util/DropConverter.ts | 4 +-- .../views/collections/CollectionTreeView.tsx | 30 +++++++++++++--------- src/scraping/buxton/scraper.py | 2 +- 3 files changed, 21 insertions(+), 15 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 3e2cc6a2e..ff0e19347 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -7,7 +7,7 @@ import { StrCast } from "../../new_fields/Types"; import { Docs } from "../documents/Documents"; import { ScriptField } from "../../new_fields/ScriptField"; -export function makeTemplate(doc: Doc): boolean { +export function makeTemplate(doc: Doc, suppressTitle = false): boolean { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0]; const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, ""); @@ -16,7 +16,7 @@ export function makeTemplate(doc: Doc): boolean { docs.forEach(d => { if (!StrCast(d.title).startsWith("-")) { any = true; - Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)); + Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc), suppressTitle); } else if (d.type === DocumentType.COL) { any = makeTemplate(d) || any; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 0f61756f4..a48208bd9 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -633,27 +633,33 @@ export class CollectionTreeView extends CollectionSubView(Document) { } ContextMenu.Instance.addItem({ description: "Buxton Layout", icon: "eye", event: () => { - // const [first, second, third] = new Array(3).map(() => Docs.Create.MulticolumnDocument([], {})); - const year = Docs.Create.TextDocument({ title: "year" }); - const wrapper = Docs.Create.StackingDocument([year], { autoHeight: true, chromeStatus: "disabled" }); + const { TextDocument, ImageDocument } = Docs.Create; + const wrapper = Docs.Create.StackingDocument([ + ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { title: "hero" }), + TextDocument({ title: "year" }), + TextDocument({ title: "degrees_of_freedom" }), + TextDocument({ title: "company" }), + TextDocument({ title: "short_description" }), + ], { autoHeight: true, chromeStatus: "disabled" }); wrapper.disableLOD = true; - makeTemplate(wrapper); - delete Doc.GetProto(year).showTitle; - delete year.showTitle; - + makeTemplate(wrapper, true); const detailedLayout = Doc.MakeAlias(wrapper); const cardLayout = ImageBox.LayoutString("hero"); this.childLayoutPairs.forEach(({ layout }) => { - Doc.GetProto(layout).layout = cardLayout; - Doc.GetProto(layout).layout_detailed = detailedLayout; - // Doc.ApplyTemplateTo(wrapper, layout, "layout_detailed"); + const proto = Doc.GetProto(layout); + proto.layout = cardLayout; + proto.layout_detailed = detailedLayout; + layout.showTitle = "title"; + layout.showTitleHover = "titlehover"; }); - CollectionDockingView.AddRightSplit(wrapper, undefined); } }); const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - onClicks.push({ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean" }) }); + onClicks.push({ + description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, + "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean", context: Doc.name }) + }); !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } outerXf = () => Utils.GetScreenTransform(this._mainEle!); diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 15b5844f7..998c7617d 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -100,9 +100,9 @@ def write_collection(parse_results, display_fields, storage_key, viewType): fields[storage_key] = listify(proxify_guids(view_guids)) fields["schemaColumns"] = listify(display_fields) fields["backgroundColor"] = "white" - fields["scale"] = 0.5 fields["viewType"] = 2 fields["author"] = "Bill Buxton" + fielsd["disableLOD"] = True, fields["creationDate"] = { "date": datetime.datetime.utcnow().microsecond, "__type": "date" -- cgit v1.2.3-70-g09d2 From df61d40f92a29f948d9f513fb9f50349f16500ee Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 20 Jan 2020 12:45:47 -0500 Subject: pivot componentDidMount true hack --- src/client/views/collections/CollectionPivotView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index bada8ff28..53ad433b3 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -20,7 +20,7 @@ import { Set } from "typescript-collections"; export class CollectionPivotView extends CollectionSubView(doc => doc) { componentDidMount = () => { this.props.Document.freeformLayoutEngine = "pivot"; - if (!this.props.Document.facetCollection) { + if (true || !this.props.Document.facetCollection) { const facetCollection = Docs.Create.FreeformDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true }); facetCollection.target = this.props.Document; -- cgit v1.2.3-70-g09d2 From a61dfaab422886733a727eebe80b619230d59684 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Mon, 20 Jan 2020 13:40:57 -0500 Subject: working palette --- src/client/documents/Documents.ts | 1 + src/client/views/GestureOverlay.scss | 6 ++ src/client/views/GestureOverlay.tsx | 87 +++++++++++++++++----- .../views/collections/CollectionLinearView.tsx | 24 ++++-- .../authentication/models/current_user_utils.ts | 7 +- 5 files changed, 99 insertions(+), 26 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3617630f3..2aa848f5c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -109,6 +109,7 @@ export interface DocumentOptions { 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 + clipboard?: Doc; //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; gridGap?: number; // gap between items in masonry view xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index 31601efd4..f9a52d976 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -5,4 +5,10 @@ top: 0; left: 0; touch-action: none; +} + +.clipboardDoc-cont { + position: absolute; + width: 300px; + height: 300px; } \ No newline at end of file diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index ab26687d7..7fb8e7797 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { Touchable } from "./Touchable"; import { observer } from "mobx-react"; import "./GestureOverlay.scss"; -import { computed, observable, action, runInAction } from "mobx"; +import { computed, observable, action, runInAction, IReactionDisposer, reaction } from "mobx"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { InteractionUtils } from "../util/InteractionUtils"; import { InkingControl } from "./InkingControl"; @@ -12,11 +12,13 @@ import { LinkManager } from "../util/LinkManager"; import { DocUtils } from "../documents/Documents"; import { undoBatch } from "../util/UndoManager"; import { Scripting } from "../util/Scripting"; -import { FieldValue, Cast, NumCast } from "../../new_fields/Types"; +import { FieldValue, Cast, NumCast, BoolCast } from "../../new_fields/Types"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import Palette from "./Palette"; -import { Utils, emptyPath, emptyFunction } from "../../Utils"; +import { Utils, emptyPath, emptyFunction, returnFalse, returnOne, returnEmptyString, returnTrue } from "../../Utils"; import { DocumentView } from "./nodes/DocumentView"; +import { Transform } from "../util/Transform"; +import { DocumentContentsView } from "./nodes/DocumentContentsView"; @observer export default class GestureOverlay extends Touchable { @@ -24,13 +26,16 @@ export default class GestureOverlay extends Touchable { @observable private _points: { X: number, Y: number }[] = []; @observable private _palette?: JSX.Element; - @observable private _elements: JSX.Element[]; + @observable private _clipboardDoc?: JSX.Element; @observable public Color: string = "rgb(244, 67, 54)"; @observable public Width: number = 5; + @observable public SavedColor?: string; + @observable public SavedWidth?: number; private _d1: Doc | undefined; private _thumbDoc: Doc | undefined; - private _thumbX?: number; + @observable private _thumbX?: number; + @observable private _thumbY?: number; private thumbIdentifier?: number; private _hands: Map = new Map(); @@ -191,6 +196,7 @@ export default class GestureOverlay extends Touchable { const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); if (thumb.identifier === this.thumbIdentifier) { this._thumbX = thumb.clientX; + this._thumbY = thumb.clientY; this._hands.set(thumb.identifier, fingers); return; } @@ -208,6 +214,7 @@ export default class GestureOverlay extends Touchable { runInAction(() => { this._thumbDoc = thumbDoc; this._thumbX = thumb.clientX; + this._thumbY = thumb.clientY; this._palette = ; }); } @@ -407,32 +414,76 @@ export default class GestureOverlay extends Touchable { @computed get elements() { return [ this.props.children, - this._elements, + // this._clipboardDoc, this._palette, this.currentStroke - ] + ]; } @action - openFloatingDoc = (doc: Doc) => { - // this._elements.push( - // - // ) + public openFloatingDoc = (doc: Doc) => { + // const t = new Transform(-(this._clipboardDoc ? (this._thumbX ?? 0) : -350), -(this._thumbY ?? 0) + 350, 1); + // let t = + this._clipboardDoc = + new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + 350, 1)} + ContentScaling={returnOne} + PanelWidth={() => 300} + PanelHeight={() => 300} + renderDepth={0} + backgroundColor={returnEmptyString} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + zoomToScale={emptyFunction} + getScale={returnOne} + />; + } + + @action + public closeFloatingDoc = () => { + this._clipboardDoc = undefined; } render() { return (
{this.elements} +
+ {this._clipboardDoc} +
); } } 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(() => { runInAction(() => { GestureOverlay.Instance.Color = "rgb(244, 67, 54)"; GestureOverlay.Instance.Width = 5; }); }); }); \ No newline at end of file +Scripting.addGlobal(function setPen(width: any, color: any) { + runInAction(() => { + GestureOverlay.Instance.SavedColor = GestureOverlay.Instance.Color; + GestureOverlay.Instance.Color = color; + GestureOverlay.Instance.SavedWidth = GestureOverlay.Instance.Width; + GestureOverlay.Instance.Width = width; + }); +}); +Scripting.addGlobal(function resetPen() { + runInAction(() => { + GestureOverlay.Instance.Color = GestureOverlay.Instance.SavedColor ?? "rgb(244, 67, 54)"; + GestureOverlay.Instance.Width = GestureOverlay.Instance.SavedWidth ?? 5; + }); +}); \ No newline at end of file diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index e91c2a01d..8b0638aa1 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -31,6 +31,9 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { this._dropDisposer && this._dropDisposer(); this._widthDisposer && this._widthDisposer(); this._selectedDisposer && this._selectedDisposer(); + this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => { + Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log); + }); } componentDidMount() { @@ -42,7 +45,22 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { this._selectedDisposer = reaction( () => NumCast(this.props.Document.selectedIndex), - (i) => runInAction(() => this._selectedIndex = i), + (i) => runInAction(() => { + this._selectedIndex = i; + let selected: any = undefined; + this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => { + const isSelected = this._selectedIndex === ind; + if (isSelected) { + selected = pair; + } + else { + Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log); + } + }); + if (selected && selected.layout) { + Cast(selected.layout.proto?.onPointerDown, ScriptField)?.script.run({ this: selected.layout.proto }, console.log); + } + }), { fireImmediately: true } ); } @@ -76,10 +94,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { const dref = React.createRef(); const nativeWidth = NumCast(pair.layout.nativeWidth, this.dimension()); const deltaSize = nativeWidth * .15 / 2; - const isSelected = this._selectedIndex === ind; - if (isSelected) { - Cast(pair.layout.proto?.onPointerDown, ScriptField)?.script.run({ this: pair.layout.proto }, console.log); - } return
Docs.Create.FontIconDocument({ nativeWidth: 10, nativeHeight: 10, width: 10, height: 10, dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, + clipboard: data.clipboard, onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true, backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, @@ -127,7 +128,7 @@ export class CurrentUserUtils { static setupThumbDoc(userDoc: Doc) { if (!userDoc.thumbDoc) { - return Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { + userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { width: 100, height: 50, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5, isExpanded: true }); } -- cgit v1.2.3-70-g09d2 From aea4acf334aa62b2cbaa060a1dfbcbc1489d800a Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 20 Jan 2020 14:12:31 -0500 Subject: scraper support for multicolumn --- src/client/documents/Documents.ts | 14 +++++++------- src/client/views/collections/CollectionTreeView.tsx | 9 ++++----- src/scraping/buxton/scraper.py | 4 +++- 3 files changed, 14 insertions(+), 13 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4fe13fa78..9f5c1dafe 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -666,28 +666,28 @@ export namespace Docs { const field = target[fieldKey]; const resolved = options || {}; if (field instanceof ImageField) { - created = Docs.Create.ImageDocument((field as ImageField).url.href, resolved); + created = Docs.Create.ImageDocument((field).url.href, resolved); layout = ImageBox.LayoutString; } else if (field instanceof Doc) { created = field; } else if (field instanceof VideoField) { - created = Docs.Create.VideoDocument((field as VideoField).url.href, resolved); + created = Docs.Create.VideoDocument((field).url.href, resolved); layout = VideoBox.LayoutString; } else if (field instanceof PdfField) { - created = Docs.Create.PdfDocument((field as PdfField).url.href, resolved); + created = Docs.Create.PdfDocument((field).url.href, resolved); layout = PDFBox.LayoutString; } else if (field instanceof IconField) { - created = Docs.Create.IconDocument((field as IconField).icon, resolved); + created = Docs.Create.IconDocument((field).icon, resolved); layout = IconBox.LayoutString; } else if (field instanceof AudioField) { - created = Docs.Create.AudioDocument((field as AudioField).url.href, resolved); + created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; } else if (field instanceof HistogramField) { - created = Docs.Create.HistogramDocument((field as HistogramField).HistoOp, resolved); + created = Docs.Create.HistogramDocument((field).HistoOp, resolved); layout = HistogramBox.LayoutString; } else if (field instanceof InkField) { const { selectedColor, selectedWidth, selectedTool } = InkingControl.Instance; - created = Docs.Create.InkDocument(selectedColor, selectedTool, Number(selectedWidth), (field as InkField).inkData, resolved); + created = Docs.Create.InkDocument(selectedColor, selectedTool, Number(selectedWidth), (field).inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index a48208bd9..a458aa0af 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -32,6 +32,7 @@ import { ScriptBox } from '../ScriptBox'; import { ImageBox } from '../nodes/ImageBox'; import { makeTemplate } from '../../util/DropConverter'; import { CollectionDockingView } from './CollectionDockingView'; +import { CollectionViewType } from './CollectionView'; export interface TreeViewProps { @@ -633,13 +634,11 @@ export class CollectionTreeView extends CollectionSubView(Document) { } ContextMenu.Instance.addItem({ description: "Buxton Layout", icon: "eye", event: () => { - const { TextDocument, ImageDocument } = Docs.Create; + const { TextDocument, ImageDocument, MulticolumnDocument } = Docs.Create; const wrapper = Docs.Create.StackingDocument([ ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { title: "hero" }), - TextDocument({ title: "year" }), - TextDocument({ title: "degrees_of_freedom" }), - TextDocument({ title: "company" }), - TextDocument({ title: "short_description" }), + // MulticolumnDocument([], { title: "data", height: 100 }), + ...["year", "company", "degrees_of_freedom", "short_description"].map(key => TextDocument({ title: key })) ], { autoHeight: true, chromeStatus: "disabled" }); wrapper.disableLOD = true; makeTemplate(wrapper, true); diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 2d1a5ca32..e80979616 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -191,7 +191,9 @@ def write_image(folder, name): "x": 10, "y": 10, "width": min(800, native_width), - "zIndex": 2 + "zIndex": 2, + "widthUnit": "*", + "widthMagnitude": 1 }, "__type": "Doc" } -- cgit v1.2.3-70-g09d2 From cc2cbf44ba5c30a70bad2ffd7a57d2c6d17d0e4e Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 20 Jan 2020 16:09:43 -0500 Subject: finalLayoutKey changes and sorted facet values --- src/client/views/collections/CollectionPivotView.tsx | 2 +- src/client/views/collections/CollectionTreeView.tsx | 5 ++--- src/client/views/nodes/DocumentView.tsx | 10 +++++++++- 3 files changed, 12 insertions(+), 5 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 53ad433b3..ad2e05908 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -76,7 +76,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { facetValues.add(child[facet]?.toString() || "(null)"); }); - const newFacetVals = facetValues.toArray().map(val => Docs.Create.TextDocument({ title: val.toString() })); + const newFacetVals = facetValues.toArray().sort().map(val => Docs.Create.TextDocument({ title: val.toString() })); const newFacet = Docs.Create.FreeformDocument(newFacetVals, { title: facet, treeViewOpen: true, isFacetFilter: true }); Doc.AddDocToList(facetCollection, "data", newFacet); } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index a458aa0af..3a208038c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -634,11 +634,10 @@ export class CollectionTreeView extends CollectionSubView(Document) { } ContextMenu.Instance.addItem({ description: "Buxton Layout", icon: "eye", event: () => { - const { TextDocument, ImageDocument, MulticolumnDocument } = Docs.Create; + const { TextDocument, ImageDocument } = Docs.Create; const wrapper = Docs.Create.StackingDocument([ ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { title: "hero" }), - // MulticolumnDocument([], { title: "data", height: 100 }), - ...["year", "company", "degrees_of_freedom", "short_description"].map(key => TextDocument({ title: key })) + ...["short_description", "year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key })) ], { autoHeight: true, chromeStatus: "disabled" }); wrapper.disableLOD = true; makeTemplate(wrapper, true); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 29f658eca..c58362f6c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -759,7 +759,15 @@ export class DocumentView extends DocComponent(Docu return (showTitle && !showTitleHover ? 0 : 0) + 1; } - @computed get finalLayoutKey() { return this.props.layoutKey || "layout"; } + @computed get finalLayoutKey() { + const { layoutKey } = this.props; + if (typeof layoutKey === "string") { + return layoutKey; + } + // return "layout"; + const fallback = Cast(this.props.Document.layoutKey, "string"); + return typeof fallback === "string" ? fallback : "layout"; + } childScaling = () => (this.layoutDoc.fitWidth ? this.props.PanelWidth() / this.nativeWidth : this.props.ContentScaling()); @computed get contents() { TraceMobx(); -- cgit v1.2.3-70-g09d2 From dabb4a9c66083b88eba7bfb07a2614634e124b10 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Mon, 20 Jan 2020 16:33:49 -0500 Subject: resizable palette --- src/client/documents/Documents.ts | 4 +- src/client/views/GestureOverlay.scss | 6 ++ src/client/views/GestureOverlay.tsx | 68 +++++++++++++++------- src/client/views/MainView.tsx | 2 +- .../views/collections/CollectionLinearView.tsx | 2 +- src/client/views/nodes/ButtonBox.tsx | 5 +- src/new_fields/documentSchemas.ts | 2 + .../authentication/models/current_user_utils.ts | 16 +++-- 8 files changed, 75 insertions(+), 30 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7e7b10ae8..821185518 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -80,7 +80,7 @@ export interface DocumentOptions { isTemplateDoc?: boolean; templates?: List; viewType?: number; - backgroundColor?: string; + backgroundColor?: string | ScriptField; ignoreClick?: boolean; lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed @@ -127,6 +127,8 @@ export interface DocumentOptions { // [key: string]: Opt; pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown isExpanded?: boolean; // is linear view expanded + textTransform?: string; // is linear view expanded + letterSpacing?: string; // is linear view expanded } class EmptyBox { diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index f9a52d976..d980b0a91 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -11,4 +11,10 @@ position: absolute; width: 300px; height: 300px; +} + +.filter-cont { + position: absolute; + background-color: transparent; + border: 1px solid black; } \ No newline at end of file diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 7fb8e7797..e44db4463 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -24,19 +24,25 @@ import { DocumentContentsView } from "./nodes/DocumentContentsView"; export default class GestureOverlay extends Touchable { static Instance: GestureOverlay; - @observable private _points: { X: number, Y: number }[] = []; - @observable private _palette?: JSX.Element; - @observable private _clipboardDoc?: JSX.Element; @observable public Color: string = "rgb(244, 67, 54)"; @observable public Width: number = 5; @observable public SavedColor?: string; @observable public SavedWidth?: number; - private _d1: Doc | undefined; - private _thumbDoc: Doc | undefined; @observable private _thumbX?: number; @observable private _thumbY?: number; + @observable private _pointerY?: number; + @observable private _points: { X: number, Y: number }[] = []; + @observable private _palette?: JSX.Element; + @observable private _clipboardDoc?: JSX.Element; + @observable private _showBounds: boolean = false; + + @computed private get height(): number { return Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 300, 300); } + + private _d1: Doc | undefined; + private _thumbDoc: Doc | undefined; private thumbIdentifier?: number; + private pointerIdentifier?: number; private _hands: Map = new Map(); protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -178,7 +184,7 @@ export default class GestureOverlay extends Touchable { e.stopPropagation(); } - handleHandDown = (e: React.TouchEvent) => { + handleHandDown = async (e: React.TouchEvent) => { const fingers = new Array(); for (let i = 0; i < e.touches.length; i++) { const pt: any = e.touches.item(i); @@ -194,6 +200,22 @@ export default class GestureOverlay extends Touchable { } } const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); + const rightMost = Math.max(...fingers.map(f => f.clientX)); + const leftMost = Math.min(...fingers.map(f => f.clientX)); + let pointer: React.Touch | undefined; + // left hand + if (thumb.clientX === rightMost) { + pointer = fingers.reduce((a, v) => a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v); + } + // right hand + else if (thumb.clientX === leftMost) { + pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v); + } + else { + console.log("not hand"); + } + this.pointerIdentifier = pointer?.identifier; + runInAction(() => this._pointerY = pointer?.clientY); if (thumb.identifier === this.thumbIdentifier) { this._thumbX = thumb.clientX; this._thumbY = thumb.clientY; @@ -201,15 +223,12 @@ export default class GestureOverlay extends Touchable { return; } this.thumbIdentifier = thumb?.identifier; - // fingers.forEach((f) => this.prevPoints.delete(f.identifier)); this._hands.set(thumb.identifier, fingers); 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)); + const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc); if (thumbDoc) { runInAction(() => { this._thumbDoc = thumbDoc; @@ -247,7 +266,7 @@ export default class GestureOverlay extends Touchable { } } const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); - if (thumb?.identifier === this.thumbIdentifier) { + if (thumb?.identifier && thumb?.identifier === this.thumbIdentifier) { this._hands.set(thumb.identifier, fingers); } @@ -259,6 +278,9 @@ export default class GestureOverlay extends Touchable { this._thumbX = pt.clientX; } } + if (pt && pt.identifier === this.pointerIdentifier) { + this._pointerY = pt.clientY; + } } } @@ -329,8 +351,6 @@ export default class GestureOverlay extends Touchable { return actionPerformed; } - - @action onPointerUp = (e: PointerEvent) => { if (this._points.length > 1) { @@ -414,7 +434,6 @@ export default class GestureOverlay extends Touchable { @computed get elements() { return [ this.props.children, - // this._clipboardDoc, this._palette, this.currentStroke ]; @@ -422,8 +441,6 @@ export default class GestureOverlay extends Touchable { @action public openFloatingDoc = (doc: Doc) => { - // const t = new Transform(-(this._clipboardDoc ? (this._thumbX ?? 0) : -350), -(this._thumbY ?? 0) + 350, 1); - // let t = this._clipboardDoc = new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + 350, 1)} + ScreenToLocalTransform={() => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1)} ContentScaling={returnOne} PanelWidth={() => 300} PanelHeight={() => 300} @@ -462,13 +479,24 @@ export default class GestureOverlay extends Touchable {
{this.elements}
{this._clipboardDoc}
-
); +
+
+
); } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 168d2ea18..14ee0dc53 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -46,7 +46,7 @@ import RichTextMenu from '../util/RichTextMenu'; @observer export class MainView extends React.Component { public static Instance: MainView; - private _buttonBarHeight = 75; + private _buttonBarHeight = 35; private _flyoutSizeOnDown = 0; private _urlState: HistoryUtil.DocUrl; private _docBtnRef = React.createRef(); diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 8b0638aa1..77af2dc0e 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (i) => runInAction(() => { this._selectedIndex = i; let selected: any = undefined; - this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => { + this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map(async (pair, ind) => { const isSelected = this._selectedIndex === ind; if (isSelected) { selected = pair; diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index d1272c266..d29fe1711 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -80,7 +80,10 @@ export class ButtonBox extends DocComponent(Butt return (
-
+
{(this.Document.text || this.Document.title)}
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index 24f6224eb..d5c34e128 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -57,6 +57,8 @@ export const documentSchema = createSchema({ yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set LODarea: "number", // area (width*height) where CollectionFreeFormViews switch from a label to rendering contents LODdisable: "boolean", // whether to disbale LOD switching for CollectionFreeFormViews + letterSpacing: "string", + textTransform: "string" }); export const positionSchema = createSchema({ diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index adeee452c..3e6399d82 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -129,7 +129,7 @@ export class CurrentUserUtils { static setupThumbDoc(userDoc: Doc) { if (!userDoc.thumbDoc) { userDoc.thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { - width: 100, height: 50, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5, isExpanded: true + width: 100, height: 50, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5, isExpanded: true, backgroundColor: "white" }); } return userDoc.thumbDoc; @@ -154,7 +154,8 @@ export class CurrentUserUtils { }); return Docs.Create.ButtonDocument({ - width: 35, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Tools", fontSize: 10, targetContainer: sidebarContainer, + width: 35, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Tools", fontSize: 10, targetContainer: sidebarContainer, + letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: Docs.Create.StackingDocument([dragCreators, color], { width: 500, height: 800, lockedPosition: true, chromeStatus: "disabled", title: "tools stack" }), @@ -179,9 +180,10 @@ export class CurrentUserUtils { }); return Docs.Create.ButtonDocument({ - width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Library", fontSize: 10, + width: 50, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Library", fontSize: 10, + letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], { - title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true + title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true, boxShadow: "0 0", }), targetContainer: sidebarContainer, onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;") @@ -191,7 +193,8 @@ export class CurrentUserUtils { // setup the Search button which will display the search panel. static setupSearchPanel(sidebarContainer: Doc) { return Docs.Create.ButtonDocument({ - width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Search", fontSize: 10, + width: 50, height: 25, backgroundColor: "lightgrey", color: "rgb(34, 34, 34)", title: "Search", fontSize: 10, + letterSpacing: "0px", textTransform: "unset", borderRounding: "5px 5px 0px 0px", boxShadow: "3px 3px 0px rgb(34, 34, 34)", sourcePanel: Docs.Create.QueryDocument({ title: "search stack", ignoreClick: true }), @@ -214,7 +217,8 @@ export class CurrentUserUtils { // Finally, setup the list of buttons to display in the sidebar doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.ToolsBtn as Doc], { width: 500, height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true, - backgroundColor: "lightgrey", chromeStatus: "disabled", title: "library stack" + backgroundColor: "rgb(100, 100, 100)", chromeStatus: "disabled", title: "library stack", + yMargin: 10, }); } -- cgit v1.2.3-70-g09d2 From 69e068d77731c25d9f1dbafb8c7d279e343a4e55 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 21 Jan 2020 00:11:41 -0500 Subject: fixed up creating template fields in stacking views (+ after the template is made) --- .../views/collections/CollectionPivotView.tsx | 37 +++++++++++++++------- .../CollectionStackingViewFieldColumn.tsx | 6 ++++ src/client/views/collections/CollectionView.tsx | 6 ++++ .../views/collections/CollectionViewChromes.tsx | 10 ++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 -- src/client/views/nodes/DocumentView.tsx | 3 +- src/new_fields/Doc.ts | 5 +++ src/new_fields/documentSchemas.ts | 7 ++++ src/new_fields/util.ts | 4 +-- 9 files changed, 61 insertions(+), 20 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index ad2e05908..07ad5683c 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -2,7 +2,7 @@ import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx"; import { faEdit, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Field, DocCastAsync } from "../../../new_fields/Doc"; import "./CollectionPivotView.scss"; import { observer } from "mobx-react"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; @@ -15,10 +15,15 @@ import { anchorPoints, Flyout } from "../TemplateMenu"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { List } from "../../../new_fields/List"; import { Set } from "typescript-collections"; +import { PrefetchProxy } from "../../../new_fields/Proxy"; @observer export class CollectionPivotView extends CollectionSubView(doc => doc) { - componentDidMount = () => { + private _narrativeDisposer: IReactionDisposer | undefined; + componentWillUnmount() { + this._narrativeDisposer?.(); + } + componentDidMount() { this.props.Document.freeformLayoutEngine = "pivot"; if (true || !this.props.Document.facetCollection) { const facetCollection = Docs.Create.FreeformDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true }); @@ -34,16 +39,24 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { facetCollection.onCheckedClick = new ScriptField(script); } - const openDocText = "const alias = getAlias(this); alias.layoutKey = 'layout_detailed'; useRightSplit(alias); "; - const openDocScript = CompileScript(openDocText, { - params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name }, - typecheck: false, - editable: true, - }); - if (openDocScript.compiled) { - this.props.Document.onChildClick = new ScriptField(openDocScript); - } - + this._narrativeDisposer = reaction(() => this.props.Document.childDetailed, + (childDetailed) => + DocCastAsync(childDetailed).then(childDetailed => { + if (childDetailed instanceof Doc) { + let captured: { [name: string]: Field } = {}; + captured["childDetailed"] = new PrefetchProxy(childDetailed); + const openDocText = "const alias = getAlias(this); Doc.ApplyTemplateTo(childDetailed, alias, 'layout_detailed'); useRightSplit(alias); "; + const openDocScript = CompileScript(openDocText, { + params: { this: Doc.name, heading: "boolean", context: Doc.name }, + typecheck: false, + editable: true, + capturedVariables: captured + }); + if (openDocScript.compiled) { + this.props.Document.onChildClick = new ScriptField(openDocScript); + } + } + }), { fireImmediately: true }); this.props.Document.facetCollection = facetCollection; this.props.Document.fitToBox = true; } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 23a664359..229a23294 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -142,6 +142,12 @@ export class CollectionStackingViewFieldColumn extends React.Component { const existing = ContextMenu.Instance.findByDescription("Layout..."); const layoutItems = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); + if (this.props.Document.childLayout instanceof Doc) { + layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, undefined, "onRight"), icon: "project-diagram" }); + } + if (this.props.Document.childDetailed instanceof Doc) { + layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childDetailed as Doc, undefined, "onRight"), icon: "project-diagram" }); + } !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); const more = ContextMenu.Instance.findByDescription("More..."); diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 996c7671e..075c48134 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -39,10 +39,15 @@ export class CollectionViewBaseChrome extends React.Component Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined) }; + _narrativeCommand = { + title: "set detailed template", script: "setChildLayout(this.target, this.source?.[0])", params: ["target", "source"], + initialize: emptyFunction, + immediate: (draggedDocs: Doc[]) => Doc.setChildDetailed(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined) + }; _contentCommand = { // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"], // bcz: doesn't look like we can do async stuff in scripting... title: "set content", script: "getProto(this.target).data = aliasDocs(this.source);", params: ["target", "source"], @@ -54,7 +59,7 @@ export class CollectionViewBaseChrome extends React.Component { this.props.CollectionView.props.Document.panX = 0; this.props.CollectionView.props.Document.panY = 0; this.props.CollectionView.props.Document.scale = 1; }, initialize: (button: Doc) => { button.restoredPanX = this.props.CollectionView.props.Document.panX; button.restoredPanY = this.props.CollectionView.props.Document.panY; button.restoredScale = this.props.CollectionView.props.Document.scale; } }; - _freeform_commands = [this._contentCommand, this._templateCommand, this._viewCommand]; + _freeform_commands = [this._contentCommand, this._templateCommand, this._narrativeCommand, this._viewCommand]; _stacking_commands = [this._contentCommand, this._templateCommand]; _masonry_commands = [this._contentCommand, this._templateCommand]; _tree_commands = []; @@ -64,6 +69,7 @@ export class CollectionViewBaseChrome extends React.Component { const layoutItems: ContextMenuProps[] = []; - if (this.childDocs.some(d => BoolCast(d.isTemplateDoc))) { - layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); - } layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" }); layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 63bb6b18c..e91013be4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -659,7 +659,8 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action - setCustomView = (custom: boolean): void => { + setCustomView = + (custom: boolean): void => { if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); } else { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index e0ab5d97c..29834256a 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -746,6 +746,11 @@ export namespace Doc { source.dragFactory instanceof Doc && source.dragFactory.isTemplateDoc ? source.dragFactory : source && source.layout instanceof Doc && source.layout.isTemplateDoc ? source.layout : undefined; } + export function setChildDetailed(target: Doc, source?: Doc) { + target.childDetailed = source && source.isTemplateDoc ? source : source && + source.dragFactory instanceof Doc && source.dragFactory.isTemplateDoc ? source.dragFactory : + source && source.layout instanceof Doc && source.layout.isTemplateDoc ? source.layout : undefined; + } export function MakeDocFilter(docFilters: string[]) { let docFilterText = ""; diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index d5c34e128..3683e5820 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -68,6 +68,13 @@ export const positionSchema = createSchema({ z: "number", }); +export const collectionSchema = createSchema({ + childLayout: Doc, // layout template for children of a collecion + childDetailed: Doc, // layout template to apply to a child when its clicked on in a collection and opened (requires onChildClick or other script to use this field) + onChildClick: ScriptField, // script to run for each child when its clicked + onCheckedClick: ScriptField, // script to run when a checkbox is clicked next to a child in a tree view +}); + export type Document = makeInterface<[typeof documentSchema]>; export const Document = makeInterface(documentSchema); diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 4147be278..3255c6172 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -1,7 +1,7 @@ import { UndoManager } from "../client/util/UndoManager"; import { Doc, Field, FieldResult, UpdatingFromServer } from "./Doc"; import { SerializationHelper } from "../client/util/SerializationHelper"; -import { ProxyField } from "./Proxy"; +import { ProxyField, PrefetchProxy } from "./Proxy"; import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; import { action, trace } from "mobx"; @@ -52,7 +52,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number value = new ProxyField(value); } if (value instanceof ObjectField) { - if (value[Parent] && value[Parent] !== receiver) { + if (value[Parent] && value[Parent] !== receiver && !(value instanceof PrefetchProxy)) { throw new Error("Can't put the same object in multiple documents at the same time"); } value[Parent] = receiver; -- cgit v1.2.3-70-g09d2 From 9090808d81028fb7ed8796339524b5a0fa0893d5 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 21 Jan 2020 08:27:50 -0500 Subject: fixed some hangs when the title is a rich text field --- src/client/views/collections/ParentDocumentSelector.tsx | 6 +++--- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 24aa6ddfa..1cc05f92c 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -6,7 +6,7 @@ import { observable, action, runInAction } from "mobx"; import { Id } from "../../../new_fields/FieldSymbols"; import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; -import { NumCast } from "../../../new_fields/Types"; +import { NumCast, StrCast } from "../../../new_fields/Types"; import { CollectionViewType } from "./CollectionView"; import { DocumentButtonBar } from "../DocumentButtonBar"; import { DocumentManager } from "../../util/DocumentManager"; @@ -71,9 +71,9 @@ export class SelectorContextMenu extends React.Component { return
Metadata: {this.metadataMenu}

Contexts:

- {this._docs.map(doc =>

{doc.col.title}

)} + {this._docs.map(doc =>

{doc.col.title?.toString()}

)} {this._otherDocs.length ?
: null} - {this._otherDocs.map(doc =>

{doc.col.title}

)} + {this._otherDocs.map(doc =>

{doc.col.title?.toString()}

)}
; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7ed36df69..db5259bdc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -78,7 +78,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _layoutComputeReaction: IReactionDisposer | undefined; private _layoutPoolData = new ObservableMap(); - public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title + ")"; } // 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[])[] = []; @@ -973,7 +973,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @computed get placeholder() { return
- {this.props.Document.title} + {this.props.Document.title?.toString()}
; } @computed get marqueeView() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e91013be4..2ccfad448 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -930,9 +930,9 @@ export class DocumentView extends DocComponent(Docu pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all", }}> StrCast((this.props.DataDoc || this.props.Document)[showTitle])} + GetValue={() => (this.props.DataDoc || this.props.Document)[showTitle]?.toString()} SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[showTitle] = value) ? true : true)} />
); -- cgit v1.2.3-70-g09d2 From 4d1c1286f311de9d69158adccb02b60638a30e83 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 21 Jan 2020 09:12:35 -0500 Subject: added a ToString() method to fields so that rendering them as RichTextField won't make them unreadable --- src/client/northstar/dash-fields/HistogramField.ts | 5 ++++- .../CollectionFreeFormLayoutEngines.tsx | 11 +++++------ src/new_fields/CursorField.ts | 5 ++++- src/new_fields/DateField.ts | 5 ++++- src/new_fields/Doc.ts | 17 ++++++++++++++++- src/new_fields/FieldSymbols.ts | 3 ++- src/new_fields/HtmlField.ts | 5 ++++- src/new_fields/IconField.ts | 5 ++++- src/new_fields/InkField.ts | 5 ++++- src/new_fields/List.ts | 5 ++++- src/new_fields/ObjectField.ts | 3 ++- src/new_fields/Proxy.ts | 5 ++++- src/new_fields/RefField.ts | 3 ++- src/new_fields/RichTextField.ts | 5 ++++- src/new_fields/SchemaHeaderField.ts | 5 ++++- src/new_fields/ScriptField.ts | 5 ++++- src/new_fields/URLField.ts | 5 ++++- 17 files changed, 75 insertions(+), 22 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts index f3365e73d..076516977 100644 --- a/src/client/northstar/dash-fields/HistogramField.ts +++ b/src/client/northstar/dash-fields/HistogramField.ts @@ -7,7 +7,7 @@ import { ObjectField } from "../../../new_fields/ObjectField"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { OmitKeys } from "../../../Utils"; import { Deserializable } from "../../util/SerializationHelper"; -import { Copy, ToScriptString } from "../../../new_fields/FieldSymbols"; +import { Copy, ToScriptString, ToString } from "../../../new_fields/FieldSymbols"; function serialize(field: HistogramField) { const obj = OmitKeys(field, ['Links', 'BrushLinks', 'Result', 'BrushColors', 'FilterModels', 'FilterOperand']).omit; @@ -60,4 +60,7 @@ export class HistogramField extends ObjectField { [ToScriptString]() { return this.toString(); } + [ToString]() { + return this.toString(); + } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 8c8da63cc..5924c3afb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -7,8 +7,10 @@ import { OverlayView, OverlayElementOptions } from "../../OverlayView"; import { emptyFunction } from "../../../../Utils"; import React = require("react"); import { ObservableMap, runInAction } from "mobx"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Id, ToString } from "../../../../new_fields/FieldSymbols"; import { DateField } from "../../../../new_fields/DateField"; +import { ObjectField } from "../../../../new_fields/ObjectField"; +import { RefField } from "../../../../new_fields/RefField"; interface PivotData { type: string; @@ -35,11 +37,8 @@ export interface ViewDefResult { } function toLabel(target: FieldResult) { - if (target instanceof DateField) { - const date = DateCast(target).date; - if (date) { - return `${date.toDateString()} ${date.toTimeString()}`; - } + if (target instanceof ObjectField || target instanceof RefField) { + return target[ToString](); } return String(target); } diff --git a/src/new_fields/CursorField.ts b/src/new_fields/CursorField.ts index fd86031a8..28467377b 100644 --- a/src/new_fields/CursorField.ts +++ b/src/new_fields/CursorField.ts @@ -2,7 +2,7 @@ import { ObjectField } from "./ObjectField"; import { observable } from "mobx"; import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, createSimpleSchema, object, date } from "serializr"; -import { OnUpdate, ToScriptString, Copy } from "./FieldSymbols"; +import { OnUpdate, ToScriptString, ToString, Copy } from "./FieldSymbols"; export type CursorPosition = { x: number, @@ -60,4 +60,7 @@ export default class CursorField extends ObjectField { [ToScriptString]() { return "invalid"; } + [ToString]() { + return "invalid"; + } } \ No newline at end of file diff --git a/src/new_fields/DateField.ts b/src/new_fields/DateField.ts index 4f999e5e8..a925148c2 100644 --- a/src/new_fields/DateField.ts +++ b/src/new_fields/DateField.ts @@ -1,7 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, date } from "serializr"; import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString } from "./FieldSymbols"; +import { Copy, ToScriptString, ToString } from "./FieldSymbols"; import { scriptingGlobal, Scripting } from "../client/util/Scripting"; @scriptingGlobal @@ -26,6 +26,9 @@ export class DateField extends ObjectField { [ToScriptString]() { return `new DateField(new Date(${this.date.toISOString()}))`; } + [ToString]() { + return this.date.toISOString(); + } } Scripting.addGlobal(function d(...dateArgs: any[]) { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 29834256a..fa5ca1489 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -4,7 +4,7 @@ import { DocServer } from "../client/DocServer"; import { DocumentType } from "../client/documents/DocumentTypes"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; -import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, Update } from "./FieldSymbols"; +import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; import { List } from "./List"; import { ObjectField } from "./ObjectField"; import { PrefetchProxy, ProxyField } from "./Proxy"; @@ -36,6 +36,18 @@ export namespace Field { return field[ToScriptString](); } } + export function toString(field: Field): string { + if (typeof field === "string") { + return `"${field}"`; + } else if (typeof field === "number" || typeof field === "boolean") { + return String(field); + } else if (field instanceof ObjectField) { + return field[ToString](); + } else if (field instanceof RefField) { + return field[ToString](); + } + return "-invalid field-"; + } export function IsField(field: any): field is Field; export function IsField(field: any, includeUndefined: true): field is Field | undefined; export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined { @@ -156,6 +168,9 @@ export class Doc extends RefField { [ToScriptString]() { return "invalid"; } + [ToString]() { + return "Doc"; + } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; public static CurrentUserEmail: string = ""; diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts index b5b3aa588..4aadb81a2 100644 --- a/src/new_fields/FieldSymbols.ts +++ b/src/new_fields/FieldSymbols.ts @@ -7,4 +7,5 @@ export const Id = Symbol("Id"); export const OnUpdate = Symbol("OnUpdate"); export const Parent = Symbol("Parent"); export const Copy = Symbol("Copy"); -export const ToScriptString = Symbol("ToScriptString"); \ No newline at end of file +export const ToScriptString = Symbol("ToScriptString"); +export const ToString = Symbol("ToString"); \ No newline at end of file diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts index f952acff9..6e8bba977 100644 --- a/src/new_fields/HtmlField.ts +++ b/src/new_fields/HtmlField.ts @@ -1,7 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, primitive } from "serializr"; import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString } from "./FieldSymbols"; +import { Copy, ToScriptString, ToString} from "./FieldSymbols"; @Deserializable("html") export class HtmlField extends ObjectField { @@ -20,4 +20,7 @@ export class HtmlField extends ObjectField { [ToScriptString]() { return "invalid"; } + [ToString]() { + return this.html; + } } diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts index 62b2cd254..76c4ddf1b 100644 --- a/src/new_fields/IconField.ts +++ b/src/new_fields/IconField.ts @@ -1,7 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, primitive } from "serializr"; import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString } from "./FieldSymbols"; +import { Copy, ToScriptString, ToString } from "./FieldSymbols"; @Deserializable("icon") export class IconField extends ObjectField { @@ -20,4 +20,7 @@ export class IconField extends ObjectField { [ToScriptString]() { return "invalid"; } + [ToString]() { + return "ICONfield"; + } } diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts index 01493bcc2..4a44b4f55 100644 --- a/src/new_fields/InkField.ts +++ b/src/new_fields/InkField.ts @@ -1,7 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, custom, createSimpleSchema, list, object, map } from "serializr"; import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString } from "./FieldSymbols"; +import { Copy, ToScriptString, ToString } from "./FieldSymbols"; export enum InkTool { None, @@ -45,4 +45,7 @@ export class InkField extends ObjectField { [ToScriptString]() { return "invalid"; } + [ToString]() { + return "InkField"; + } } diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts index bb48b1bb3..a43f11e82 100644 --- a/src/new_fields/List.ts +++ b/src/new_fields/List.ts @@ -6,7 +6,7 @@ import { observable, action } from "mobx"; import { ObjectField } from "./ObjectField"; import { RefField } from "./RefField"; import { ProxyField } from "./Proxy"; -import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, Copy } from "./FieldSymbols"; +import { Self, Update, Parent, OnUpdate, SelfProxy, ToScriptString, ToString, Copy } from "./FieldSymbols"; import { Scripting } from "../client/util/Scripting"; const listHandlers: any = { @@ -292,6 +292,9 @@ class ListImpl extends ObjectField { [ToScriptString]() { return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; } + [ToString]() { + return "List"; + } } export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; export const List: { new (fields?: T[]): List } = ListImpl as any; diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts index 65ada91c0..b693c8c98 100644 --- a/src/new_fields/ObjectField.ts +++ b/src/new_fields/ObjectField.ts @@ -1,6 +1,6 @@ import { Doc } from "./Doc"; import { RefField } from "./RefField"; -import { OnUpdate, Parent, Copy, ToScriptString } from "./FieldSymbols"; +import { OnUpdate, Parent, Copy, ToScriptString, ToString } from "./FieldSymbols"; import { Scripting } from "../client/util/Scripting"; export abstract class ObjectField { @@ -9,6 +9,7 @@ export abstract class ObjectField { abstract [Copy](): ObjectField; abstract [ToScriptString](): string; + abstract [ToString](): string; } export namespace ObjectField { diff --git a/src/new_fields/Proxy.ts b/src/new_fields/Proxy.ts index c6292e37c..d50c0f14e 100644 --- a/src/new_fields/Proxy.ts +++ b/src/new_fields/Proxy.ts @@ -5,7 +5,7 @@ import { observable, action } from "mobx"; import { DocServer } from "../client/DocServer"; import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; -import { Id, Copy, ToScriptString } from "./FieldSymbols"; +import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; import { Plugins } from "./util"; @@ -32,6 +32,9 @@ export class ProxyField extends ObjectField { [ToScriptString]() { return "invalid"; } + [ToString]() { + return "ProxyField"; + } @serializable(primitive()) readonly fieldId: string = ""; diff --git a/src/new_fields/RefField.ts b/src/new_fields/RefField.ts index f7bea8c94..b6ef69750 100644 --- a/src/new_fields/RefField.ts +++ b/src/new_fields/RefField.ts @@ -1,6 +1,6 @@ import { serializable, primitive, alias } from "serializr"; import { Utils } from "../Utils"; -import { Id, HandleUpdate, ToScriptString } from "./FieldSymbols"; +import { Id, HandleUpdate, ToScriptString, ToString } from "./FieldSymbols"; import { afterDocDeserialize } from "../client/util/SerializationHelper"; export type FieldId = string; @@ -17,4 +17,5 @@ export abstract class RefField { protected [HandleUpdate]?(diff: any): void | Promise; abstract [ToScriptString](): string; + abstract [ToString](): string; } diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index fd5459876..a0f21f45e 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -1,7 +1,7 @@ import { ObjectField } from "./ObjectField"; import { serializable } from "serializr"; import { Deserializable } from "../client/util/SerializationHelper"; -import { Copy, ToScriptString } from "./FieldSymbols"; +import { Copy, ToScriptString, ToString } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; @scriptingGlobal @@ -26,5 +26,8 @@ export class RichTextField extends ObjectField { [ToScriptString]() { return `new RichTextField("${this.Data}", "${this.Text}")`; } + [ToString]() { + return this.Text; + } } \ No newline at end of file diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts index 42a8485ac..07c90f5a2 100644 --- a/src/new_fields/SchemaHeaderField.ts +++ b/src/new_fields/SchemaHeaderField.ts @@ -1,7 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, primitive } from "serializr"; import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, OnUpdate } from "./FieldSymbols"; +import { Copy, ToScriptString, ToString, OnUpdate } from "./FieldSymbols"; import { scriptingGlobal } from "../client/util/Scripting"; import { ColumnType } from "../client/views/collections/CollectionSchemaView"; @@ -116,4 +116,7 @@ export class SchemaHeaderField extends ObjectField { [ToScriptString]() { return `invalid`; } + [ToString]() { + return `SchemaHeaderField`; + } } \ No newline at end of file diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts index b5ad4a7f6..09a18c258 100644 --- a/src/new_fields/ScriptField.ts +++ b/src/new_fields/ScriptField.ts @@ -1,6 +1,6 @@ import { ObjectField } from "./ObjectField"; import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions } from "../client/util/Scripting"; -import { Copy, ToScriptString, Parent, SelfProxy } from "./FieldSymbols"; +import { Copy, ToScriptString, ToString, Parent, SelfProxy } from "./FieldSymbols"; import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr"; import { Deserializable, autoObject } from "../client/util/SerializationHelper"; import { Doc } from "../new_fields/Doc"; @@ -101,6 +101,9 @@ export class ScriptField extends ObjectField { [ToScriptString]() { return "script field"; } + [ToString]() { + return "script field"; + } public static CompileScript(script: string, params: object = {}, addReturn = false) { const compiled = CompileScript(script, { params: { this: Doc.name, _last_: "any", ...params }, diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts index 35ef6dd02..cfab36906 100644 --- a/src/new_fields/URLField.ts +++ b/src/new_fields/URLField.ts @@ -1,7 +1,7 @@ import { Deserializable } from "../client/util/SerializationHelper"; import { serializable, custom } from "serializr"; import { ObjectField } from "./ObjectField"; -import { ToScriptString, Copy } from "./FieldSymbols"; +import { ToScriptString, ToString, Copy } from "./FieldSymbols"; import { Scripting, scriptingGlobal } from "../client/util/Scripting"; function url() { @@ -32,6 +32,9 @@ export abstract class URLField extends ObjectField { [ToScriptString]() { return `new ${this.constructor.name}("${this.url.href}")`; } + [ToString]() { + return this.url.href; + } [Copy](): this { return new (this.constructor as any)(this.url); -- cgit v1.2.3-70-g09d2 From 119f90cc5c4ef773133f1bfd18a813dde9487f94 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 21 Jan 2020 10:26:45 -0500 Subject: buxton templating fixes --- .../views/collections/CollectionPivotView.tsx | 5 ++-- .../views/collections/CollectionTreeView.tsx | 29 +++++++++++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 07ad5683c..816939a41 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -43,8 +43,9 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { (childDetailed) => DocCastAsync(childDetailed).then(childDetailed => { if (childDetailed instanceof Doc) { - let captured: { [name: string]: Field } = {}; - captured["childDetailed"] = new PrefetchProxy(childDetailed); + const targetKey = "childDetailed"; + const captured: { [name: string]: Field } = {}; + captured[targetKey] = new PrefetchProxy(childDetailed); const openDocText = "const alias = getAlias(this); Doc.ApplyTemplateTo(childDetailed, alias, 'layout_detailed'); useRightSplit(alias); "; const openDocScript = CompileScript(openDocText, { params: { this: Doc.name, heading: "boolean", context: Doc.name }, diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 3a208038c..4a6dffc1c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -634,22 +634,27 @@ export class CollectionTreeView extends CollectionSubView(Document) { } ContextMenu.Instance.addItem({ description: "Buxton Layout", icon: "eye", event: () => { - const { TextDocument, ImageDocument } = Docs.Create; + const { TextDocument, ImageDocument, MulticolumnDocument } = Docs.Create; + const { Document } = this.props; + const fallback = "http://www.cs.brown.edu/~bcz/face.gif"; const wrapper = Docs.Create.StackingDocument([ - ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { title: "hero" }), + ImageDocument(fallback, { title: "hero" }), + MulticolumnDocument([], { title: "data", height: 100 }), ...["short_description", "year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key })) ], { autoHeight: true, chromeStatus: "disabled" }); wrapper.disableLOD = true; - makeTemplate(wrapper, true); - const detailedLayout = Doc.MakeAlias(wrapper); - const cardLayout = ImageBox.LayoutString("hero"); - this.childLayoutPairs.forEach(({ layout }) => { - const proto = Doc.GetProto(layout); - proto.layout = cardLayout; - proto.layout_detailed = detailedLayout; - layout.showTitle = "title"; - layout.showTitleHover = "titlehover"; - }); + wrapper.isTemplateDoc = makeTemplate(wrapper, true); + + const cardLayout = ImageDocument(fallback); + const proto = Doc.GetProto(cardLayout); + proto.layout = ImageBox.LayoutString("hero"); + cardLayout.showTitle = "title"; + cardLayout.showTitleHover = "titlehover"; + cardLayout.isTemplateField = true; // make this document act like a template field + cardLayout.isTemplateDoc = true; // but it's also a template doc itself... a little weird + + Document.childLayout = cardLayout; + Document.childDetailed = wrapper; } }); const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); -- cgit v1.2.3-70-g09d2 From 691d5500c0722d0af9ef3b3ab9036042970e75e9 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 21 Jan 2020 13:35:06 -0500 Subject: cffview nested panning --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 5 +++-- src/client/views/nodes/DocumentView.tsx | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 01b978c81..132bf9c8e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -347,6 +347,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._lastX = pt.pageX; this._lastY = pt.pageY; e.preventDefault(); + e.stopPropagation(); } else { e.preventDefault(); @@ -362,7 +363,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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 }); + const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { title: "ink stroke", x: B.x, y: B.y, width: B.width, height: B.height }); this.addDocument(inkDoc); e.stopPropagation(); break; @@ -384,7 +385,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } return pass; }); - this.addDocument(Docs.Create.FreeformDocument(sel, { x: bounds.x, y: bounds.y, width: bWidth, height: bHeight, panX: 0, panY: 0 })); + this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, width: bWidth, height: bHeight, panX: 0, panY: 0 })); sel.forEach(d => this.props.removeDocument(d)); break; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c35a44860..e0913b154 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -315,10 +315,10 @@ export class DocumentView extends DocComponent(Docu handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (this.Document.onPointerDown) return; + const touch = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true)[0]; + this._downX = touch.clientX; + this._downY = touch.clientY; if (!e.nativeEvent.cancelBubble) { - const touch = InteractionUtils.GetMyTargetTouches(me, 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") { -- cgit v1.2.3-70-g09d2 From 74bad9d9deadeaf4959906e89457c16ed564fd01 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 21 Jan 2020 16:09:30 -0500 Subject: initial commit --- src/client/documents/Documents.ts | 1 + .../views/collections/CollectionPivotView.tsx | 18 +++++-------- .../views/collections/CollectionTreeView.tsx | 7 +++-- src/new_fields/Doc.ts | 30 ++++++++++++++++++++++ 4 files changed, 41 insertions(+), 15 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 91085cd0f..6cb3f604e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -129,6 +129,7 @@ export interface DocumentOptions { isExpanded?: boolean; // is linear view expanded textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded + treeViewChecked?: ScriptField; // computes whether or not the checkbox for this facet is checked } class EmptyBox { diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 816939a41..546fa3744 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -10,7 +10,7 @@ import { CollectionTreeView } from "./CollectionTreeView"; import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { Docs } from "../../documents/Documents"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { CompileScript } from "../../util/Scripting"; +import { CompileScript, Scripting } from "../../util/Scripting"; import { anchorPoints, Flyout } from "../TemplateMenu"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { List } from "../../../new_fields/List"; @@ -43,9 +43,8 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { (childDetailed) => DocCastAsync(childDetailed).then(childDetailed => { if (childDetailed instanceof Doc) { - const targetKey = "childDetailed"; const captured: { [name: string]: Field } = {}; - captured[targetKey] = new PrefetchProxy(childDetailed); + captured.childDetailed = new PrefetchProxy(childDetailed); const openDocText = "const alias = getAlias(this); Doc.ApplyTemplateTo(childDetailed, alias, 'layout_detailed'); useRightSplit(alias); "; const openDocScript = CompileScript(openDocText, { params: { this: Doc.name, heading: "boolean", context: Doc.name }, @@ -76,6 +75,9 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { return facets.toArray(); } + /** + * Responds to clicking the check box in the flyout menu + */ facetClick = (facet: string) => { const facetCollection = this.props.Document.facetCollection; if (facetCollection instanceof Doc) { @@ -84,15 +86,9 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { //Doc.RemoveDocFromList(facetCollection, "data", DocListCast(facetCollection.data)[found]); (facetCollection.data as List).splice(found, 1); } else { - const facetValues = new Set(); - this.childDocs.forEach(child => { - Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facet]?.toString() || "(null)")); - facetValues.add(child[facet]?.toString() || "(null)"); - }); - - const newFacetVals = facetValues.toArray().sort().map(val => Docs.Create.TextDocument({ title: val.toString() })); - const newFacet = Docs.Create.FreeformDocument(newFacetVals, { title: facet, treeViewOpen: true, isFacetFilter: true }); + const newFacet = Docs.Create.FreeformDocument([], { title: facet, treeViewOpen: true, isFacetFilter: true }); Doc.AddDocToList(facetCollection, "data", newFacet); + newFacet.data = ScriptField.MakeFunction("readFacetData()", { params: {} }); } } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 4a6dffc1c..2e9f0379c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -373,12 +373,12 @@ class TreeView extends React.Component { @action bulletClick = (e: React.MouseEvent) => { if (this.props.onCheckedClick && this.props.document.type !== DocumentType.COL) { - this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check"; + // this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check"; ScriptCast(this.props.onCheckedClick).script.run({ this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document, heading: this.props.containingCollection.title, checked: this.props.document.treeViewChecked === "check" ? false : this.props.document.treeViewChecked === "x" ? "x" : "none", - context: this.props.treeViewId + context: this.props.treeViewId, }, console.log); } else { this.treeViewOpen = !this.treeViewOpen; @@ -634,12 +634,11 @@ export class CollectionTreeView extends CollectionSubView(Document) { } ContextMenu.Instance.addItem({ description: "Buxton Layout", icon: "eye", event: () => { - const { TextDocument, ImageDocument, MulticolumnDocument } = Docs.Create; + const { TextDocument, ImageDocument } = Docs.Create; const { Document } = this.props; const fallback = "http://www.cs.brown.edu/~bcz/face.gif"; const wrapper = Docs.Create.StackingDocument([ ImageDocument(fallback, { title: "hero" }), - MulticolumnDocument([], { title: "data", height: 100 }), ...["short_description", "year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key })) ], { autoHeight: true, chromeStatus: "disabled" }); wrapper.disableLOD = true; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index fa5ca1489..74b8df1a0 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -16,6 +16,7 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u import { intersectRect } from "../Utils"; import { UndoManager } from "../client/util/UndoManager"; import { computedFn } from "mobx-utils"; +import { Docs } from "../client/documents/Documents"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -823,4 +824,33 @@ Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: an } const docFilterText = Doc.MakeDocFilter(docFilters); container.viewSpecScript = docFilterText ? ScriptField.MakeFunction(docFilterText, { doc: Doc.name }) : undefined; +}); + +Scripting.addGlobal(function readFacetData(target: Doc, facet: string) { + const facetValues = new Set(); + DocListCast(target.dataField).forEach(child => { + Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facet]?.toString() || "(null)")); + facetValues.add(child[facet]?.toString() || "(null)"); + }); + return Array.from(facetValues).sort().map(val => { + const capturedVariables: { [name: string]: Field } = {}; + capturedVariables.facet = val; + capturedVariables.container = target; + return Docs.Create.TextDocument({ + title: val.toString(), + treeViewChecked: ScriptField.MakeFunction("readCheckedState(container, facetValue)", { capturedVariables }) + }); + }); +}); + +Scripting.addGlobal(function readCheckedState(container: Doc, facetValue: string) { + const docFilters = Cast(container.docFilter, listSpec("string"), []); + for (let i = 0; i < docFilters.length; i += 3) { + const key = docFilters[i]; + const value = docFilters[i + 1]; + if (key === facetValue) { + return value; + } + } + return false; }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 4eb5fde5f9b14d1b13c1995b9a1110347a57a582 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 21 Jan 2020 17:00:04 -0500 Subject: fixed templating with collection views set? --- src/client/views/collections/CollectionView.tsx | 2 +- src/new_fields/Doc.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index cc8ae2ac1..a99bd29d6 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -92,7 +92,7 @@ export class CollectionView extends Touchable { public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } - @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.Document, this.props.fieldKey); } get collectionViewType(): CollectionViewType | undefined { if (!this.extensionDoc) return CollectionViewType.Invalid; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index fa5ca1489..6b0ec3fd3 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -608,6 +608,8 @@ export namespace Doc { if (fieldTemplate.layout instanceof Doc) { fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout); } + const fieldLayoutKey = StrCast(Doc.LayoutField(fieldLayoutDoc))?.split("'")[1] + const fieldLayoutExt = fieldLayoutKey && Doc.MakeDelegate(fieldTemplate[fieldLayoutKey + "_ext"] as Doc); fieldTemplate.templateField = metadataFieldName; fieldTemplate.title = metadataFieldName; @@ -629,6 +631,7 @@ export namespace Doc { !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (Doc.GetProto(templateDataDoc)[metadataFieldName] = ObjectField.MakeCopy(data)); const layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldName}'}`); const layoutDelegate = Doc.Layout(fieldTemplate); + layoutDelegate[metadataFieldName + "_ext"] = fieldLayoutExt; layoutDelegate.layout = layout; fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor; @@ -672,7 +675,7 @@ export namespace Doc { // the document containing the view layout information - will be the Document itself unless the Document has // a layout field. In that case, all layout information comes from there unless overriden by Document - export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; } + export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? Doc.LayoutField(doc) as Doc : doc; } export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; } export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } const manager = new DocData(); -- cgit v1.2.3-70-g09d2 From 7fffe13954ed0113bf1b2f9318518d015dc63844 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 21 Jan 2020 17:07:35 -0500 Subject: made onClick override default link follow behavior --- src/client/views/collections/CollectionView.tsx | 1 - src/client/views/nodes/DocuLinkBox.tsx | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index a99bd29d6..430aecef4 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -91,7 +91,6 @@ export class CollectionView extends Touchable { @observable private static _safeMode = false; public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.Document, this.props.fieldKey); } get collectionViewType(): CollectionViewType | undefined { diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 0d4d50c59..a4a9a62aa 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -61,10 +61,12 @@ export class DocuLinkBox extends DocComponent(Doc } } onClick = (e: React.MouseEvent) => { - if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) { - DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false); + if (!this.props.Document.onClick) { + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) { + DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false); + } + e.stopPropagation(); } - e.stopPropagation(); } render() { -- cgit v1.2.3-70-g09d2 From 8a4a8212b024e2804596a08ea820be646c9a7c0f Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 21 Jan 2020 19:02:43 -0500 Subject: functional but incomplete updating logic --- src/client/documents/Documents.ts | 2 - .../views/collections/CollectionPivotView.tsx | 30 ++++++++---- src/client/views/collections/CollectionSubView.tsx | 10 +++- .../views/collections/CollectionTreeView.tsx | 2 +- src/new_fields/Doc.ts | 55 +++++++++++----------- src/new_fields/ScriptField.ts | 13 ++--- 6 files changed, 67 insertions(+), 45 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6cb3f604e..d7292837c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -129,7 +129,6 @@ export interface DocumentOptions { isExpanded?: boolean; // is linear view expanded textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded - treeViewChecked?: ScriptField; // computes whether or not the checkbox for this facet is checked } class EmptyBox { @@ -789,7 +788,6 @@ 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/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 546fa3744..f31f1aba6 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -9,7 +9,7 @@ import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormV import { CollectionTreeView } from "./CollectionTreeView"; import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; import { Docs } from "../../documents/Documents"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; import { CompileScript, Scripting } from "../../util/Scripting"; import { anchorPoints, Flyout } from "../TemplateMenu"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -25,7 +25,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { } componentDidMount() { this.props.Document.freeformLayoutEngine = "pivot"; - if (true || !this.props.Document.facetCollection) { + if (!this.props.Document.facetCollection) { const facetCollection = Docs.Create.FreeformDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true }); facetCollection.target = this.props.Document; @@ -38,7 +38,6 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { if (script.compiled) { facetCollection.onCheckedClick = new ScriptField(script); } - this._narrativeDisposer = reaction(() => this.props.Document.childDetailed, (childDetailed) => DocCastAsync(childDetailed).then(childDetailed => { @@ -78,17 +77,32 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { /** * Responds to clicking the check box in the flyout menu */ - facetClick = (facet: string) => { - const facetCollection = this.props.Document.facetCollection; + facetClick = (facetHeader: string) => { + const { Document, fieldKey } = this.props; + const facetCollection = Document.facetCollection; if (facetCollection instanceof Doc) { - const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facet); + const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader); if (found !== -1) { //Doc.RemoveDocFromList(facetCollection, "data", DocListCast(facetCollection.data)[found]); (facetCollection.data as List).splice(found, 1); } else { - const newFacet = Docs.Create.FreeformDocument([], { title: facet, treeViewOpen: true, isFacetFilter: true }); + const newFacet = Docs.Create.FreeformDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); Doc.AddDocToList(facetCollection, "data", newFacet); - newFacet.data = ScriptField.MakeFunction("readFacetData()", { params: {} }); + const { dataDoc } = this; + const capturedVariables = { + layoutDoc: Document, + dataDoc, + dataKey: fieldKey, + facetHeader + }; + const params = { + layoutDoc: Doc.name, + dataDoc: Doc.name, + dataKey: "string", + facetHeader: "string" + }; + newFacet.container = dataDoc; + newFacet.data = ComputedField.MakeFunction("readFacetData(layoutDoc, dataDoc, dataKey, facetHeader)", params, capturedVariables); } } } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5f4ee3669..9357b0507 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -90,7 +90,15 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { // to its children which may be templates. // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { - return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey]; + const { annotationsKey, fieldKey } = this.props; + const { extensionDoc, dataDoc } = this; + if (annotationsKey) { + if (extensionDoc) { + return extensionDoc[annotationsKey]; + } + return undefined; + } + return dataDoc[fieldKey]; } get childLayoutPairs(): { layout: Doc; data: Doc; }[] { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2e9f0379c..ffaad2ddd 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -377,7 +377,7 @@ class TreeView extends React.Component { ScriptCast(this.props.onCheckedClick).script.run({ this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document, heading: this.props.containingCollection.title, - checked: this.props.document.treeViewChecked === "check" ? false : this.props.document.treeViewChecked === "x" ? "x" : "none", + checked: this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check", context: this.props.treeViewId, }, console.log); } else { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 74b8df1a0..69d478a48 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -1,4 +1,4 @@ -import { observable, ObservableMap, runInAction, action, untracked } from "mobx"; +import { observable, ObservableMap, runInAction } from "mobx"; import { alias, map, serializable } from "serializr"; import { DocServer } from "../client/DocServer"; import { DocumentType } from "../client/documents/DocumentTypes"; @@ -11,7 +11,7 @@ import { PrefetchProxy, ProxyField } from "./Proxy"; import { FieldId, RefField } from "./RefField"; import { listSpec } from "./Schema"; import { ComputedField, ScriptField } from "./ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, PromiseValue, StrCast, ToConstructor } from "./Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction } from "./util"; import { intersectRect } from "../Utils"; import { UndoManager } from "../client/util/UndoManager"; @@ -807,16 +807,15 @@ Scripting.addGlobal(function matchFieldValue(doc: Doc, key: string, value: any) } return false; }); -Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers: string) { +Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: string) { const docFilters = Cast(container.docFilter, listSpec("string"), []); - let found = false; - for (let i = 0; i < docFilters.length && !found; i += 3) { + for (let i = 0; i < docFilters.length; i += 3) { if (docFilters[i] === key && docFilters[i + 1] === value) { - found = true; docFilters.splice(i, 3); + break; } } - if (!found || modifiers !== "none") { + if (modifiers !== undefined) { docFilters.push(key); docFilters.push(value); docFilters.push(modifiers); @@ -826,31 +825,33 @@ Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: an container.viewSpecScript = docFilterText ? ScriptField.MakeFunction(docFilterText, { doc: Doc.name }) : undefined; }); -Scripting.addGlobal(function readFacetData(target: Doc, facet: string) { +Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) { const facetValues = new Set(); - DocListCast(target.dataField).forEach(child => { - Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facet]?.toString() || "(null)")); - facetValues.add(child[facet]?.toString() || "(null)"); - }); - return Array.from(facetValues).sort().map(val => { - const capturedVariables: { [name: string]: Field } = {}; - capturedVariables.facet = val; - capturedVariables.container = target; - return Docs.Create.TextDocument({ - title: val.toString(), - treeViewChecked: ScriptField.MakeFunction("readCheckedState(container, facetValue)", { capturedVariables }) - }); + DocListCast(dataDoc[dataKey]).forEach(child => { + Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facetHeader]?.toString() || "(null)")); + facetValues.add(child[facetHeader]?.toString() || "(null)"); }); + const text = "determineCheckedState(layoutDoc, facetHeader, facetValue)"; + const params = { + layoutDoc: Doc.name, + facetHeader: "string", + facetValue: "string" + }; + const capturedVariables = { layoutDoc, facetHeader }; + return new List(Array.from(facetValues).sort().map(facetValue => { + const value = Docs.Create.TextDocument({ title: facetValue.toString() }); + value.treeViewChecked = ComputedField.MakeFunction(text, params, { ...capturedVariables, facetValue }); + return value; + })); }); -Scripting.addGlobal(function readCheckedState(container: Doc, facetValue: string) { - const docFilters = Cast(container.docFilter, listSpec("string"), []); +Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { + const docFilters = Cast(layoutDoc.docFilter, listSpec("string"), []); for (let i = 0; i < docFilters.length; i += 3) { - const key = docFilters[i]; - const value = docFilters[i + 1]; - if (key === facetValue) { - return value; + const [header, value, state] = docFilters.slice(i, i + 3); + if (header === facetHeader && value === facetValue) { + return state; } } - return false; + return undefined; }); \ No newline at end of file diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts index 09a18c258..f8a8d1226 100644 --- a/src/new_fields/ScriptField.ts +++ b/src/new_fields/ScriptField.ts @@ -3,7 +3,7 @@ import { CompiledScript, CompileScript, scriptingGlobal, ScriptOptions } from ". import { Copy, ToScriptString, ToString, Parent, SelfProxy } from "./FieldSymbols"; import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr"; import { Deserializable, autoObject } from "../client/util/SerializationHelper"; -import { Doc } from "../new_fields/Doc"; +import { Doc, Field } from "../new_fields/Doc"; import { Plugins } from "./util"; import { computedFn } from "mobx-utils"; import { ProxyField } from "./Proxy"; @@ -104,12 +104,13 @@ export class ScriptField extends ObjectField { [ToString]() { return "script field"; } - public static CompileScript(script: string, params: object = {}, addReturn = false) { + public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) { const compiled = CompileScript(script, { params: { this: Doc.name, _last_: "any", ...params }, typecheck: false, editable: true, - addReturn: addReturn + addReturn: addReturn, + capturedVariables }); return compiled; } @@ -130,12 +131,12 @@ export class ComputedField extends ScriptField { _lastComputedResult: any; //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc value = computedFn((doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, _last_: this._lastComputedResult }, console.log).result); - public static MakeScript(script: string, params: object = {}, ) { + public static MakeScript(script: string, params: object = {}) { const compiled = ScriptField.CompileScript(script, params, false); return compiled.compiled ? new ComputedField(compiled) : undefined; } - public static MakeFunction(script: string, params: object = {}) { - const compiled = ScriptField.CompileScript(script, params, true); + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) { + const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); return compiled.compiled ? new ComputedField(compiled) : undefined; } } -- cgit v1.2.3-70-g09d2 From 377837d63289c5e1a155e6ab40f008cc0507b088 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 21 Jan 2020 19:21:45 -0500 Subject: fixed error with dependencies --- .../views/collections/CollectionTreeView.tsx | 52 +++++++++++++++++----- src/new_fields/Doc.ts | 32 ------------- 2 files changed, 41 insertions(+), 43 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ffaad2ddd..48b5d91e9 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,19 +1,22 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faAngleRight, faArrowsAltH, faBell, faCamera, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faExpand, faMinus, faPlus, faTrash, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, untracked, runInAction } from "mobx"; +import { action, computed, observable, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, Field, HeightSym, WidthSym } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { Document, listSpec } from '../../../new_fields/Schema'; import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast, StrCast, ScriptCast } from '../../../new_fields/Types'; -import { emptyFunction, Utils, returnFalse, emptyPath } from '../../../Utils'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../new_fields/Types'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { emptyFunction, emptyPath, returnFalse, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; +import { makeTemplate } from '../../util/DropConverter'; +import { Scripting } from '../../util/Scripting'; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; @@ -21,18 +24,14 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from "../EditableView"; import { MainView } from '../MainView'; +import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; +import { ImageBox } from '../nodes/ImageBox'; import { KeyValueBox } from '../nodes/KeyValueBox'; +import { ScriptBox } from '../ScriptBox'; import { Templates } from '../Templates'; -import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { ScriptBox } from '../ScriptBox'; -import { ImageBox } from '../nodes/ImageBox'; -import { makeTemplate } from '../../util/DropConverter'; -import { CollectionDockingView } from './CollectionDockingView'; -import { CollectionViewType } from './CollectionView'; export interface TreeViewProps { @@ -713,4 +712,35 @@ export class CollectionTreeView extends CollectionSubView(Document) {
); } -} \ No newline at end of file +} + +Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) { + const facetValues = new Set(); + DocListCast(dataDoc[dataKey]).forEach(child => { + Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facetHeader]?.toString() || "(null)")); + facetValues.add(child[facetHeader]?.toString() || "(null)"); + }); + const text = "determineCheckedState(layoutDoc, facetHeader, facetValue)"; + const params = { + layoutDoc: Doc.name, + facetHeader: "string", + facetValue: "string" + }; + const capturedVariables = { layoutDoc, facetHeader }; + return new List(Array.from(facetValues).sort().map(facetValue => { + const value = Docs.Create.TextDocument({ title: facetValue.toString() }); + value.treeViewChecked = ComputedField.MakeFunction(text, params, { ...capturedVariables, facetValue }); + return value; + })); +}); + +Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { + const docFilters = Cast(layoutDoc.docFilter, listSpec("string"), []); + for (let i = 0; i < docFilters.length; i += 3) { + const [header, value, state] = docFilters.slice(i, i + 3); + if (header === facetHeader && value === facetValue) { + return state; + } + } + return undefined; +}); \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index d57a94e9f..50dc02e57 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -16,7 +16,6 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u import { intersectRect } from "../Utils"; import { UndoManager } from "../client/util/UndoManager"; import { computedFn } from "mobx-utils"; -import { Docs } from "../client/documents/Documents"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -826,35 +825,4 @@ Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: an } const docFilterText = Doc.MakeDocFilter(docFilters); container.viewSpecScript = docFilterText ? ScriptField.MakeFunction(docFilterText, { doc: Doc.name }) : undefined; -}); - -Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) { - const facetValues = new Set(); - DocListCast(dataDoc[dataKey]).forEach(child => { - Object.keys(Doc.GetProto(child)).forEach(key => child[key] instanceof Doc && facetValues.add((child[key] as Doc)[facetHeader]?.toString() || "(null)")); - facetValues.add(child[facetHeader]?.toString() || "(null)"); - }); - const text = "determineCheckedState(layoutDoc, facetHeader, facetValue)"; - const params = { - layoutDoc: Doc.name, - facetHeader: "string", - facetValue: "string" - }; - const capturedVariables = { layoutDoc, facetHeader }; - return new List(Array.from(facetValues).sort().map(facetValue => { - const value = Docs.Create.TextDocument({ title: facetValue.toString() }); - value.treeViewChecked = ComputedField.MakeFunction(text, params, { ...capturedVariables, facetValue }); - return value; - })); -}); - -Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { - const docFilters = Cast(layoutDoc.docFilter, listSpec("string"), []); - for (let i = 0; i < docFilters.length; i += 3) { - const [header, value, state] = docFilters.slice(i, i + 3); - if (header === facetHeader && value === facetValue) { - return state; - } - } - return undefined; }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 613c2c8de636927fb38a7a32ec6b0427b238db2b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 21 Jan 2020 21:33:42 -0500 Subject: added filter resizer for pivot viewer. fixed click/drag confusion with mainview divider. --- src/client/views/MainView.tsx | 5 +- .../views/collections/CollectionPivotView.scss | 13 +++- .../views/collections/CollectionPivotView.tsx | 90 +++++++++++++--------- 3 files changed, 70 insertions(+), 38 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d7656aba4..0958c434a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -307,8 +307,10 @@ export class MainView extends React.Component { ; } + _canClick = false; onPointerDown = (e: React.PointerEvent) => { if (this._flyoutTranslate) { + this._canClick = true; this._flyoutSizeOnDown = e.clientX; document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -339,11 +341,12 @@ export class MainView extends React.Component { @action onPointerMove = (e: PointerEvent) => { this.flyoutWidth = Math.max(e.clientX, 0); + Math.abs(this.flyoutWidth - this._flyoutSizeOnDown) > 6 && (this._canClick = false); this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30; } @action onPointerUp = (e: PointerEvent) => { - if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4) { + if (Math.abs(e.clientX - this._flyoutSizeOnDown) < 4 && this._canClick) { this.flyoutWidth = this.flyoutWidth < 15 ? 250 : 0; this.flyoutWidth && (this.sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30); } diff --git a/src/client/views/collections/CollectionPivotView.scss b/src/client/views/collections/CollectionPivotView.scss index bd3d6c77b..2ddcc97ea 100644 --- a/src/client/views/collections/CollectionPivotView.scss +++ b/src/client/views/collections/CollectionPivotView.scss @@ -45,7 +45,7 @@ } .collectionPivotView-tree { display:inline-block; - width: 200px; + width: 100%; height: calc(100% - 30px); } } @@ -54,4 +54,15 @@ width: calc(100% - 200px); height: 100%; } + .collectionPivotView-dragger { + background-color: lightgray; + height: 40px; + width: 20px; + position: absolute; + border-radius: 10px; + top: 55%; + border: 1px black solid; + z-index: 2; + left:-10px; + } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index f31f1aba6..8e712e370 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -65,7 +65,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey); } - bodyPanelWidth = () => this.props.PanelWidth() - 200; + bodyPanelWidth = () => this.props.PanelWidth() - this._facetWidth; getTransform = () => this.props.ScreenToLocalTransform().translate(-200, 0); @computed get _allFacets() { @@ -78,39 +78,53 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { * Responds to clicking the check box in the flyout menu */ facetClick = (facetHeader: string) => { - const { Document, fieldKey } = this.props; - const facetCollection = Document.facetCollection; + const facetCollection = this.props.Document.facetCollection; if (facetCollection instanceof Doc) { const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader); if (found !== -1) { - //Doc.RemoveDocFromList(facetCollection, "data", DocListCast(facetCollection.data)[found]); (facetCollection.data as List).splice(found, 1); } else { const newFacet = Docs.Create.FreeformDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); + const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc }; + const params = { layoutDoc: Doc.name, dataDoc: Doc.name, }; + newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables); Doc.AddDocToList(facetCollection, "data", newFacet); - const { dataDoc } = this; - const capturedVariables = { - layoutDoc: Document, - dataDoc, - dataKey: fieldKey, - facetHeader - }; - const params = { - layoutDoc: Doc.name, - dataDoc: Doc.name, - dataKey: "string", - facetHeader: "string" - }; - newFacet.container = dataDoc; - newFacet.data = ComputedField.MakeFunction("readFacetData(layoutDoc, dataDoc, dataKey, facetHeader)", params, capturedVariables); } } } + _canClick = false; + _facetWidthOnDown = 0; + @observable _facetWidth = 200; + onPointerDown = (e: React.PointerEvent) => { + this._canClick = true; + this._facetWidthOnDown = e.screenX; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + e.preventDefault(); + } + + + @action + onPointerMove = (e: PointerEvent) => { + this._facetWidth = Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0); + Math.abs(e.movementX) > 6 && (this._canClick = false); + } + @action + onPointerUp = (e: PointerEvent) => { + if (Math.abs(e.screenX - this._facetWidthOnDown) < 6 && this._canClick) { + this._facetWidth = this._facetWidth < 15 ? 200 : 0; + } + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } render() { const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null); const flyout = ( -
+
{this._allFacets.map(facet => )}
); - return !facetCollection ? (null) :
-
-
e.stopPropagation()}> - -
- Facet Filters - -
-
+ return !facetCollection ? (null) : +
+
+
-
- +
+
e.stopPropagation()}> + +
+ Facet Filters + +
+
+
+
+ +
-
-
- -
-
; +
+ +
+
; } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From cc6b30d2824ad5ff686fe728587d89d17aa3c8b2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 22 Jan 2020 01:00:09 -0500 Subject: fixed pivot viewer to not have multiple columns with the same value. fixed imagebox to show fade views. cleaned up makeMetadataFieldTemplate --- .../CollectionFreeFormLayoutEngines.tsx | 4 +- src/client/views/nodes/ImageBox.tsx | 2 +- src/new_fields/Doc.ts | 76 +++++++++++----------- 3 files changed, 42 insertions(+), 40 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 5924c3afb..5c6336248 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -8,7 +8,6 @@ import { emptyFunction } from "../../../../Utils"; import React = require("react"); import { ObservableMap, runInAction } from "mobx"; import { Id, ToString } from "../../../../new_fields/FieldSymbols"; -import { DateField } from "../../../../new_fields/DateField"; import { ObjectField } from "../../../../new_fields/ObjectField"; import { RefField } from "../../../../new_fields/RefField"; @@ -47,8 +46,9 @@ export function computePivotLayout(poolData: ObservableMap, pivotDo const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 200); const pivotColumnGroups = new Map, Doc[]>(); + const pivotFieldKey = toLabel(pivotDoc.pivotField); for (const doc of childDocs) { - const val = doc[StrCast(pivotDoc.pivotField, "title")]; + const val = Field.toString(doc[pivotFieldKey] as Field); if (val) { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []); pivotColumnGroups.get(val)!.push(doc); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 634555012..4b2a1c1cb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -319,7 +319,7 @@ export class ImageBox extends DocAnnotatableComponent - {fadepath === srcpath ? (null) :
+ {fadepath === srcpath ? (null) :
{ - !templateDataDoc[metadataFieldName] && data instanceof ObjectField && (Doc.GetProto(templateDataDoc)[metadataFieldName] = ObjectField.MakeCopy(data)); - const layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldName}'}`); - const layoutDelegate = Doc.Layout(fieldTemplate); - layoutDelegate[metadataFieldName + "_ext"] = fieldLayoutExt; - layoutDelegate.layout = layout; - fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout; - if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor; - fieldTemplate.proto = templateDataDoc; - // }), 0); + // + // This function converts a generic field layout display into a field layout that displays a specific + // metadata field indicated by the title of the template field (not the default field that it was rendering) + // + export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Doc, suppressTitle: boolean = false): boolean { + + // find the metadata field key that this template field doc will display (indicated by its title) + const metadataFieldKey = StrCast(templateField.title).replace(/^-/, ""); + + // update the original template to mark it as a template + templateField.templateField = metadataFieldKey; + templateField.isTemplateField = true; + templateField.title = metadataFieldKey; + templateField.showTitle = suppressTitle ? undefined : "title"; + + // move any data that the template field had been rendering over to the template doc so that things will + // appear the same after the conversion to a template has completed. (otherwise, there would be no data for the template to render) + // note: this will not overwrite any field that already exists on the template doc at the field key + if (!templateDoc[metadataFieldKey] && templateField.data instanceof ObjectField) { + (Doc.GetProto(templateDoc)[metadataFieldKey] = ObjectField.MakeCopy(templateField.data)); + } + + // get the layout string that the template uses to specify its layout + const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField))); + + // change itto render the target metadata field instead of what it was rendering before and assign it to the template field layout document. + Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`); + + // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino) + Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc); + + if (templateField.backgroundColor !== templateDoc.defaultBackgroundColor) { + templateField.defaultBackgroundColor = templateField.backgroundColor; + } + + // finally, make the templateField be a delegate of the templateDoc so that it can find all the fields needed to render itselt + // (note that this is only useful to see the template doc itself which may not be necessary for many use cases) + templateField.proto = templateDoc; return true; } -- cgit v1.2.3-70-g09d2 From 5a6f6868dc25b2b24c6c7f4a7bc25670e6a35f47 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 22 Jan 2020 12:08:20 -0500 Subject: fixed template/scripting problem. added collectionDoc parameter for onClick scripts. made buxton layout update hero image on click. --- src/client/documents/Documents.ts | 3 ++- src/client/util/DropConverter.ts | 4 ++-- .../collections/CollectionStackingViewFieldColumn.tsx | 2 +- src/client/views/collections/CollectionTreeView.tsx | 13 +++++++------ .../CollectionMulticolumnView.tsx | 6 +++++- src/client/views/nodes/DocumentView.tsx | 19 ++++++++++--------- src/new_fields/Doc.ts | 5 +---- 7 files changed, 28 insertions(+), 24 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d7292837c..634c223a7 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -88,6 +88,7 @@ export interface DocumentOptions { defaultBackgroundColor?: string; dropAction?: dropActionType; chromeStatus?: string; + disableLOD?: boolean; columnWidth?: number; fontSize?: number; curPage?: number; @@ -330,7 +331,7 @@ export namespace Docs { */ export namespace Create { - const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "nativeWidth", "nativeHeight", "dropAction", "annotationOn", "forceActive", "fitWidth"]; + const delegateKeys = ["x", "y", "width", "height", "panX", "panY", "nativeWidth", "nativeHeight", "dropAction", "annotationOn", "forceActive", "fitWidth", "diableLOD"]; /** * This function receives the relevant document prototype and uses diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index ff0e19347..3e2cc6a2e 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -7,7 +7,7 @@ import { StrCast } from "../../new_fields/Types"; import { Docs } from "../documents/Documents"; import { ScriptField } from "../../new_fields/ScriptField"; -export function makeTemplate(doc: Doc, suppressTitle = false): boolean { +export function makeTemplate(doc: Doc): boolean { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^']*'}/)![0]; const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, ""); @@ -16,7 +16,7 @@ export function makeTemplate(doc: Doc, suppressTitle = false): boolean { docs.forEach(d => { if (!StrCast(d.title).startsWith("-")) { any = true; - Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc), suppressTitle); + Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)); } else if (d.type === DocumentType.COL) { any = makeTemplate(d) || any; } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 229a23294..f0dcadb5b 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -145,7 +145,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { - const { TextDocument, ImageDocument } = Docs.Create; + const { TextDocument, ImageDocument, MulticolumnDocument } = Docs.Create; const { Document } = this.props; const fallback = "http://www.cs.brown.edu/~bcz/face.gif"; + const imageRack = MulticolumnDocument([], { title: "data", height: 100 }); const wrapper = Docs.Create.StackingDocument([ ImageDocument(fallback, { title: "hero" }), + imageRack, ...["short_description", "year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key })) - ], { autoHeight: true, chromeStatus: "disabled" }); - wrapper.disableLOD = true; - wrapper.isTemplateDoc = makeTemplate(wrapper, true); + ], { autoHeight: true, chromeStatus: "disabled", disableLOD: true, title: "detailed layout stack" }); + wrapper.isTemplateDoc = makeTemplate(wrapper); + imageRack.onChildClick = ScriptField.MakeFunction(`containingCollection.resolvedDataDoc.hero = copyField(this.data)`, { containingCollection: Doc.name }); const cardLayout = ImageDocument(fallback); - const proto = Doc.GetProto(cardLayout); - proto.layout = ImageBox.LayoutString("hero"); + cardLayout.proto!.layout = ImageBox.LayoutString("hero"); cardLayout.showTitle = "title"; cardLayout.showTitleHover = "titlehover"; cardLayout.isTemplateField = true; // make this document act like a template field diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 70e56183c..b9b367106 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -4,7 +4,7 @@ import { documentSchema } from '../../../../new_fields/documentSchemas'; import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import * as React from "react"; import { Doc } from '../../../../new_fields/Doc'; -import { NumCast, StrCast, BoolCast } from '../../../../new_fields/Types'; +import { NumCast, StrCast, BoolCast, ScriptCast } from '../../../../new_fields/Types'; import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView'; import { Utils } from '../../../../Utils'; import "./collectionMulticolumnView.scss"; @@ -186,6 +186,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu return Transform.Identity(); // type coersion, this case should never be hit } + @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } + /** * @returns the resolved list of rendered child documents, displayed * at their resolved pixel widths, each separated by a resizer. @@ -206,9 +208,11 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu {...this.props} Document={layout} DataDocument={layout.resolvedDataDoc as Doc} + CollectionDoc={this.props.Document} PanelWidth={() => this.lookupPixels(layout)} PanelHeight={() => PanelHeight() - (BoolCast(Document.showWidthLabels) ? 20 : 0)} getTransform={() => this.lookupIndividualTransform(layout)} + onClick={this.onChildClickHandler} /> (Docu SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } else if (this.onClickHandler && this.onClickHandler.script) { - this.onClickHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); + this.onClickHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, containingCollection: this.props.ContainingCollectionDoc }, console.log); } else if (this.Document.type === DocumentType.BUTTON) { ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); } else if (this.props.Document.isButton === "Selector") { // this should be moved to an OnClick script @@ -552,7 +552,7 @@ export class DocumentView extends DocComponent(Docu const docTemplate = Docs.Create.FreeformDocument([fieldTemplate], { title: doc.title + "_layout", width: width + 20, height: Math.max(100, height + 45) }); - Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate), true); + Doc.MakeMetadataFieldTemplate(fieldTemplate, Doc.GetProto(docTemplate)); Doc.ApplyTemplateTo(docTemplate, dataDoc || doc, "layoutCustom", undefined); } else { doc.layoutKey = "layoutCustom"; @@ -659,14 +659,14 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action - setCustomView = - (custom: boolean): void => { - if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); - } else { - custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document); + setCustomView = + (custom: boolean): void => { + if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); + } else { + custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document); + } } - } @undoBatch @action @@ -996,6 +996,7 @@ export class DocumentView extends DocComponent(Docu color: StrCast(this.Document.color), outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, + boxShadow: this.props.Document.isTemplateField ? "black 0.2vw 0.2vw 0.8vw" : undefined, background: this.layoutDoc.type === DocumentType.FONTICON || this.layoutDoc.viewType === CollectionViewType.Linear ? undefined : backgroundColor, width: animwidth, height: animheight, diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 674c7cc25..9620f753f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -614,7 +614,7 @@ export namespace Doc { templateField.templateField = metadataFieldKey; templateField.isTemplateField = true; templateField.title = metadataFieldKey; - templateField.showTitle = suppressTitle ? undefined : "title"; + // templateField.showTitle = suppressTitle ? undefined : "title"; // move any data that the template field had been rendering over to the template doc so that things will // appear the same after the conversion to a template has completed. (otherwise, there would be no data for the template to render) @@ -636,9 +636,6 @@ export namespace Doc { templateField.defaultBackgroundColor = templateField.backgroundColor; } - // finally, make the templateField be a delegate of the templateDoc so that it can find all the fields needed to render itselt - // (note that this is only useful to see the template doc itself which may not be necessary for many use cases) - templateField.proto = templateDoc; return true; } -- cgit v1.2.3-70-g09d2 From 6b1e9b68c3d409df6ea2a8909b99d5d8d75226c5 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 22 Jan 2020 12:53:40 -0500 Subject: various fixes to pivot view related things. --- src/client/documents/Documents.ts | 3 +- .../views/collections/CollectionPivotView.tsx | 39 ++++++++++------------ .../views/collections/CollectionTreeView.tsx | 33 +++++++++--------- .../CollectionFreeFormLayoutEngines.tsx | 6 +--- .../collections/collectionFreeForm/MarqueeView.tsx | 1 + src/scraping/buxton/scraper.py | 6 +++- .../authentication/models/current_user_utils.ts | 2 +- 7 files changed, 45 insertions(+), 45 deletions(-) (limited to 'src/client/views/collections') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 634c223a7..e86ed52f3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -88,7 +88,7 @@ export interface DocumentOptions { defaultBackgroundColor?: string; dropAction?: dropActionType; chromeStatus?: string; - disableLOD?: boolean; + LODdisable?: boolean; columnWidth?: number; fontSize?: number; curPage?: number; @@ -107,6 +107,7 @@ 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; + onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked onPointerDown?: ScriptField; onPointerUp?: ScriptField; dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 8e712e370..f23540529 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -26,36 +26,33 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { componentDidMount() { this.props.Document.freeformLayoutEngine = "pivot"; if (!this.props.Document.facetCollection) { - const facetCollection = Docs.Create.FreeformDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true }); + const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true }); facetCollection.target = this.props.Document; - const scriptText = "setDocFilter(context.target, heading, this.title, checked)"; + const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)"; const script = CompileScript(scriptText, { - params: { this: Doc.name, heading: "boolean", checked: "boolean", context: Doc.name }, + params: { this: Doc.name, heading: "boolean", checked: "boolean", containingTreeView: Doc.name }, typecheck: false, editable: true, }); if (script.compiled) { facetCollection.onCheckedClick = new ScriptField(script); } - this._narrativeDisposer = reaction(() => this.props.Document.childDetailed, - (childDetailed) => - DocCastAsync(childDetailed).then(childDetailed => { - if (childDetailed instanceof Doc) { - const captured: { [name: string]: Field } = {}; - captured.childDetailed = new PrefetchProxy(childDetailed); - const openDocText = "const alias = getAlias(this); Doc.ApplyTemplateTo(childDetailed, alias, 'layout_detailed'); useRightSplit(alias); "; - const openDocScript = CompileScript(openDocText, { - params: { this: Doc.name, heading: "boolean", context: Doc.name }, - typecheck: false, - editable: true, - capturedVariables: captured - }); - if (openDocScript.compiled) { - this.props.Document.onChildClick = new ScriptField(openDocScript); - } + const openDocText = "const alias = getAlias(this); Doc.ApplyTemplateTo(childDetailed, alias, 'layout_detailed'); useRightSplit(alias); "; + this._narrativeDisposer = reaction(() => DocCastAsync(this.props.Document.childDetailed), + (childDetailedPromise) => childDetailedPromise.then(childDetailed => { + if (childDetailed) { + const openDocScript = CompileScript(openDocText, { + params: { this: Doc.name, heading: "boolean", containingTreeView: Doc.name }, + capturedVariables: { childDetailed: new PrefetchProxy(childDetailed) }, + typecheck: false, + editable: true, + }); + if (openDocScript.compiled) { + this.props.Document.onChildClick = new ScriptField(openDocScript); } - }), { fireImmediately: true }); + } + }), { fireImmediately: true }); this.props.Document.facetCollection = facetCollection; this.props.Document.fitToBox = true; } @@ -84,7 +81,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { if (found !== -1) { (facetCollection.data as List).splice(found, 1); } else { - const newFacet = Docs.Create.FreeformDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); + const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true }); const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc }; const params = { layoutDoc: Doc.name, dataDoc: Doc.name, }; newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, params, capturedVariables); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2433cc40e..bef587786 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -32,6 +32,7 @@ import { Templates } from '../Templates'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); +import { CollectionViewType } from './CollectionView'; export interface TreeViewProps { @@ -377,7 +378,7 @@ class TreeView extends React.Component { this: this.props.document.isTemplateField && this.props.dataDoc ? this.props.dataDoc : this.props.document, heading: this.props.containingCollection.title, checked: this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check", - context: this.props.treeViewId, + containingTreeView: this.props.treeViewId, }, console.log); } else { this.treeViewOpen = !this.treeViewOpen; @@ -635,32 +636,32 @@ export class CollectionTreeView extends CollectionSubView(Document) { description: "Buxton Layout", icon: "eye", event: () => { const { TextDocument, ImageDocument, MulticolumnDocument } = Docs.Create; const { Document } = this.props; - const fallback = "http://www.cs.brown.edu/~bcz/face.gif"; - const imageRack = MulticolumnDocument([], { title: "data", height: 100 }); - const wrapper = Docs.Create.StackingDocument([ - ImageDocument(fallback, { title: "hero" }), - imageRack, - ...["short_description", "year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key })) - ], { autoHeight: true, chromeStatus: "disabled", disableLOD: true, title: "detailed layout stack" }); - wrapper.isTemplateDoc = makeTemplate(wrapper); - imageRack.onChildClick = ScriptField.MakeFunction(`containingCollection.resolvedDataDoc.hero = copyField(this.data)`, { containingCollection: Doc.name }); - - const cardLayout = ImageDocument(fallback); + const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif"; + + const detailedLayout = Docs.Create.StackingDocument([ + ImageDocument(fallbackImg, { title: "activeHero" }), + MulticolumnDocument([], { title: "data", height: 100, onChildClick: ScriptField.MakeFunction(`containingCollection.resolvedDataDoc.activeHero = copyField(this.data)`, { containingCollection: Doc.name }) }), + TextDocument({ title: "short_description", autoHeight: true }), + ...["year", "company", "degrees_of_freedom"].map(key => TextDocument({ title: key, height: 30 })) + ], { autoHeight: true, chromeStatus: "disabled", title: "detailed layout stack" }); + detailedLayout.isTemplateDoc = makeTemplate(detailedLayout); + + const cardLayout = ImageDocument(fallbackImg, { isTemplateDoc: true, isTemplateField: true }); // this acts like a template doc and a template field ... a little weird, but seems to work? cardLayout.proto!.layout = ImageBox.LayoutString("hero"); cardLayout.showTitle = "title"; cardLayout.showTitleHover = "titlehover"; - cardLayout.isTemplateField = true; // make this document act like a template field - cardLayout.isTemplateDoc = true; // but it's also a template doc itself... a little weird Document.childLayout = cardLayout; - Document.childDetailed = wrapper; + Document.childDetailed = detailedLayout; + Document.viewType = CollectionViewType.Pivot; + Document.pivotField = "company"; } }); const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: "Edit onChecked Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Checked Changed ...", this.props.Document, - "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean", context: Doc.name }) + "onCheckedClick", obj.x, obj.y, { heading: "boolean", checked: "boolean", treeViewContainer: Doc.name }) }); !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 5c6336248..32097816d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -59,11 +59,7 @@ export function computePivotLayout(poolData: ObservableMap, pivotDo let numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize))); const docMap = new Map(); const groupNames: PivotData[] = []; - if (panelDim[0] < 2500) numCols = Math.min(5, numCols); - if (panelDim[0] < 2000) numCols = Math.min(4, numCols); - if (panelDim[0] < 1400) numCols = Math.min(3, numCols); - if (panelDim[0] < 1000) numCols = Math.min(2, numCols); - if (panelDim[0] < 600) numCols = 1; + numCols = Math.min(panelDim[0] / pivotAxisWidth, numCols) const expander = 1.05; const gap = .15; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 523edb918..584d18372 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -331,6 +331,7 @@ export class MarqueeView extends React.Component Date: Wed, 22 Jan 2020 15:41:04 -0500 Subject: basic cleanup for warnings --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/util/ProseMirrorEditorView.tsx | 74 ---------------------- src/client/util/RichTextMenu.tsx | 68 ++++++++++---------- src/client/util/RichTextSchema.tsx | 2 +- src/client/views/EditableView.tsx | 2 +- src/client/views/MainView.tsx | 2 +- .../views/collections/CollectionPivotView.tsx | 11 ++-- .../views/collections/CollectionTreeView.tsx | 2 +- .../views/collections/CollectionViewChromes.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 3 + src/client/views/linking/LinkMenu.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/search/SearchItem.tsx | 1 - src/new_fields/Doc.ts | 10 +-- src/new_fields/documentSchemas.ts | 1 + src/server/Websocket/Websocket.ts | 2 - 17 files changed, 59 insertions(+), 132 deletions(-) delete mode 100644 src/client/util/ProseMirrorEditorView.tsx (limited to 'src/client/views/collections') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index fd3d9e2f1..d654e2530 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -46,7 +46,7 @@ export class YoutubeBox extends React.Component { * When component mounts, last search's results are laoded in based on the back up stored * in the document of the props. */ - async componentWillMount() { + async componentDidMount() { //DocServer.getYoutubeChannels(); const castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc); const awaitedBackUp = await castedSearchBackUp; diff --git a/src/client/util/ProseMirrorEditorView.tsx b/src/client/util/ProseMirrorEditorView.tsx deleted file mode 100644 index b42adfbb4..000000000 --- a/src/client/util/ProseMirrorEditorView.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from "react"; -import { EditorView } from "prosemirror-view"; -import { EditorState } from "prosemirror-state"; - -export interface ProseMirrorEditorViewProps { - /* EditorState instance to use. */ - editorState: EditorState; - /* Called when EditorView produces new EditorState. */ - onEditorState: (editorState: EditorState) => any; -} - -/** - * This wraps ProseMirror's EditorView into React component. - * This code was found on https://discuss.prosemirror.net/t/using-with-react/904 - */ -export class ProseMirrorEditorView extends React.Component { - - private _editorView?: EditorView; - - _createEditorView = (element: HTMLDivElement | null) => { - if (element !== null) { - this._editorView = new EditorView(element, { - state: this.props.editorState, - dispatchTransaction: this.dispatchTransaction, - }); - } - } - - dispatchTransaction = (tx: any) => { - // In case EditorView makes any modification to a state we funnel those - // modifications up to the parent and apply to the EditorView itself. - const editorState = this.props.editorState.apply(tx); - if (this._editorView) { - this._editorView.updateState(editorState); - } - this.props.onEditorState(editorState); - } - - focus() { - if (this._editorView) { - this._editorView.focus(); - } - } - - componentWillReceiveProps(nextProps: { editorState: EditorState; }) { - // In case we receive new EditorState through props — we apply it to the - // EditorView instance. - if (this._editorView) { - if (nextProps.editorState !== this.props.editorState) { - this._editorView.updateState(nextProps.editorState); - } - } - } - - componentWillUnmount() { - if (this._editorView) { - this._editorView.destroy(); - } - } - - shouldComponentUpdate() { - // Note that EditorView manages its DOM itself so we'd ratrher don't mess - // with it. - return false; - } - - render() { - // Render just an empty div which is then used as a container for an - // EditorView instance. - return ( -
- ); - } -} \ No newline at end of file diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx index f070589ae..eb5c90654 100644 --- a/src/client/util/RichTextMenu.tsx +++ b/src/client/util/RichTextMenu.tsx @@ -73,7 +73,7 @@ export default class RichTextMenu extends AntimodeMenu { this.fontSizeOptions = [ { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, - { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, + { mark: schema.marks.pFontSize.create({ fontSize: 9 }), title: "Set font size", label: "9pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 10 }), title: "Set font size", label: "10pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 12 }), title: "Set font size", label: "12pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 14 }), title: "Set font size", label: "14pt", command: this.changeFontSize }, @@ -312,22 +312,22 @@ export default class RichTextMenu extends AntimodeMenu { } return ( - ); } - createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[]): JSX.Element { + createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { return label === activeOption ? - : - ; + : + ; } return label === activeOption ? - : - ; + : + ; }); const self = this; @@ -340,19 +340,19 @@ export default class RichTextMenu extends AntimodeMenu { } }); } - return ; + return ; } - createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[]): JSX.Element { + createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean, style?: {} }[], key: string): JSX.Element { const items = options.map(({ title, label, hidden, style }) => { if (hidden) { return label === activeOption ? - : - ; + : + ; } return label === activeOption ? - : - ; + : + ; }); const self = this; @@ -363,7 +363,7 @@ export default class RichTextMenu extends AntimodeMenu { } }); } - return ; + return ; } changeFontSize = (mark: Mark, view: EditorView) => { @@ -472,7 +472,7 @@ export default class RichTextMenu extends AntimodeMenu {
; return ( - + ); } @@ -535,21 +535,21 @@ export default class RichTextMenu extends AntimodeMenu { ; const dropdownContent = -
+

Change font color:

{this.fontColors.map(color => { if (color) { return this.activeFontColor === color ? - : - ; + : + ; } })}
; return ( - + ); } @@ -582,7 +582,7 @@ export default class RichTextMenu extends AntimodeMenu { } const button = - ; @@ -594,15 +594,15 @@ export default class RichTextMenu extends AntimodeMenu { {this.highlightColors.map(color => { if (color) { return this.activeHighlightColor === color ? - : - ; + : + ; } })}
; return ( - + ); } @@ -635,7 +635,7 @@ export default class RichTextMenu extends AntimodeMenu {
; return ( - + ); } @@ -773,7 +773,7 @@ export default class RichTextMenu extends AntimodeMenu { render() { - const row1 =
{[ + const row1 =
{[ this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), @@ -787,14 +787,14 @@ export default class RichTextMenu extends AntimodeMenu { this.createButton("indent", "Summarize", undefined, this.insertSummarizer), ]}
; - const row2 =
-
- {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions), - this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions), - this.createNodesDropdown(this.activeListType, this.listTypeOptions)]} + const row2 =
+
+ {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), + this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), + this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes")]}
-
- {this.getDragger()} @@ -864,12 +864,12 @@ class ButtonDropdown extends React.Component { : <> {this.props.button} - } - {this.showDropdown ? this.props.dropdownContent : <>} + {this.showDropdown ? this.props.dropdownContent : (null)}
); } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1f70cafc4..5751c8c7e 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -306,7 +306,7 @@ export const marks: { [index: string]: MarkSpec } = { } }], toDOM(node: any) { - return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; } }, diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 0d677b8ce..394caba72 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -66,7 +66,7 @@ export class EditableView extends React.Component { } @action - componentWillReceiveProps(nextProps: EditableProps) { + componentDidUpdate(nextProps: EditableProps) { // this is done because when autosuggest is turned on, the suggestions are passed in as a prop, // so when the suggestions are passed in, and no editing prop is passed in, it used to set it // to false. this will no longer do so -syip diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0958c434a..79e0cfea5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -64,7 +64,7 @@ export class MainView extends React.Component { public isPointerDown = false; - componentWillMount() { + componentDidMount() { const tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api"; diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index f23540529..aa68cc18f 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -28,6 +28,7 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { if (!this.props.Document.facetCollection) { const facetCollection = Docs.Create.TreeDocument([], { title: "facetFilters", yMargin: 0, treeViewHideTitle: true }); facetCollection.target = this.props.Document; + facetCollection.dontCopyOnAlias = true; const scriptText = "setDocFilter(containingTreeView.target, heading, this.title, checked)"; const script = CompileScript(scriptText, { @@ -122,8 +123,8 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) { const facetCollection = Cast(this.props.Document?.facetCollection, Doc, null); const flyout = (
- {this._allFacets.map(facet =>