From a646731204c808d7d2e24cd2dd4b4aec4752a516 Mon Sep 17 00:00:00 2001 From: dg314 Date: Thu, 21 Oct 2021 04:57:21 -0400 Subject: page rank --- src/client/views/search/SearchBox.tsx | 146 +++++++++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 3612bd7c4..57c9a3d46 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,7 +1,7 @@ import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc'; +import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc'; import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from '../../../fields/FieldSymbols'; import { createSchema, makeInterface } from '../../../fields/Schema'; @@ -14,6 +14,8 @@ import "./SearchBox.scss"; import { DocumentManager } from '../../util/DocumentManager'; import { DocUtils } from '../../documents/Documents'; import { Tooltip } from "@material-ui/core"; +import { DictationOverlay } from '../DictationOverlay'; +import { CollectionSchemaBooleanCell } from '../collections/collectionSchema/CollectionSchemaCells'; export const searchSchema = createSchema({ Document: Doc @@ -22,6 +24,10 @@ export const searchSchema = createSchema({ type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>; const SearchBoxDocument = makeInterface(documentSchema, searchSchema); +const DAMPENING_FACTOR = 0.9; +const MAX_ITERATIONS = 25; +const ERROR = 0.03; + export interface SearchBoxProps extends FieldViewProps { linkSearch: boolean; linkFrom?: (() => Doc | undefined) | undefined; @@ -40,7 +46,10 @@ export class SearchBox extends ViewBoxBaseComponent = new Map(); + @observable _pageRanks: Map = new Map(); + @observable _linkedDocsOut: Map> = new Map>(); + @observable _linkedDocsIn: Map> = new Map>(); @observable _selectedResult: Doc | undefined = undefined; @observable _deletedDocsStatus: boolean = false; @observable _onlyAliases: boolean = true; @@ -110,11 +119,9 @@ export class SearchBox extends ViewBoxBaseComponent { - console.log(linkTo.title); if (this.props.linkFrom) { const linkFrom = this.props.linkFrom(); if (linkFrom) { - console.log(linkFrom.title); DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }, "Link"); } } @@ -204,7 +211,7 @@ export class SearchBox extends ViewBoxBaseComponent(); SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key)); blockedKeys.forEach(key => hlights.delete(key)); - Array.from(hlights.keys()).length > 0 && this._results.push([doc, Array.from(hlights.keys())]); + + if (Array.from(hlights.keys()).length > 0) { + this._results.set(doc, Array.from(hlights.keys())); + } } docIDs.push(doc[Id]); }); } + + this.computePageRanks(); } /** - * @param {Doc} doc - doc for which keys are returned - * - * This method returns a list of a document doc's keys. + * This method initializes the page rank of every document to the reciprocal + * of the number of documents in the collection. + */ + @action + initializePageRanks() { + this._pageRanks.clear(); + this._linkedDocsOut.clear(); + + this._results.forEach((_, doc) => { + this._linkedDocsIn.set(doc, new Set()); + }); + + this._results.forEach((_, doc) => { + this._pageRanks.set(doc, 1.0 / this._results.size); + + if (Doc.GetProto(doc)[DirectLinksSym].size == 0) { + this._linkedDocsOut.set(doc, new Set(this._results.keys())); + + this._results.forEach((_, linkedDoc) => { + this._linkedDocsIn.get(linkedDoc)?.add(doc); + }); + } + else { + let linkedDocSet: Set = new Set(); + + Doc.GetProto(doc)[DirectLinksSym].forEach((link) => { + let d1 = link?.anchor1 as Doc; + let d2 = link?.anchor2 as Doc; + if (doc == d1 && this._results.has(d2)) { + linkedDocSet.add(d2); + this._linkedDocsIn.get(d2)?.add(doc); + } + else if (doc == d2 && this._results.has(d1)) { + linkedDocSet.add(d1); + this._linkedDocsIn.get(d1)?.add(doc); + } + }) + + this._linkedDocsOut.set(doc, linkedDocSet); + } + }); + } + + /** + * This method runs one complete iteration of the page rank algorithm. It + * returns true iff all page ranks have converged (i.e. changed by less than + * the _error value), which means that the algorithm should terminate. + * + * @return true if page ranks have converged; false otherwise */ + @action + pageRankIteration(): boolean { + let converged = true; + const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size; + + let nextPageRanks: Map = new Map(); + + this._results.forEach((_, doc) => { + let nextPageRank = pageRankFromAll; + + this._linkedDocsIn.get(doc)?.forEach((linkedDoc) => { + nextPageRank += DAMPENING_FACTOR * (this._pageRanks.get(linkedDoc) ?? 0) / (this._linkedDocsOut.get(linkedDoc)?.size ?? 1); + }); + + nextPageRanks.set(doc, nextPageRank); + + if (Math.abs(nextPageRank - (this._pageRanks.get(doc) ?? 0)) > ERROR) { + converged = false; + } + }); + + this._pageRanks = nextPageRanks; + + return converged; + } + + /** + * This method performs the page rank algorithm on the graph of documents + * that match the search query. Vertices are documents and edges are links + * between documents. + */ + @action + computePageRanks() { + this.initializePageRanks(); + + for (let i = 0; i < MAX_ITERATIONS; i++) { + if (this.pageRankIteration()) { + break; + } + } + } + + /** + * @param {Doc} doc - doc for which keys are returned + * + * This method returns a list of a document doc's keys. + */ static documentKeys(doc: Doc) { const keys: { [key: string]: boolean } = {}; Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false)); @@ -244,7 +349,7 @@ export class SearchBox extends ViewBoxBaseComponent { - this._results.forEach(result => { - Doc.UnBrushDoc(result[0]); - Doc.UnHighlightDoc(result[0]); + this._results.forEach((_, doc) => { + Doc.UnBrushDoc(doc); + Doc.UnHighlightDoc(doc); Doc.ClearSearchMatches(); }); }); /** * @param {Doc} doc - doc to be selected - * + * * This method selects a doc by either jumping to it (centering/zooming in on it) * or opening it in a new tab. */ @@ -292,8 +397,11 @@ export class SearchBox extends ViewBoxBaseComponent (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)) // sorted by page rank + + const resultsJSX = Array(); - const results = this._results.map(result => { + sortedResults.forEach((result) => { var className = "searchBox-results-scroll-view-result"; if (this._selectedResult === result[0]) { @@ -305,7 +413,7 @@ export class SearchBox extends ViewBoxBaseComponent
{title}
}>
this.makeLink(result[0]) : @@ -326,12 +434,8 @@ export class SearchBox extends ViewBoxBaseComponent ); } - - return null; }); - results.filter(result => result); - return (
@@ -345,7 +449,7 @@ export class SearchBox extends ViewBoxBaseComponent
- {results} + {resultsJSX}
-- cgit v1.2.3-70-g09d2 From 21119bf271f9291265bbbc6e2bd4ef26f3fc0762 Mon Sep 17 00:00:00 2001 From: dg314 Date: Tue, 26 Oct 2021 14:30:45 -0400 Subject: update --- src/client/views/search/SearchBox.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 57c9a3d46..fe297782c 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -330,9 +330,9 @@ export class SearchBox extends ViewBoxBaseComponent Object.keys(proto).forEach(key => keys[key] = false)); -- cgit v1.2.3-70-g09d2 From 4bbfe0fad535033309e352311fae64520b9068a6 Mon Sep 17 00:00:00 2001 From: Lauren Date: Thu, 28 Oct 2021 16:07:11 -0400 Subject: adding eraser tool to menu --- src/client/util/CurrentUserUtils.ts | 1 + src/client/views/GestureOverlay.tsx | 9 ++++++++- src/client/views/InkStrokeProperties.ts | 4 ++-- src/client/views/nodes/button/FontIconBox.tsx | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9d06ad8a3..656ba61e8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1001,6 +1001,7 @@ export class CurrentUserUtils { static inkTools(doc: Doc) { const tools: Button[] = [ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")', checkResult: 'setActiveInkTool("pen" , true)' }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser")', checkResult: 'setActiveInkTool("eraser" , true)' }, // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', checkResult: 'setActiveInkTool("highlighter", true)' }, { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', checkResult: 'setActiveInkTool("circle" , true)' }, // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', checkResult: 'setActiveInkTool("square" , true)' }, diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index f28485e43..99829139f 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -3,7 +3,7 @@ import * as fitCurve from 'fit-curve'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; -import { InkData, InkTool } from "../../fields/InkField"; +import { InkData, InkTool, PointData } from "../../fields/InkField"; import { Cast, FieldValue, NumCast } from "../../fields/Types"; import MobileInkOverlay from "../../mobile/MobileInkOverlay"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; @@ -489,12 +489,19 @@ export class GestureOverlay extends Touchable { e.stopPropagation(); } + @action + checkInkIntersection = (point: PointData) => { + + } + @action onPointerDown = (e: React.PointerEvent) => { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); + } else if (InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) { + this.checkInkIntersection({ X: e.clientX, Y: e.clientY }); } } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 7c91a3323..02288bbb5 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -239,12 +239,12 @@ export class InkStrokeProperties { if (ink) { const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]); if (screenDragPt) { - var snapData = this.snapToAllCurves(screenDragPt, inkView, { nearestPt: { X: 0, Y: 0 }, distance: 10 }, ink, controlIndex); + const snapData = this.snapToAllCurves(screenDragPt, inkView, { nearestPt: { X: 0, Y: 0 }, distance: 10 }, ink, controlIndex); if (snapData.distance < 10) { const deltaX = (snapData.nearestPt.X - ink[controlIndex].X); const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y); const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex); - console.log("X= " + snapData.nearestPt.X + " " + snapData.nearestPt.Y); + console.log("X = " + snapData.nearestPt.X + " " + snapData.nearestPt.Y); return res; } } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 33fa23805..14b1cbb5d 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -766,7 +766,7 @@ Scripting.addGlobal(function setActiveInkTool(tool: string, checkResult?: boolea Doc.UserDoc().activeInkTool = InkTool.Pen; GestureOverlay.Instance.InkShape = tool; } - } else if (tool) { // pen + } else if (tool) { // pen or eraser if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { Doc.UserDoc().activeInkTool = InkTool.None; } else { -- cgit v1.2.3-70-g09d2 From f6775da5d14f35be2d05de13008f86fbae2cbf68 Mon Sep 17 00:00:00 2001 From: Lauren Date: Fri, 29 Oct 2021 13:57:37 -0400 Subject: working on ink stroke intersection --- .../collectionFreeForm/CollectionFreeFormView.tsx | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index febccbfcc..f0064d15e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -5,7 +5,7 @@ import { DateField } from "../../../../fields/DateField"; import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas"; import { Id } from "../../../../fields/FieldSymbols"; -import { InkData, InkField, InkTool } from "../../../../fields/InkField"; +import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; @@ -51,6 +51,7 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { ColorScheme } from "../../../util/SettingsManager"; +import { Bezier } from "bezier-js"; export const panZoomSchema = createSchema({ _panX: "number", @@ -111,6 +112,8 @@ export class CollectionFreeFormView extends CollectionSubView(); @observable _marqueeRef = React.createRef(); @observable _keyframeEditing = false; @@ -450,6 +453,11 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { + this._prevPoint = this._currPoint = { X: -1, Y: -1 }; document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this.removeMoveListeners(); @@ -627,6 +636,10 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.props.Document._isGroup) return; // groups don't pan when dragged -- instead let the event go through to allow the group itself to drag if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; + if (InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) { + this._prevPoint = this._currPoint; + this._currPoint = { X: e.clientX, Y: e.clientY } + } if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { @@ -642,6 +655,18 @@ export class CollectionFreeFormView extends CollectionSubView { + // this.children.filter((doc: Doc) => doc.type === DocumentType.INK) + // .forEach((doc: Doc) => { + // const inkView = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView); + // const ctrlPoints = inkView?.ComponentView?.inkScaledData().inkData.slice(); + // const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; + // const intersects = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).intersects({p1: {x: this._prevPoint.X, y: this._prevPoint.Y}, p2: {x: this._currPoint.X, y: this._currPoint.Y}}); + // if (intersects) return inkView; + // }); + return null; + } + handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (!e.cancelBubble) { const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); -- cgit v1.2.3-70-g09d2 From 2e6c1f2c1710548ebf86476e1476c22889f11819 Mon Sep 17 00:00:00 2001 From: Lauren Date: Fri, 5 Nov 2021 18:27:07 -0400 Subject: ink document iteration not working --- src/client/views/InkingStroke.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 52 +++++++++++++++------- 2 files changed, 36 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 81a888256..ecb46a5b3 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -130,7 +130,7 @@ export class InkingStroke extends ViewBoxBaseComponent { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f0064d15e..0941db5f8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -114,6 +114,7 @@ export class CollectionFreeFormView extends CollectionSubView(); @observable _marqueeRef = React.createRef(); @observable _keyframeEditing = false; @@ -453,11 +454,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - this._prevPoint = this._currPoint = { X: -1, Y: -1 }; document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); + if (CurrentUserUtils.SelectedTool !== InkTool.None) { + this._deleteList.forEach(ink => { + ink.props.removeDocument?.(ink.props.Document); + }); + this._prevPoint = this._currPoint = { X: -1, Y: -1 }; + this._deleteList = []; + } } } @@ -636,9 +639,15 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.props.Document._isGroup) return; // groups don't pan when dragged -- instead let the event go through to allow the group itself to drag if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; - if (InteractionUtils.IsType(e, InteractionUtils.ERASERTYPE)) { - this._prevPoint = this._currPoint; - this._currPoint = { X: e.clientX, Y: e.clientY } + if (CurrentUserUtils.SelectedTool !== InkTool.None) { + this._currPoint = { X: e.clientX, Y: e.clientY }; + const intersectStroke = this.getInkIntersection(); + if (intersectStroke) { + if (!this._deleteList.includes(intersectStroke)) { + this._deleteList.push(intersectStroke); + // lower intersectStroke opacity to give user a visual indicator + } + } } if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { if (this.props.isContentActive(true)) e.stopPropagation(); @@ -655,15 +664,24 @@ export class CollectionFreeFormView extends CollectionSubView { - // this.children.filter((doc: Doc) => doc.type === DocumentType.INK) - // .forEach((doc: Doc) => { - // const inkView = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView); - // const ctrlPoints = inkView?.ComponentView?.inkScaledData().inkData.slice(); - // const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; - // const intersects = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).intersects({p1: {x: this._prevPoint.X, y: this._prevPoint.Y}, p2: {x: this._currPoint.X, y: this._currPoint.Y}}); - // if (intersects) return inkView; - // }); + this.props.childDocuments?.filter(doc => doc.type === DocumentType.INK) + .forEach(doc => { + console.log("in for each"); + const inkView = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView); + const ctrlPoints = Cast(inkView?.dataDoc[this.props.fieldKey], InkField)?.inkData ?? []; + for (var i = 0; i < ctrlPoints.length - 3; i += 4) { + const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; + const intersects = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).intersects( + { p1: { x: this._prevPoint.X, y: this._prevPoint.Y }, + p2: { x: this._currPoint.X, y: this._currPoint.Y } }); + if (intersects) return inkView; + } + }); return null; } -- cgit v1.2.3-70-g09d2 From 50f8a862bd27c212b13fcd1f626aafa5434e4842 Mon Sep 17 00:00:00 2001 From: Lauren Date: Sat, 6 Nov 2021 15:32:47 -0400 Subject: from last --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0941db5f8..0b7bf92cc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -669,7 +669,10 @@ export class CollectionFreeFormView extends CollectionSubView { - this.props.childDocuments?.filter(doc => doc.type === DocumentType.INK) + console.log("in intersection"); + const currentCollection = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + currentCollection?.childDocs + .filter(doc => doc.type === DocumentType.INK) .forEach(doc => { console.log("in for each"); const inkView = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView); -- cgit v1.2.3-70-g09d2 From b2b95106d21f03a0b2b46f972a36bbf00068ab20 Mon Sep 17 00:00:00 2001 From: Lauren Date: Sat, 6 Nov 2021 16:49:36 -0400 Subject: eraser tool working --- .../collectionFreeForm/CollectionFreeFormView.tsx | 46 +++++++++++++--------- 1 file changed, 27 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0b7bf92cc..f60af6355 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -605,9 +605,11 @@ export class CollectionFreeFormView extends CollectionSubView { - ink.props.removeDocument?.(ink.props.Document); - }); + if (this._deleteList.length > 0) { + this._deleteList.forEach(ink => { + ink.props.removeDocument?.(ink.props.Document); + }); + } this._prevPoint = this._currPoint = { X: -1, Y: -1 }; this._deleteList = []; } @@ -641,12 +643,15 @@ export class CollectionFreeFormView extends CollectionSubView 0) { + intersections.forEach(ink =>{ + if (!this._deleteList.includes(ink)) { + this._deleteList.push(ink); + // Lowering ink opacity to give the user a visual indicator of deletion. + ink.Document.opacity = 0.5; + } + }); } } if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { @@ -668,24 +673,27 @@ export class CollectionFreeFormView extends CollectionSubView { - console.log("in intersection"); - const currentCollection = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - currentCollection?.childDocs + getInkIntersection = (): DocumentView[] => { + const inks: DocumentView[] = []; + this.childDocs .filter(doc => doc.type === DocumentType.INK) .forEach(doc => { - console.log("in for each"); const inkView = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView); const ctrlPoints = Cast(inkView?.dataDoc[this.props.fieldKey], InkField)?.inkData ?? []; for (var i = 0; i < ctrlPoints.length - 3; i += 4) { const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; - const intersects = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).intersects( - { p1: { x: this._prevPoint.X, y: this._prevPoint.Y }, - p2: { x: this._currPoint.X, y: this._currPoint.Y } }); - if (intersects) return inkView; + // Converting from screen space to ink space for the intersection. + const prevPointInkSpace = inkView?.ComponentView?.ptFromScreen?.(this._prevPoint); + const currPointInkSpace = inkView?.ComponentView?.ptFromScreen?.(this._currPoint); + if (prevPointInkSpace && currPointInkSpace) { + const intersects = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).intersects( + { p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, + p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y } }); + if (inkView && intersects.length > 0) inks.push(inkView); + } } }); - return null; + return inks; } handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { -- cgit v1.2.3-70-g09d2 From dcd2fd6f3030f7b57000536005f6005a0abaa482 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 8 Nov 2021 12:14:31 -0500 Subject: fixed dragging snap lines to get recomputed properly (sometimes they didn't activate before) and fixed snapping to not be off by 1 screen space pixel --- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 8 +++----- src/client/views/nodes/DocumentView.tsx | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index febccbfcc..37caf508f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -146,7 +146,7 @@ export class CollectionFreeFormView extends CollectionSubView this._keyframeEditing = set; @@ -169,7 +169,6 @@ export class CollectionFreeFormView extends CollectionSubView this.cachedGetTransform.copy(); getLocalTransform = () => this.cachedGetLocalTransform.copy(); getContainerTransform = () => this.cachedGetContainerTransform.copy(); - getTransformOverlay = () => this.getContainerTransform().translate(1, 1); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); isAnyChildContentActive = () => this.props.isAnyChildContentActive(); addLiveTextBox = (newBox: Doc) => { @@ -221,7 +220,7 @@ export class CollectionFreeFormView extends CollectionSubView { - (DocumentDecorations.Instance.Interacting || (this.props.layerProvider?.(this.props.Document) !== false && SnappingManager.GetIsDragging())) && this.setupDragLines(e.ctrlKey || e.shiftKey); e.stopPropagation(); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c9b246c10..6c058ff97 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -415,6 +415,7 @@ export class DocumentViewInternal extends DocComponent (ffview.ChildDrag = this.props.DocumentView())); DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) }, () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed. + ffview?.setupDragLines(false); } } -- cgit v1.2.3-70-g09d2 From f450c8760c0563cdadd414f169902faeace585ec Mon Sep 17 00:00:00 2001 From: vkalev Date: Tue, 9 Nov 2021 16:08:01 -0500 Subject: working on undo/redo --- src/client/views/InkStrokeProperties.ts | 20 +++++++++++--------- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +++++++- 2 files changed, 18 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 02288bbb5..58d2992e0 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -356,16 +356,18 @@ export class InkStrokeProperties { const oldHandlePoint = ink[handleIndex]; const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; - const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; const inkCopy = ink.slice(); - inkCopy[handleIndex] = newHandlePoint; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); - const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; - // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && - (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { - const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); + if (!Number.isNaN(deltaX / xScale) && !Number.isNaN(deltaY / yScale)) { + const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; + inkCopy[handleIndex] = newHandlePoint; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; + // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). + if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && + (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { + const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); + inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); + } } return inkCopy; }) diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f60af6355..018f4f79a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -29,7 +29,7 @@ import { SearchUtil } from "../../../util/SearchUtil"; import { SelectionManager } from "../../../util/SelectionManager"; import { SnappingManager } from "../../../util/SnappingManager"; import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from "../../../views/global/globalCssVariables.scss"; import { Timeline } from "../../animationtimeline/Timeline"; import { ContextMenu } from "../../ContextMenu"; @@ -99,6 +99,7 @@ export class CollectionFreeFormView extends CollectionSubView(); private _cachedPool: Map = new Map(); private _lastTap = 0; + private _batch: UndoManager.Batch | undefined = undefined; private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } @@ -449,6 +450,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { document.removeEventListener("pointermove", this.onPointerMove); @@ -607,11 +611,13 @@ export class CollectionFreeFormView extends CollectionSubView 0) { this._deleteList.forEach(ink => { + ink.Document.opacity = 1; ink.props.removeDocument?.(ink.props.Document); }); } this._prevPoint = this._currPoint = { X: -1, Y: -1 }; this._deleteList = []; + this._batch?.end(); } } } -- cgit v1.2.3-70-g09d2 From 44c3ed6af9516c7bb91785934a997c7ab054ec5f Mon Sep 17 00:00:00 2001 From: vkalev Date: Fri, 12 Nov 2021 16:54:51 -0500 Subject: undo works with ink deletion --- package-lock.json | 6 +++--- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index d03f8af6b..128d6fba8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7709,14 +7709,14 @@ "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { - "image-size": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", "readable-stream": "^1.0.33", "tryit": "^1.0.1" }, "dependencies": { "image-size": { - "version": "git+ssh://git@github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1", - "from": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" + "version": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "from": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" }, "isarray": { "version": "0.0.1", diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 018f4f79a..bd72e4af9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -99,7 +99,6 @@ export class CollectionFreeFormView extends CollectionSubView(); private _cachedPool: Map = new Map(); private _lastTap = 0; - private _batch: UndoManager.Batch | undefined = undefined; private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } @@ -450,7 +449,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { @@ -610,14 +608,18 @@ export class CollectionFreeFormView extends CollectionSubView 0) { + // Ensuring ink opacity returns to normal if the user undos after deletion. this._deleteList.forEach(ink => { ink.Document.opacity = 1; + }); + const batch = UndoManager.StartBatch("collectionErase"); + this._deleteList.forEach(ink => { ink.props.removeDocument?.(ink.props.Document); }); + batch.end(); } this._prevPoint = this._currPoint = { X: -1, Y: -1 }; this._deleteList = []; - this._batch?.end(); } } } -- cgit v1.2.3-70-g09d2 From 1fd8d25496d6a52902731cddc21969fe10cb2efa Mon Sep 17 00:00:00 2001 From: vkalev Date: Fri, 12 Nov 2021 18:01:42 -0500 Subject: reverting NaN check when moving tangent handle --- src/client/views/InkStrokeProperties.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 58d2992e0..02288bbb5 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -356,18 +356,16 @@ export class InkStrokeProperties { const oldHandlePoint = ink[handleIndex]; const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; + const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; const inkCopy = ink.slice(); - if (!Number.isNaN(deltaX / xScale) && !Number.isNaN(deltaY / yScale)) { - const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; - inkCopy[handleIndex] = newHandlePoint; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); - const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; - // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && - (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { - const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); - inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); - } + inkCopy[handleIndex] = newHandlePoint; + const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; + // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). + if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && + (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { + const angle = this.angleChange(oldHandlePoint, newHandlePoint, controlPoint); + inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } return inkCopy; }) -- cgit v1.2.3-70-g09d2 From 3381bbb0ef5160707513f4bbbe551ca551b64b0d Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 13 Nov 2021 10:38:53 -0500 Subject: change isContentActive to a tri-state to allow turning on/off and default - fixes issues with videobox and others so that content can be turned off reliably. added annotation overlay for treeViews for ppt like slides. lots of fixes to tree view to get layout to be more robust. --- src/Utils.ts | 2 +- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 20 +- src/client/views/DocComponent.tsx | 18 +- src/client/views/DocumentDecorations.scss | 6 +- src/client/views/DocumentDecorations.tsx | 27 +-- src/client/views/MainView.tsx | 6 +- src/client/views/OverlayView.tsx | 2 +- src/client/views/Palette.tsx | 2 +- src/client/views/PropertiesView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 7 +- .../views/collections/CollectionTreeView.scss | 9 + .../views/collections/CollectionTreeView.tsx | 252 ++++++++++++--------- src/client/views/collections/CollectionView.tsx | 12 +- src/client/views/collections/TabDocView.tsx | 3 +- src/client/views/collections/TreeView.scss | 7 +- src/client/views/collections/TreeView.tsx | 49 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +- .../CollectionMulticolumnView.tsx | 4 +- .../CollectionMultirowView.tsx | 4 +- .../collectionMulticolumn/MulticolumnResizer.tsx | 2 +- .../collectionMulticolumn/MultirowResizer.tsx | 2 +- .../collectionSchema/CollectionSchemaHeaders.tsx | 2 +- .../collections/collectionSchema/SchemaTable.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 10 +- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/FilterBox.tsx | 2 +- src/client/views/nodes/LinkBox.tsx | 2 +- src/client/views/nodes/LinkDocPreview.tsx | 2 +- src/client/views/nodes/ScreenshotBox.tsx | 2 +- src/client/views/nodes/VideoBox.scss | 10 +- src/client/views/nodes/VideoBox.tsx | 11 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 18 +- src/mobile/AudioUpload.tsx | 2 +- src/mobile/MobileInterface.tsx | 2 +- 36 files changed, 298 insertions(+), 221 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index bfb29fe8d..ca1432de2 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -448,7 +448,7 @@ export function returnEmptyDoclist() { return [] as any[]; } export let emptyPath = []; -export function emptyFunction() { } +export function emptyFunction() { return undefined; } export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index da5c8efa9..219f51a3a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -94,6 +94,7 @@ type DROPt = DAInfo | dropActionType; export class DocumentOptions { system?: BOOLt = new BoolInfo("is this a system created/owned doc"); _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); + allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); color?: string; // foreground color data doc @@ -289,6 +290,7 @@ export class DocumentOptions { treeViewExpandedViewLock?: boolean; // whether the expanded view can be changed treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked treeViewTruncateTitleWidth?: number; + treeViewHasOverlay?: boolean; // whether the treeview has an overlay for freeform annotations treeViewType?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy sidebarColor?: string; // background color of text sidebar sidebarViewType?: string; // collection type of text sidebar @@ -821,7 +823,7 @@ export namespace Docs { } export function TreeDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Tree }, id, undefined, protoId); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xMargin: 5, _yMargin: 5, ...options, _viewType: CollectionViewType.Tree }, id, undefined, protoId); } export function StackingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9d06ad8a3..c9bef5924 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -339,7 +339,7 @@ export class CurrentUserUtils { const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc); if (doc.templateDocs === undefined) { doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], { - title: "template layouts", _xPadding: 0, system: true, + title: "template layouts", _xMargin: 0, system: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) })); } @@ -421,7 +421,11 @@ export class CurrentUserUtils { ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptySlide === undefined) { - const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", _autoHeight: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) }); + const textDoc = Docs.Create.TreeDocument([], { + title: "Slide", _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, + allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, + backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) + }); Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); FormattedTextBox.SelectOnLoad = textDoc[Id]; doc.emptySlide = textDoc; @@ -811,7 +815,7 @@ export class CurrentUserUtils { const newDashboardButton: Doc = Docs.Create.FontIconDocument({ onClick: newDashboard, _forceActive: true, toolTip: "Create new dashboard", _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, freezeChildren: "remove|add", - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true, explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." @@ -840,7 +844,7 @@ export class CurrentUserUtils { const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "Create new trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); doc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Trails", _showTitle: "title", _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, explainer: "All of the trails that you have created will appear here." @@ -865,7 +869,7 @@ export class CurrentUserUtils { }); doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileOrphans as Doc], { title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true, @@ -884,7 +888,7 @@ export class CurrentUserUtils { const clearDocsButton: Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true }); doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." @@ -902,7 +906,7 @@ export class CurrentUserUtils { if (doc.currentFilter === undefined) { doc.currentFilter = Docs.Create.FilterDocument({ title: "Unnamed Filter", _height: 150, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", + treeViewHideTitle: true, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, _autoHeight: true, _fitWidth: true }); @@ -918,7 +922,7 @@ export class CurrentUserUtils { doc.treeViewOpen = true; doc.treeViewExpandedView = "fields"; doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title", + treeViewHideTitle: true, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title", treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })) as any as Doc; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 32c351bf5..b9772fd57 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -44,7 +44,7 @@ interface ViewBoxBaseProps { fieldKey: string; layerProvider?: (doc: Doc, assign?: boolean) => boolean; isSelected: (outsideReaction?: boolean) => boolean; - isContentActive: () => boolean; + isContentActive: () => boolean | undefined; renderDepth: number; rootSelected: (outsideReaction?: boolean) => boolean; } @@ -65,10 +65,12 @@ export function ViewBoxBaseComponent

(schemaCtor: lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result; - isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || - (this.props.isContentActive?.() || this.props.Document.forceActive || - this.props.isSelected(outsideReaction) || - this.props.rootSelected(outsideReaction)) ? true : false) + isContentActive = (outsideReaction?: boolean) => ( + this.props.isContentActive?.() === false ? false : + (CurrentUserUtils.SelectedTool !== InkTool.None || + (this.props.isContentActive?.() || this.props.Document.forceActive || + this.props.isSelected(outsideReaction) || + this.props.rootSelected(outsideReaction)) ? true : undefined)) protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } return Component; @@ -82,7 +84,7 @@ export interface ViewBoxAnnotatableProps { fieldKey: string; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) layerProvider?: (doc: Doc, assign?: boolean) => boolean; - isContentActive: () => boolean; + isContentActive: () => boolean | undefined; select: (isCtrlPressed: boolean) => void; whenChildContentsActiveChanged: (isActive: boolean) => void; isSelected: (outsideReaction?: boolean) => boolean; @@ -165,13 +167,13 @@ export function ViewBoxAnnotatableComponent

