diff options
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
7 files changed, 324 insertions, 189 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 7a0fd2b31..fc5212edd 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -9,6 +9,7 @@ opacity: 0.5; transform: translate(10000px,10000px); pointer-events: all; + cursor: pointer; } .collectionfreeformlinkview-linkText { stroke: rgb(0,0,0); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index ba7e6cf9e..b546d1b78 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -25,18 +25,18 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2); let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2); let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2); - this.props.LinkDocs.map(l => { - let width = l[WidthSym](); - l.x = (x1 + x2) / 2 - width / 2; - l.y = (y1 + y2) / 2 + 10; - if (!this.props.removeDocument(l)) this.props.addDocument(l, false); - }); + // this.props.LinkDocs.map(l => { + // let width = l[WidthSym](); + // l.x = (x1 + x2) / 2 - width / 2; + // l.y = (y1 + y2) / 2 + 10; + // if (!this.props.removeDocument(l)) this.props.addDocument(l, false); + // }); e.stopPropagation(); e.preventDefault(); } } render() { - let l = this.props.LinkDocs; + // let l = this.props.LinkDocs; let a = this.props.A; let b = this.props.B; let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / NumCast(a.zoomBasis, 1) / 2); @@ -44,13 +44,13 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2); let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2); let text = ""; - let first = this.props.LinkDocs[0]; - if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : ""); - else text = "-multiple-"; + // let first = this.props.LinkDocs[0]; + // if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : ""); + // else text = "-multiple-"; return ( <> <line key="linkLine" className="collectionfreeformlinkview-linkLine" - style={{ strokeWidth: `${2 * l.length / 2}` }} + style={{ strokeWidth: `${2 * 1 / 2}` }} x1={`${x1}`} y1={`${y1}`} x2={`${x2}`} y2={`${y2}`} /> {/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle" diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index be75c6c5c..2d94f1b8e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -17,58 +17,56 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP _brushReactionDisposer?: IReactionDisposer; componentDidMount() { - this._brushReactionDisposer = reaction( - () => { - let doclist = DocListCast(this.props.Document[this.props.fieldKey]); - return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) }; - }, - () => { - let doclist = DocListCast(this.props.Document[this.props.fieldKey]); - let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : []; - views.forEach((dstDoc, i) => { - views.forEach((srcDoc, j) => { - let dstTarg = dstDoc; - let srcTarg = srcDoc; - let x1 = NumCast(srcDoc.x); - let x2 = NumCast(dstDoc.x); - let x1w = NumCast(srcDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1); - let x2w = NumCast(dstDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1); - if (x1w < 0 || x2w < 0 || i === j) { } - else { - let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => { - let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined; - return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false; - }); - let brushAction = (field: (Doc | Promise<Doc>)[]) => { - let found = findBrush(field); - if (found !== -1) { - console.log("REMOVE BRUSH " + srcTarg.title + " " + dstTarg.title); - field.splice(found, 1); - } - }; - if (Math.abs(x1 + x1w - x2) < 20) { - let linkDoc: Doc = new Doc(); - linkDoc.title = "Histogram Brush"; - linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title); - linkDoc.brushingDocs = new List([dstTarg, srcTarg]); + // this._brushReactionDisposer = reaction( + // () => { + // let doclist = DocListCast(this.props.Document[this.props.fieldKey]); + // return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) }; + // }, + // () => { + // let doclist = DocListCast(this.props.Document[this.props.fieldKey]); + // let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : []; + // views.forEach((dstDoc, i) => { + // views.forEach((srcDoc, j) => { + // let dstTarg = dstDoc; + // let srcTarg = srcDoc; + // let x1 = NumCast(srcDoc.x); + // let x2 = NumCast(dstDoc.x); + // let x1w = NumCast(srcDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1); + // let x2w = NumCast(dstDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1); + // if (x1w < 0 || x2w < 0 || i === j) { } + // else { + // let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => { + // let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined; + // return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false; + // }); + // let brushAction = (field: (Doc | Promise<Doc>)[]) => { + // let found = findBrush(field); + // if (found !== -1) { + // field.splice(found, 1); + // } + // }; + // if (Math.abs(x1 + x1w - x2) < 20) { + // let linkDoc: Doc = new Doc(); + // linkDoc.title = "Histogram Brush"; + // linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title); + // linkDoc.brushingDocs = new List([dstTarg, srcTarg]); - brushAction = (field: (Doc | Promise<Doc>)[]) => { - if (findBrush(field) === -1) { - console.log("ADD BRUSH " + srcTarg.title + " " + dstTarg.title); - field.push(linkDoc); - } - }; - } - if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>(); - if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>(); - let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []); - let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []); - brushAction(dstBrushDocs); - brushAction(srcBrushDocs); - } - }); - }); - }); + // brushAction = (field: (Doc | Promise<Doc>)[]) => { + // if (findBrush(field) === -1) { + // field.push(linkDoc); + // } + // }; + // } + // if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>(); + // if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>(); + // let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []); + // let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []); + // brushAction(dstBrushDocs); + // brushAction(srcBrushDocs); + // } + // }); + // }); + // }); } componentWillUnmount() { if (this._brushReactionDisposer) { @@ -97,6 +95,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { let srcViews = this.documentAnchors(connection.a); let targetViews = this.documentAnchors(connection.b); + let possiblePairs: { a: Doc, b: Doc, }[] = []; srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document }))); possiblePairs.map(possiblePair => { @@ -114,19 +113,16 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP }); return drawnPairs; }, [] as { a: Doc, b: Doc, l: Doc[] }[]); - return connections.map(c => { - let x = c.l.reduce((p, l) => p + l[Id], ""); - return <CollectionFreeFormLinkView key={x} A={c.a} B={c.b} LinkDocs={c.l} - removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />; - }); + return connections.map(c => <CollectionFreeFormLinkView key={c.l.reduce((p, l) => p + l[Id], "")} A={c.a} B={c.b} LinkDocs={c.l} + removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />); } render() { return ( <div className="collectionfreeformlinksview-container"> - <svg className="collectionfreeformlinksview-svgCanvas"> + {/* <svg className="collectionfreeformlinksview-svgCanvas"> {this.uniqueConnections} - </svg> + </svg> */} {this.props.children} </div> ); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 5ac2e1f9c..00407d39a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,7 +1,7 @@ @import "../../globalCssVariables"; .collectionfreeformview-ease { - position: absolute; + position: inherit; top: 0; left: 0; width: 100%; @@ -25,8 +25,9 @@ height: 100%; width: 100%; } + >.jsx-parser { - z-index:0; + z-index: 0; } //nested freeform views @@ -35,56 +36,20 @@ // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px); // background-size: 30px 30px; // } - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + opacity: 0.99; border: 0px solid $light-color-secondary; - border-radius: $border-radius; + border-radius: inherit; box-sizing: border-box; position: absolute; - .marqueeView { - overflow: hidden; - } - top: 0; - left: 0; - width: 100%; - height: 100%; -} - - -.collectionfreeformview-overlay { - .collectionfreeformview>.jsx-parser { - position: inherit; - height: 100%; - } - - >.jsx-parser { - position:absolute; - z-index:0; - } - - .formattedTextBox-cont { - background: $light-color-secondary; - overflow: visible; - } - opacity: 0.99; - border: 0px solid transparent; - border-radius: $border-radius; - box-sizing: border-box; - position:absolute; - z-index: -1; .marqueeView { overflow: hidden; } + top: 0; left: 0; width: 100%; height: 100%; - - .collectionfreeformview { - .formattedTextBox-cont { - background: yellow; - } - } } // selection border...? diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5ee16970b..4669ff142 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,23 +1,25 @@ import { action, computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; -import { BoolCast, Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; import { emptyFunction, returnOne } from "../../../../Utils"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"; +import { ContextMenu } from "../../ContextMenu"; import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; import { pageSchema } from "../../nodes/ImageBox"; +import PDFMenu from "../../pdf/PDFMenu"; import { CollectionSubView } from "../CollectionSubView"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; @@ -25,13 +27,19 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); -import PDFMenu from "../../pdf/PDFMenu"; import { Timeline } from "../../nodes/Timeline"; +import { ScriptField } from "../../../../new_fields/ScriptField"; +import { OverlayView, OverlayElementOptions } from "../../OverlayView"; +import { ScriptBox } from "../../ScriptBox"; +import { CompileScript } from "../../../util/Scripting"; + export const panZoomSchema = createSchema({ panX: "number", panY: "number", - scale: "number" + scale: "number", + arrangeScript: ScriptField, + arrangeInit: ScriptField, }); type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; @@ -45,13 +53,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private get _pwidth() { return this.props.PanelWidth(); } private get _pheight() { return this.props.PanelHeight(); } + @computed get contentBounds() { + let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined; + return { + panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0, + panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0, + scale: bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1 + }; + } + @computed get nativeWidth() { return this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.Document.nativeHeight || 0; } - public get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } + public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt') private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } - private panX = () => this.Document.panX || 0; - private panY = () => this.Document.panY || 0; - private zoomScaling = () => this.Document.scale || 1; + private panX = () => this.contentBounds.panX; + private panY = () => this.contentBounds.panY; + private zoomScaling = () => this.contentBounds.scale; private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0; // shift so pan position is at center of window for non-overlay collections private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform()); @@ -79,6 +96,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }); } + @computed get fieldExtensionDoc() { + return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); + } + + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { @@ -86,10 +108,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { let dragDoc = de.data.droppedDocuments[0]; - let zoom = NumCast(dragDoc.zoomBasis, 1); let [xp, yp] = this.getTransform().transformPoint(de.x, de.y); - let x = xp - de.data.xOffset / zoom; - let y = yp - de.data.yOffset / zoom; + let x = xp - de.data.xOffset; + let y = yp - de.data.yOffset; let dropX = NumCast(de.data.droppedDocuments[0].x); let dropY = NumCast(de.data.droppedDocuments[0].y); de.data.droppedDocuments.forEach(d => { @@ -110,10 +131,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; - let zoom = NumCast(dragDoc.zoomBasis, 1); let [xp, yp] = this.getTransform().transformPoint(de.x, de.y); - let x = xp - de.data.xOffset / zoom; - let y = yp - de.data.yOffset / zoom; + let x = xp - de.data.xOffset; + let y = yp - de.data.yOffset; let dropX = NumCast(de.data.dropDocument.x); let dropY = NumCast(de.data.dropDocument.y); dragDoc.x = x + NumCast(dragDoc.x) - dropX; @@ -152,18 +172,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (!this.isAnnotationOverlay) { PDFMenu.Instance.fadeOut(true); let minx = docs.length ? NumCast(docs[0].x) : 0; - let maxx = docs.length ? NumCast(docs[0].width) / NumCast(docs[0].zoomBasis, 1) + minx : minx; + let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; let miny = docs.length ? NumCast(docs[0].y) : 0; - let maxy = docs.length ? NumCast(docs[0].height) / NumCast(docs[0].zoomBasis, 1) + miny : miny; + let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; let ranges = docs.filter(doc => doc).reduce((range, doc) => { let x = NumCast(doc.x); - let xe = x + NumCast(doc.width) / NumCast(doc.zoomBasis, 1); + let xe = x + NumCast(doc.width); let y = NumCast(doc.y); - let ye = y + NumCast(doc.height) / NumCast(doc.zoomBasis, 1); + let ye = y + NumCast(doc.height); return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; }, [[minx, maxx], [miny, maxy]]); - let ink = Cast(this.props.Document.ink, InkField); + let ink = Cast(this.fieldExtensionDoc.ink, InkField); if (ink && ink.inkData) { ink.inkData.forEach((value: StrokeData, key: string) => { let bounds = InkingCanvas.StrokeRect(value); @@ -191,14 +211,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { + if (BoolCast(this.props.Document.lockedPosition)) return; // if (!this.props.active()) { // return; // } + if (this.props.Document.type === "pdf") { + return; + } let childSelected = this.childDocs.some(doc => { var dv = DocumentManager.Instance.getDocumentView(doc); return dv && SelectionManager.IsSelected(dv) ? true : false; }); - if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) { + if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) { return; } e.stopPropagation(); @@ -217,7 +241,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } else { // if (modes[e.deltaMode] === 'pixels') coefficient = 50; // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height?? - let deltaScale = (1 - (e.deltaY / coefficient)); + let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); } @@ -234,13 +258,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number) { - + if (BoolCast(this.props.Document.lockedPosition)) return; this.props.Document.panTransformType = "None"; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY)); this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; - this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; + this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY; + // this.props.Document.panX = panX; + // this.props.Document.panY = panY; + if (this.props.Document.scrollY) { + this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym](); + } } @action @@ -262,11 +291,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { doc.zIndex = docs.length + 1; } - focusDocument = (doc: Doc) => { + focusDocument = (doc: Doc, willZoom: boolean) => { const panX = this.Document.panX; const panY = this.Document.panY; const id = this.Document[Id]; const state = HistoryUtil.getState(); + state.initializers = state.initializers || {}; // TODO This technically isn't correct if type !== "doc", as // currently nothing is done, but we should probably push a new state if (state.type === "doc" && panX !== undefined && panY !== undefined) { @@ -283,28 +313,87 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } SelectionManager.DeselectAll(); - const newPanX = NumCast(doc.x) + NumCast(doc.width) / NumCast(doc.zoomBasis, 1) / 2; - const newPanY = NumCast(doc.y) + NumCast(doc.height) / NumCast(doc.zoomBasis, 1) / 2; + const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; + const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; const newState = HistoryUtil.getState(); - newState.initializers[id] = { panX: newPanX, panY: newPanY }; + (newState.initializers || (newState.initializers = {}))[id] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); this.setPan(newPanX, newPanY); + this.props.Document.panTransformType = "Ease"; this.props.focus(this.props.Document); + if (willZoom) { + this.setScaleToZoom(doc); + } + + } + + setScaleToZoom = (doc: Doc) => { + let p = this.props; + let PanelHeight = p.PanelHeight(); + let panelWidth = p.PanelWidth(); + + let docHeight = NumCast(doc.height); + let docWidth = NumCast(doc.width); + let targetHeight = 0.5 * PanelHeight; + let targetWidth = 0.5 * panelWidth; + + let maxScaleX: number = targetWidth / docWidth; + let maxScaleY: number = targetHeight / docHeight; + let maxApplicableScale = Math.min(maxScaleX, maxScaleY); + this.Document.scale = maxApplicableScale; } + zoomToScale = (scale: number) => { + this.Document.scale = scale; + } + + getScale = () => { + if (this.Document.scale) { + return this.Document.scale; + } + return 1; + } - getDocumentViewProps(document: Doc): DocumentViewProps { + + getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps { + let self = this; + let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; + let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc); + return { + DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined, + Document: layoutDoc, + addDocument: this.props.addDocument, + removeDocument: this.props.removeDocument, + moveDocument: this.props.moveDocument, + ScreenToLocalTransform: this.getTransform, + renderDepth: this.props.renderDepth + 1, + selectOnLoad: layoutDoc[Id] === this._selectOnLoaded, + PanelWidth: layoutDoc[WidthSym], + PanelHeight: layoutDoc[HeightSym], + ContentScaling: returnOne, + ContainingCollectionView: this.props.CollectionView, + focus: this.focusDocument, + parentActive: this.props.active, + whenActiveChanged: this.props.whenActiveChanged, + bringToFront: this.bringToFront, + addDocTab: this.props.addDocTab, + zoomToScale: this.zoomToScale, + getScale: this.getScale + }; + } + getDocumentViewProps(layoutDoc: Doc): DocumentViewProps { return { - Document: document, + DataDoc: this.props.DataDoc, + Document: this.props.Document, addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, ScreenToLocalTransform: this.getTransform, - isTopMost: false, - selectOnLoad: document[Id] === this._selectOnLoaded, - PanelWidth: document[WidthSym], - PanelHeight: document[HeightSym], + renderDepth: this.props.renderDepth + 1, + selectOnLoad: layoutDoc[Id] === this._selectOnLoaded, + PanelWidth: layoutDoc[WidthSym], + PanelHeight: layoutDoc[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, @@ -312,19 +401,41 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { whenActiveChanged: this.props.whenActiveChanged, bringToFront: this.bringToFront, addDocTab: this.props.addDocTab, + zoomToScale: this.zoomToScale, + getScale: this.getScale }; } + getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } { + const result = script.script.run(params); + if (!result.success) { + return {}; + } + return result.result === undefined ? {} : result.result; + } + @computed.struct get views() { let curPage = FieldValue(this.Document.curPage, -1); - let docviews = this.childDocs.reduce((prev, doc) => { + const initScript = this.Document.arrangeInit; + const script = this.Document.arrangeScript; + let state: any = undefined; + const docs = this.childDocs; + if (initScript) { + const initResult = initScript.script.run({ docs, collection: this.Document }); + if (initResult.success) { + state = initResult.result; + } + } + let docviews = docs.reduce((prev, doc) => { if (!(doc instanceof Doc)) return prev; var page = NumCast(doc.page, -1); - if (Math.round(page) === Math.round(curPage) || page === -1) { + if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { let minim = BoolCast(doc.isMinimized, false); if (minim === undefined || !minim) { - prev.push(<CollectionFreeFormDocumentView key={doc[Id]} {...this.getDocumentViewProps(doc)} />); + const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {}; + state = pos.state === undefined ? state : pos.state; + prev.push(<CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />); } } return prev; @@ -340,25 +451,84 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } + onContextMenu = () => { + ContextMenu.Instance.addItem({ + description: "Arrange contents in grid", + event: async () => { + const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); + UndoManager.RunInBatch(() => { + if (docs) { + let startX = this.Document.panX || 0; + let x = startX; + let y = this.Document.panY || 0; + let i = 0; + const width = Math.max(...docs.map(doc => NumCast(doc.width))); + const height = Math.max(...docs.map(doc => NumCast(doc.height))); + for (const doc of docs) { + doc.x = x; + doc.y = y; + x += width + 20; + if (++i === 6) { + i = 0; + x = startX; + y += height + 20; + } + } + } + }, "arrange contents"); + } + }); + ContextMenu.Instance.addItem({ + description: "Add freeform arrangement", + event: () => { + let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => { + let overlayDisposer: () => void = emptyFunction; + const script = this.Document[key]; + let originalText: string | undefined = undefined; + if (script) originalText = script.script.originalScript; + // tslint:disable-next-line: no-unnecessary-callback-wrapper + let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => { + const script = CompileScript(text, { + params, + requiredType, + typecheck: false + }); + if (!script.compiled) { + onError(script.errors.map(error => error.messageText).join("\n")); + return; + } + const docs = DocListCast(this.Document[this.props.fieldKey]); + docs.map(d => d.transition = "transform 1s"); + this.Document[key] = new ScriptField(script); + overlayDisposer(); + setTimeout(() => docs.map(d => d.transition = undefined), 1200); + }} />; + overlayDisposer = OverlayView.Instance.addElement(scriptingBox, options); + }; + addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, { collection: "Doc", docs: "Doc[]" }, undefined); + addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}"); + } + }); + } + private childViews = () => [ <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />, ...this.views ] render() { - const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`; const easing = () => this.props.Document.panTransformType === "Ease"; + + Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); return ( - <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel} - style={{ borderRadius: "inherit" }} - onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} > + <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel} + onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onContextMenu={this.onContextMenu}> <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected} addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}> <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> - <CollectionFreeFormLinksView {...this.props} key="freeformLinks"> - <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} > + <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} > {this.childViews} </InkingCanvas> </CollectionFreeFormLinksView> @@ -376,7 +546,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { @computed get overlayView() { return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"} - isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />); + renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />); } render() { return this.overlayView; @@ -386,8 +556,9 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & @observer class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { @computed get backgroundView() { + let props = this.props; return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} - isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />); + renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />); } render() { return this.props.Document.backgroundLayout ? this.backgroundView : (null); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 6e8ec8662..9fc2e44fb 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -21,6 +21,6 @@ white-space:nowrap; } .marquee-legend::after { - content: "Press: c (collection), s (summary), r (replace) or Delete" + content: "Press: c (collection), s (summary), or Delete" } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3f7efcb66..b765517a2 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,7 +1,7 @@ import * as htmlToImage from "html-to-image"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../../new_fields/Doc"; +import { Doc, FieldResult } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; @@ -14,7 +14,6 @@ import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; -import { SearchBox } from "../../SearchBox"; import { Templates } from "../../Templates"; import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; @@ -45,14 +44,12 @@ export class MarqueeView extends React.Component<MarqueeViewProps> _commandExecuted = false; @action - cleanupInteractions = (all: boolean = false, rem_keydown: boolean = true) => { + cleanupInteractions = (all: boolean = false) => { if (all) { document.removeEventListener("pointerup", this.onPointerUp, true); document.removeEventListener("pointermove", this.onPointerMove, true); } - if (all) { - document.removeEventListener("keydown", this.marqueeCommand, true); - } + document.removeEventListener("keydown", this.marqueeCommand, true); this._visible = false; } @@ -79,7 +76,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } ns.map(line => { let indent = line.search(/\S|$/); - let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); + let newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); this.props.addDocument(newBox, false); y += 40 * this.props.getTransform().Scale; }); @@ -89,13 +86,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps> navigator.clipboard.readText().then(text => { let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== ""); if (ns.length === 1 && text.startsWith("http")) { - this.props.addDocument(Docs.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer + this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer } else { this.pasteTable(ns, x, y); } }); } else if (!e.ctrlKey) { - let newBox = Docs.TextDocument({ width: 200, height: 30, x: x, y: y, title: "-typed text-" }); + let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); newBox.proto!.autoHeight = true; this.props.addLiveTextDocument(newBox); } @@ -137,7 +134,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> doc.width = 200; docList.push(doc); } - let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 }); + let newCol = Docs.Create.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 }); this.props.addDocument(newCol, false); } @@ -150,7 +147,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps> PreviewCursor.Visible = false; this.cleanupInteractions(true); if (e.button === 2 || (e.button === 0 && e.altKey)) { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); document.addEventListener("pointermove", this.onPointerMove, true); document.addEventListener("pointerup", this.onPointerUp, true); document.addEventListener("keydown", this.marqueeCommand, true); @@ -184,20 +180,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @action onPointerUp = (e: PointerEvent): void => { - console.log("pointer up!"); + if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); + // console.log("pointer up!"); if (this._visible) { - console.log("visible"); + // console.log("visible"); let mselect = this.marqueeSelect(); if (!e.shiftKey) { SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); - mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true); - } - else { - console.log("invisible"); - this.cleanupInteractions(true); } + //console.log("invisible"); + this.cleanupInteractions(true); if (e.altKey) { e.preventDefault(); @@ -230,6 +224,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; } + get ink() { + let container = this.props.container.Document; + let containerKey = this.props.container.props.fieldKey; + return Cast(container[containerKey + "_ink"], InkField); + } + + set ink(value: InkField | undefined) { + let container = Doc.GetProto(this.props.container.Document); + let containerKey = this.props.container.props.fieldKey; + container[containerKey + "_ink"] = value; + } + @undoBatch @action marqueeCommand = async (e: KeyboardEvent) => { @@ -241,15 +247,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps> e.stopPropagation(); (e as any).propagationIsStopped = true; this.marqueeSelect().map(d => this.props.removeDocument(d)); - let ink = Cast(this.props.container.props.Document.ink, InkField); - if (ink) { - this.marqueeInkDelete(ink.inkData); + if (this.ink) { + this.marqueeInkDelete(this.ink.inkData); } SelectionManager.DeselectAll(); this.cleanupInteractions(false); e.stopPropagation(); } - if (e.key === "c" || e.key === "s" || e.key === "S" || e.key === "e" || e.key === "p") { + if (e.key === "c" || e.key === "s" || e.key === "S") { this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); @@ -265,21 +270,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return d; }); } - let ink = Cast(this.props.container.props.Document.ink, InkField); - let inkData = ink ? ink.inkData : undefined; - let zoomBasis = NumCast(this.props.container.props.Document.scale, 1); - let newCollection = Docs.FreeformDocument(selected, { + let inkData = this.ink ? this.ink.inkData : undefined; + let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panX: 0, panY: 0, - borderRounding: e.key === "e" ? -1 : undefined, backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", width: bounds.width, height: bounds.height, - ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined, - title: e.key === "s" || e.key === "S" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection", + title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", }); + newCollection.data_ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; this.marqueeInkDelete(inkData); if (e.key === "s") { @@ -290,14 +292,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps> d.page = -1; return d; }); - let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); newCollection.proto!.summaryDoc = summary; selected = [newCollection]; newCollection.x = bounds.left + bounds.width; summary.proto!.subBulletDocs = new List<Doc>(selected); //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" summary.templates = new List<string>([Templates.Bullet.Layout]); - let container = Docs.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" }); + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" }); container.viewType = CollectionViewType.Stacking; this.props.addLiveTextDocument(container); // }); @@ -309,7 +311,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> d.page = -1; return d; }); - let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); newCollection.proto!.summaryDoc = summary; selected = [newCollection]; newCollection.x = bounds.left + bounds.width; @@ -356,7 +358,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> let idata = new Map(); ink.forEach((value: StrokeData, key: string, map: any) => !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); - Doc.SetOnPrototype(this.props.container.props.Document, "ink", new InkField(idata)); + this.ink = new InkField(idata); } } |