boolean, annotationKey?: string): boolean => { + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[], annotationKey?: string) => boolean, annotationKey?: string): boolean => { if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } const first = doc instanceof Doc ? doc : doc[0]; if (!first?._stayInCollection && addDocument !== returnFalse) { - return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc)); + return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey)); } return false; } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index d8ad47ecb..82dca1287 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -240,8 +240,10 @@ $linkGap: 3px; text-align: center; display: flex; margin-left: 5px; - height: 22px; + height: 20px; position: absolute; + border-radius: 8px; + background: rgba(159,159,159,0.1); .documentDecorations-titleSpan, .documentDecorations-titleSpan-Dark { @@ -288,7 +290,7 @@ $linkGap: 3px; text-align: center; display: flex; margin-left: 5px; - height: 22px; + height: 20px; position: absolute; .documentDecorations-titleSpan { width: 100%; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 522995479..1c0b1b995 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -454,17 +454,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P ); const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); - const titleArea = this._edtingTitle ? - this.titleBlur()} - onChange={action(e => this._accumulatedTitle = e.target.value)} - onKeyPress={this.titleEntered} /> : -

- {`${this.selectionTitle}`} -
; + const titleArea = hideTitle ?
: + this._edtingTitle ? + this.titleBlur()} + onChange={action(e => this._accumulatedTitle = e.target.value)} + onKeyPress={this.titleEntered} /> : +
+ {`${this.selectionTitle}`} +
; let inMainMenuPanel = false; for (let node = seldoc.ContentDiv; node && !inMainMenuPanel; node = node?.parentNode as any) { @@ -498,8 +499,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> {!canDelete ?
: topBtn("close", "times", undefined, this.onCloseClick, "Close")} - {hideTitle ? (null) : titleArea}{!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} - + {titleArea} + {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} {hideResizers ? (null) : <> {SelectionManager.Views().length !== 1 || hideTitle ? (null) : diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9a885fbf8..546b0e360 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -511,7 +511,7 @@ export class MainView extends React.Component { bringToFront={emptyFunction} select={emptyFunction} isAnyChildContentActive={returnFalse} - isContentActive={returnFalse} + isContentActive={emptyFunction} isSelected={returnFalse} docViewPath={returnEmptyDoclist} moveDocument={this.moveButtonDoc} @@ -592,7 +592,7 @@ export class MainView extends React.Component { pinToPres={returnFalse} ScreenToLocalTransform={Transform.Identity} bringToFront={returnFalse} - isContentActive={returnFalse} + isContentActive={emptyFunction} whenChildContentsActiveChanged={returnFalse} focus={returnFalse} docViewPath={returnEmptyDoclist} @@ -669,7 +669,7 @@ export class MainView extends React.Component { pinToPres={returnFalse} ScreenToLocalTransform={Transform.Identity} bringToFront={returnFalse} - isContentActive={returnFalse} + isContentActive={emptyFunction} whenChildContentsActiveChanged={returnFalse} focus={returnFalse} PanelWidth={() => 500} diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index af04b967a..7cf388872 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -191,7 +191,7 @@ export class OverlayView extends React.Component { ScreenToLocalTransform={Transform.Identity} renderDepth={1} isDocumentActive={returnTrue} - isContentActive={returnFalse} + isContentActive={emptyFunction} whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index 86ab881bb..529697f71 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -50,7 +50,7 @@ export default class Palette extends React.Component { PanelHeight={() => window.screen.height} renderDepth={0} isDocumentActive={returnTrue} - isContentActive={returnFalse} + isContentActive={emptyFunction} focus={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={returnEmptyString} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ab9022a84..1083e0075 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -295,7 +295,7 @@ export class PropertiesView extends React.Component { freezeDimensions={true} dontCenter={"y"} isDocumentActive={returnFalse} - isContentActive={returnFalse} + isContentActive={emptyFunction} NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined} NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined} PanelWidth={panelWidth} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 648ff5087..bffaf86b1 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -226,7 +226,7 @@ export class CollectionStackingView extends CollectionSubView; - SetSubView?: (subView: any) => void; isAnyChildContentActive: () => boolean; } @@ -49,10 +48,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: this.createDashEventsTarget(ele); } - componentDidMount() { - this.props.SetSubView?.(this); - } - componentWillUnmount() { this.gestureDisposer?.(); this._multiTouchDisposer?.(); @@ -220,7 +215,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); if (movedDocs.length) { - const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || !this.props.isAnnotationOverlay || + const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || (!this.props.isAnnotationOverlay || this.props.Document.allowOverlayDrop) || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); } else { diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index d370d21ab..b664d9d82 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -71,6 +71,15 @@ display: none; } +.collectionTreeView-titleBar { + display: inline-block; + width: 100%; + height: max-content; + .contentFittingDocumentView { + display: block; // makes titleBar take up full width of the treeView (flex doesn't for some reason) + } +} + .collectionTreeView-keyHeader:hover { background: #797777; cursor: pointer; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 3852987b9..6fb18d4c2 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,4 +1,3 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; @@ -8,13 +7,14 @@ import { Document, listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyFunction } from '../../../Utils'; +import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from "../../util/DragManager"; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; +import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; @@ -22,11 +22,11 @@ import { EditableView } from "../EditableView"; import { DocumentView } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; +import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import { TreeView } from "./TreeView"; import React = require("react"); -import { Transform } from '../../util/Transform'; const _global = (window /* browser */ || global /* node */) as any; export type collectionTreeViewProps = { @@ -41,10 +41,13 @@ export type collectionTreeViewProps = { @observer export class CollectionTreeView extends CollectionSubView>(Document) { - private treedropDisposer?: DragManager.DragDropDisposer; + private _treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; + private _titleRef?: HTMLDivElement | HTMLInputElement | null; private _disposers: { [name: string]: IReactionDisposer } = {}; - MainEle = () => this._mainEle; + private _isDisposing = false; // notes that instance is in process of being disposed + private refList: Set = new Set(); // list of tree view items to monitor for height changes + private observer: any; // observer for mnonitoring tree view items. @computed get doc() { return this.props.Document; } @computed get dataDoc() { return this.props.DataDoc || this.doc; } @@ -54,6 +57,10 @@ export class CollectionTreeView extends CollectionSubView this._mainEle; + // these should stay in synch with counterparts in DocComponent.ts ViewBoxAnnotatableComponent @observable _isAnyChildContentActive = false; whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); @@ -62,11 +69,10 @@ export class CollectionTreeView extends CollectionSubView disposer?.()); } @@ -76,13 +82,13 @@ export class CollectionTreeView extends CollectionSubView = new Set(); - observer: any; computeHeight = () => { - if (this.isDisposing) return; - const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.paddingTop() + this.paddingBot()); - this.layoutDoc._autoHeightMargins = bodyHeight; - this.props.setHeight(this.documentTitleHeight() + bodyHeight); + if (!this._isDisposing) { + const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", "")); + const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot()); + this.layoutDoc._autoHeightMargins = bodyHeight; + this.props.setHeight(bodyHeight + titleHeight); + } } unobserveHeight = (ref: any) => { this.refList.delete(ref); @@ -101,8 +107,8 @@ export class CollectionTreeView extends CollectionSubView { - this.treedropDisposer?.(); - if (this._mainEle = ele) this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); + this._treedropDisposer?.(); + if (this._mainEle = ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); } protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { @@ -165,60 +171,54 @@ export class CollectionTreeView extends CollectionSubView { - return !this.dataDoc ? (null) : - StrCast(this.dataDoc.title)} - SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(childDocs); - this.dataDoc.title = value; - return true; - })} />; + get editableTitle() { + return StrCast(this.dataDoc.title)} + SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { + if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(this.treeChildren); + this.dataDoc.title = value; + return true; + })} />; } - documentTitle = (childDocs: Doc[]) => { - return
{ - e.stopPropagation(); - e.key === "Enter" && this.makeTextCollection(childDocs); - }}> - -
; + get documentTitle() { + return ; } childContextMenuItems = () => { const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []); @@ -263,21 +263,31 @@ export class CollectionTreeView extends CollectionSubView this._titleRef = r} + onKeyDown={e => { + if (this.outlineMode) { + e.stopPropagation(); + e.key === "Enter" && this.makeTextCollection(this.treeChildren); + } + }}> + {this.outlineMode ? this.documentTitle : this.editableTitle} +
; + } + + @computed get noviceExplainer() { + return !Doc.UserDoc().noviceMode || !this.rootDoc.explainer ? (null) : +
{this.rootDoc.explainer}
; } return35 = () => 35; @computed get buttonMenu() { - const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null); + const menuDoc = Cast(this.rootDoc.buttonMenuDoc, Doc, null); // To create a multibutton menu add a CollectionLinearView - if (menuDoc) { - - const width: number = NumCast(menuDoc._width, 30); - const height: number = NumCast(menuDoc._height, 30); - console.log(menuDoc.title, width, height); - return (
+ return !menuDoc ? null : + (
); - } } - @observable _explainerHeight: number = 0; - @computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); } @computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); } @@ -321,47 +328,82 @@ export class CollectionTreeView extends CollectionSubView NumCast(this.doc._xPadding, 15); - paddingTop = () => NumCast(this.doc._yPadding, 20); - paddingBot = () => NumCast(this.doc._yPadding, 20); + marginX = () => NumCast(this.doc._xMargin); + marginTop = () => NumCast(this.doc._yMargin); + marginBot = () => NumCast(this.doc._yMargin); documentTitleWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.panelWidth()); documentTitleHeight = () => (this.layoutDoc?.[HeightSym]() || 0) - NumCast(this.layoutDoc.autoHeightMargins); titleTransform = () => this.props.ScreenToLocalTransform().translate(-NumCast(this.doc._xPadding, 10), -NumCast(this.doc._yPadding, 20)); truncateTitleWidth = () => this.treeViewtruncateTitleWidth; onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); - panelWidth = () => (this.props.PanelWidth() - 2 * this.paddingX()) * (this.props.scaling?.() || 1); - render() { - TraceMobx(); + panelWidth = () => (this.props.PanelWidth() - 2 * this.marginX()) * (this.props.scaling?.() || 1); + + addAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; + remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; + moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => + this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false + + contentFunc = () => { const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined; - const buttonMenu = this.rootDoc.buttonMenu; - const noviceExplainer = this.rootDoc.explainer; - - return !(this.doc instanceof Doc) || !this.treeChildren ? (null) : - <> - {this.titleBar} + const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : this.titleBar; + return [ +
+ {titleBar}
- {buttonMenu || noviceExplainer ?
r && (this._explainerHeight = r.getBoundingClientRect().height))}> - {buttonMenu ? this.buttonMenu : null} - {Doc.UserDoc().noviceMode && noviceExplainer ? -
- {noviceExplainer} -
- : null - } -
: null} + {!this.buttonMenu && !this.noviceExplainer ? (null) : +
r && (this._explainerHeight = r.getBoundingClientRect().height))}> + {this.buttonMenu} + {this.noviceExplainer} +
+ }
e.stopPropagation()} onDrop={this.onTreeDrop} - ref={this.createTreeDropTarget}> + ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}>
    {this.treeViewElements}
- ; +
+ ]; + } + render() { + TraceMobx(); + + return !(this.doc instanceof Doc) || !this.treeChildren ? (null) : + this.doc.treeViewHasOverlay ? + + {this.contentFunc} + : + this.contentFunc(); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 38e027fb3..8e84b59de 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -63,6 +63,7 @@ export enum CollectionViewType { } export interface CollectionViewProps extends FieldViewProps { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) + isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) layoutEngine?: () => string; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; @@ -125,8 +126,9 @@ export class CollectionView extends ViewBoxAnnotatableComponent this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()); - private SubView = (type: CollectionViewType, props: SubCollectionViewProps) => { + private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => { TraceMobx(); + if (type === undefined) return null; switch (type) { default: case CollectionViewType.Freeform: return ; @@ -246,17 +248,13 @@ export class CollectionView extends ViewBoxAnnotatableComponent this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); } - - @observable _subView: any = undefined; - isContentActive = (outsideReaction?: boolean) => { - return this.props.isContentActive() ? true : false; + return this.props.isContentActive(); } render() { TraceMobx(); const props: SubCollectionViewProps = { ...this.props, - SetSubView: action((subView: any) => this._subView = subView), addDocument: this.addDocument, moveDocument: this.moveDocument, removeDocument: this.removeDocument, @@ -273,7 +271,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent {this.showIsTagged()} - {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} + {this.renderSubView(this.collectionViewType, props)}
); } } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index eb95bb913..5ba019698 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -476,7 +476,6 @@ export class TabMinimapView extends React.Component {
this} CollectionView={undefined} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} @@ -484,7 +483,7 @@ export class TabMinimapView extends React.Component { childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. noOverlay={true} // don't render overlay Docs since they won't scale setHeight={returnFalse} - isContentActive={returnFalse} + isContentActive={emptyFunction} isAnyChildContentActive={returnFalse} select={emptyFunction} dropAction={undefined} diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 1ebc5873e..2e33d3564 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -53,14 +53,11 @@ } } +.treeView-container-outline-active .treeView-container-active { z-index: 100; position: relative; - - .formattedTextbox-sidebar { - background-color: #ffff001f !important; - height: 500px !important; - } + pointer-events: all; } .treeView-openRight { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 7f2128230..a266c301f 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -81,7 +81,7 @@ export class TreeView extends React.Component { static _openLevelScript: Opt; private _header: React.RefObject = React.createRef(); private _tref = React.createRef(); - private _docRef: Opt; + @observable _docRef: Opt; private _selDisposer: Opt; private _editTitleScript: (() => ScriptField) | undefined; private _openScript: (() => ScriptField) | undefined; @@ -116,7 +116,8 @@ export class TreeView extends React.Component { @computed get childLinks() { return this.childDocList("links"); } @computed get childAliases() { return this.childDocList("aliases"); } @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } - @computed get selected() { return SelectionManager.Views().lastElement()?.props.Document === this.props.document; } + @computed get selected() { return SelectionManager.IsSelected(this._docRef); } + // SelectionManager.Views().lastElement()?.props.Document === this.props.document; } childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.doc), Doc, null); @@ -125,7 +126,12 @@ export class TreeView extends React.Component { DocListCastOrNull(this.doc[field]); // otherwise use the document's data field } @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc); + if (this.doc !== target && addDoc !== returnFalse) { // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse + if (this.props.removeDoc?.(doc) === true) { + return addDoc(doc); + } + } + return false; } @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { this.props.treeView.props.select(false); @@ -141,8 +147,10 @@ export class TreeView extends React.Component { this._editTitle = false; } else if (docView.isSelected()) { + const doc = docView.Document; + SelectionManager.SelectSchemaViewDoc(doc); this._editTitle = true; - this._selDisposer = reaction(() => docView.isSelected(), sel => !sel && this.setEditTitle(undefined)); + this._selDisposer = reaction(() => SelectionManager.SelectedSchemaDoc(), seldoc => seldoc !== doc && this.setEditTitle(undefined)); } else { docView.select(false); } @@ -222,7 +230,7 @@ export class TreeView extends React.Component { public static makeTextBullet() { const bullet = Docs.Create.TextDocument("-text-", { layout: CollectionView.LayoutString("data"), - title: "-title-", "sidebarColor": "transparent", "sidebarViewType": CollectionViewType.Freeform, + title: "-title-", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline", x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, backgroundColor: "transparent", _width: 1000, _height: 10 @@ -266,23 +274,25 @@ export class TreeView extends React.Component { e.stopPropagation(); } const docDragData = de.complete.docDragData; - if (docDragData) { - e.stopPropagation(); + if (docDragData && pt[0] < rect.left + rect.width) { if (docDragData.draggedDocuments[0] === this.doc) return true; - this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document); + if (this.dropDocuments(docDragData.droppedDocuments, before, inside, docDragData.dropAction, docDragData.moveDocument, docDragData.treeViewDoc === this.props.treeView.props.Document)) { + e.stopPropagation(); + } } } dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); - const canAdd = !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add") || forceAdd; + const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add")) || forceAdd; const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false; const addDoc = !inside ? parentAddDoc : (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument; if (canAdd) { - UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false)); + return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) : addDoc(d)) || added, false)); } + return false; } refTransform = (ref: HTMLDivElement | undefined | null) => { @@ -432,7 +442,7 @@ export class TreeView extends React.Component {
; } - return
    {this.renderEmbeddedDocument(false)}
; // "layout" + return
    { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, returnFalse)}
; // "layout" } get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } @@ -581,6 +591,7 @@ export class TreeView extends React.Component { } titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth())); + return18 = () => 18; /** * Renders the EditableView title element for placement into the tree. */ @@ -636,10 +647,10 @@ export class TreeView extends React.Component { moveDocument={this.move} removeDocument={this.props.removeDoc} ScreenToLocalTransform={this.getTransform} - NativeHeight={() => 18} + NativeHeight={this.return18} NativeWidth={this.titleWidth} PanelWidth={this.titleWidth} - PanelHeight={() => 18} + PanelHeight={this.return18} contextMenuItems={this.contextMenuItems} renderDepth={1} isContentActive={this.props.isContentActive} @@ -679,6 +690,7 @@ export class TreeView extends React.Component { renderBulletHeader = (contents: JSX.Element, editing: boolean) => { return <>
{ } - renderEmbeddedDocument = (asText: boolean) => { + renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { const layout = StrCast(Doc.LayoutField(this.layoutDoc)); const isExpandable = layout.includes(FormattedTextBox.name) || layout.includes(SliderBox.name); const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth; @@ -704,8 +716,8 @@ export class TreeView extends React.Component { NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined} - isContentActive={asText ? this.props.isContentActive : returnFalse} - isDocumentActive={asText ? this.props.isContentActive : returnFalse} + isContentActive={isActive} + isDocumentActive={isActive} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} hideTitle={asText} fitContentsToDoc={returnTrue} @@ -749,7 +761,7 @@ export class TreeView extends React.Component { @computed get renderDocumentAsHeader() { return <> {this.renderBullet} - {this.renderEmbeddedDocument(true)} + {this.renderEmbeddedDocument(true, this.props.isContentActive)} ; } @@ -776,13 +788,12 @@ export class TreeView extends React.Component { return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document onKeyDown={this.onKeyDown}>
  • {hideTitle && this.doc.type !== DocumentType.RTF ? - this.renderEmbeddedDocument(false) : + this.renderEmbeddedDocument(false, returnFalse) : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader : this.renderTitleAsHeader, this._editTitle)}
  • ; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 37caf508f..190d0e76a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -165,7 +165,7 @@ export class CollectionFreeFormView extends CollectionSubView this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY); zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)); - contentTransform = () => `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; + contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; getTransform = () => this.cachedGetTransform.copy(); getLocalTransform = () => this.cachedGetLocalTransform.copy(); getContainerTransform = () => this.cachedGetContainerTransform.copy(); @@ -802,7 +802,7 @@ export class CollectionFreeFormView extends CollectionSubView boolean; + isContentActive?: () => boolean | undefined; columnUnitLength(): number | undefined; toLeft?: Doc; toRight?: Doc; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index 5478bf709..006ef4df6 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -11,7 +11,7 @@ import { StyleProviderFunc } from "../../nodes/DocumentView"; interface ResizerProps { height: number; styleProvider?: StyleProviderFunc; - isContentActive?: () => boolean; + isContentActive?: () => boolean | undefined; columnUnitLength(): number | undefined; toTop?: Doc; toBottom?: Doc; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index 1306b79cb..dc35b5749 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -225,7 +225,7 @@ export interface KeysDropdownProps { fieldKey: string; ContainingCollectionDoc: Doc | undefined; ContainingCollectionView: Opt; - active?: (outsideReaction?: boolean) => boolean; + active?: (outsideReaction?: boolean) => boolean | undefined; openHeader: (column: any, screenx: number, screeny: number) => void; col: SchemaHeaderField; icon: IconProp; diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index bc5a9559f..2219345f6 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -68,7 +68,7 @@ export interface SchemaTableProps { addDocument?: (document: Doc | Doc[]) => boolean; moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; ScreenToLocalTransform: () => Transform; - active: (outsideReaction: boolean | undefined) => boolean; + active: (outsideReaction: boolean | undefined) => boolean | undefined; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6c058ff97..c8a32a911 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -138,6 +138,7 @@ export interface DocumentViewSharedProps { hideLinkButton?: boolean; hideCaptions?: boolean; ignoreAutoHeight?: boolean; + forceAutoHeight?: boolean; disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. pointerEvents?: string; scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document @@ -224,7 +225,7 @@ export class DocumentViewInternal extends DocComponent Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view); isContentActive = (outsideReaction?: boolean) => { - return CurrentUserUtils.SelectedTool !== InkTool.None || + return this.props.isContentActive() === false ? false : ( + CurrentUserUtils.SelectedTool !== InkTool.None || SnappingManager.GetIsDragging() || this.props.rootSelected() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._componentView?.isAnyChildContentActive?.() || - this.props.isContentActive() ? true : false; + this.props.isContentActive()) ? true : undefined; } @computed get contents() { TraceMobx(); @@ -1256,7 +1258,7 @@ export class DocumentView extends React.Component { position: this.props.Document.isInkMask ? "absolute" : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, - height: isButton ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : + height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), }}> void; - isContentActive: (outsideReaction?: boolean) => boolean; + isContentActive: (outsideReaction?: boolean) => boolean | undefined; isDocumentActive?: () => boolean; isSelected: (outsideReaction?: boolean) => boolean; scaling?: () => number; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 2041c7399..fb8e89da9 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -225,7 +225,7 @@ export class FilterBox extends ViewBoxBaseComponent(LinkDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } - isContentActiveFunc = () => this.isContentActive() ? true : false; + isContentActiveFunc = () => this.isContentActive(); render() { if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true); return
    { docViewPath={returnEmptyDoclist} ScreenToLocalTransform={Transform.Identity} isDocumentActive={returnFalse} - isContentActive={returnFalse} + isContentActive={emptyFunction} addDocument={returnFalse} removeDocument={returnFalse} addDocTab={returnFalse} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 7ad96bf05..0c631e5f9 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -313,7 +313,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponentLoading
    : -
    -
    +
    +