From e24bf03dd4edab2e5be2d73a6dc7f9c01fc5da85 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 5 Jun 2019 13:04:58 -0400 Subject: cleaned up handling of dropped urls and prosemirror clippings. --- src/client/views/collections/CollectionSubView.tsx | 23 ++++++++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 20 +-------- src/client/views/nodes/FormattedTextBox.tsx | 47 +++++++++++++++++----- 3 files changed, 56 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 31e662da5..06d77fec5 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -17,6 +17,7 @@ import { CollectionPDFView } from "./CollectionPDFView"; import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; import React = require("react"); +import { FormattedTextBox } from "../nodes/FormattedTextBox"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; @@ -166,11 +167,23 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { e.stopPropagation(); e.preventDefault(); - if (html && html.indexOf(document.location.origin)) { // prosemirror text containing link to dash document - let start = html.indexOf(window.location.origin); - let path = html.substr(start, html.length - start); - let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => (f instanceof Doc) && this.props.addDocument(f, false)); + if (html && FormattedTextBox.IsFragment(html)) { + let href = FormattedTextBox.GetHref(html); + if (href) { + let docid = FormattedTextBox.GetDocFromUrl(href); + if (docid) { // prosemirror text containing link to dash document + DocServer.GetRefField(docid).then(f => { + if (f instanceof Doc) { + if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + (f instanceof Doc) && this.props.addDocument(f, false); + } + }); + } else { + this.props.addDocument && this.props.addDocument(Docs.WebDocument(href, options)); + } + } else if (text) { + this.props.addDocument && this.props.addDocument(Docs.TextDocument({ ...options, documentText: "@@@" + text }), false); + } return; } if (html && html.indexOf(" { var pt = this.getTransform().transformPoint(e.pageX, e.pageY); - let html = e.dataTransfer.getData("text/html"); - if (html && html.indexOf(document.location.origin)) { // prosemirror text containing link to dash document - e.stopPropagation(); - e.preventDefault(); - let start = html.indexOf(window.location.origin); - let path = html.substr(start, html.length - start); - let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.prepend("/doc/"), "").split("?")[0]; - DocServer.GetRefField(docid).then(f => { - if (f instanceof Doc) { - f.x = pt[0]; - f.y = pt[1]; - (f instanceof Doc) && this.props.addDocument(f, false); - } - }); - return; - } else { - super.onDrop(e, { x: pt[0], y: pt[1] }); - } + super.onDrop(e, { x: pt[0], y: pt[1] }); } onDragOver = (): void => { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9eef38e0a..478e7ce93 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,7 +12,7 @@ import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; -import { DocUtils } from '../../documents/Documents'; +import { DocUtils, Docs } from '../../documents/Documents'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorKeymap"; @@ -27,6 +27,7 @@ import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); +import { Id } from '../../../new_fields/FieldSymbols'; library.add(faEdit); library.add(faSmile); @@ -68,15 +69,40 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _ref: React.RefObject; private _proseRef: React.RefObject; private _editorView: Opt; - private _gotDown: boolean = false; + private _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + private _lastState: any = undefined; + private _applyingChange: boolean = false; private _dropDisposer?: DragManager.DragDropDisposer; + private _linkClicked = ""; private _reactionDisposer: Opt; private _inputReactionDisposer: Opt; private _proxyReactionDisposer: Opt; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + @observable _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; public static InputBoxOverlayScroll: number = 0; + public static IsFragment(html: string) { + return html.indexOf("data-pm-slice") !== -1; + } + public static GetHref(html: string): string { + let parser = new DOMParser(); + let parsedHtml = parser.parseFromString(html, 'text/html'); + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && + (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; + } + return ""; + } + public static GetDocFromUrl(url: string) { + if (url.startsWith(document.location.origin)) { + let start = url.indexOf(window.location.origin); + let path = url.substr(start, url.length - start); + let docid = path.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + return docid; + } + return ""; + } constructor(props: FieldViewProps) { super(props); @@ -88,9 +114,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - _applyingChange: boolean = false; - _lastState: any = undefined; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._lastState = this._editorView.state.apply(tx); @@ -221,7 +245,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - _linkClicked = ""; onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); @@ -235,14 +258,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { href = parent.childNodes[0].href; } - if (href && href.indexOf(DocServer.prepend("/doc/")) === 0) { - this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + if (href) { + if (href.indexOf(DocServer.prepend("/doc/")) === 0) { + this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; + } else { + let webDoc = Docs.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) }); + this.props.addDocument && this.props.addDocument(webDoc); + this._linkClicked = webDoc[Id]; + } e.stopPropagation(); e.preventDefault(); } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - this._gotDown = true; e.preventDefault(); } } @@ -302,7 +330,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } - _toolTipTextMenu: TooltipTextMenu | undefined = undefined; tooltipLinkingMenuPlugin() { let myprops = this.props; return new Plugin({ @@ -338,8 +365,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - @observable - _entered = false; @action onPointerEnter = (e: React.PointerEvent) => { this._entered = true; -- cgit v1.2.3-70-g09d2 From 1087133d71ef2ac7df6976c61685167ed0fe71d9 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 5 Jun 2019 14:09:40 -0400 Subject: cleaned up selections somewhat so that PDFs don't flicker when manipulating annotations. --- src/client/util/SelectionManager.ts | 24 +++++++++------------- src/client/views/DocumentDecorations.tsx | 1 - .../views/collections/CollectionBaseView.tsx | 2 +- src/client/views/collections/CollectionPDFView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - .../collections/collectionFreeForm/MarqueeView.tsx | 1 - src/client/views/nodes/PDFBox.tsx | 16 +++++++-------- 7 files changed, 20 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 8c92c2023..5311841e1 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -21,6 +21,13 @@ export namespace SelectionManager { doc.props.whenActiveChanged(true); } } + @action + DeSelectDoc(doc: DocumentView): void { + if (manager.SelectedDocuments.indexOf(doc) !== -1) { + manager.SelectedDocuments.splice(manager.SelectedDocuments.indexOf(doc), 1); + doc.props.whenActiveChanged(false); + } + } @action DeselectAll(): void { @@ -28,16 +35,6 @@ export namespace SelectionManager { manager.SelectedDocuments = []; FormattedTextBox.InputBoxOverlay = undefined; } - @action - ReselectAll() { - let sdocs = manager.SelectedDocuments.map(d => d); - manager.SelectedDocuments = []; - return sdocs; - } - @action - ReselectAll2(sdocs: DocumentView[]) { - sdocs.map(s => SelectionManager.SelectDoc(s, true)); - } } const manager = new Manager(); @@ -49,6 +46,9 @@ export namespace SelectionManager { export function IsSelected(doc: DocumentView): boolean { return manager.SelectedDocuments.indexOf(doc) !== -1; } + export function DeSelectDoc(doc: DocumentView): void { + manager.DeSelectDoc(doc); + } export function DeselectAll(except?: Doc): void { let found: DocumentView | undefined = undefined; @@ -62,10 +62,6 @@ export namespace SelectionManager { if (found) manager.SelectDoc(found, false); } - export function ReselectAll() { - let sdocs = manager.ReselectAll(); - setTimeout(() => manager.ReselectAll2(sdocs), 0); - } export function SelectedDocuments(): Array { return manager.SelectedDocuments; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 37895516a..cdd537760 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -463,7 +463,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.stopPropagation(); this._resizing = ""; this.Interacting = false; - SelectionManager.ReselectAll(); if (e.button === 0) { e.preventDefault(); this._isPointerDown = false; diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 9350dc974..cb92464e5 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -158,7 +158,7 @@ export class CollectionBaseView extends React.Component { @action.bound moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - if (this.props.Document === targetCollection) { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } if (this.removeDocument(doc)) { diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 5e51437a4..25c1535b3 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -70,7 +70,7 @@ export class CollectionPDFView extends React.Component { return ( <> - {this.props.isSelected() ? this.uIButtons : (null)} + {this.props.active() ? this.uIButtons : (null)} ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 634b4dfa0..582f93c2e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -104,7 +104,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.bringToFront(d); }); - SelectionManager.ReselectAll(); } return true; } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 9ec941eff..81ca4c29a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -325,7 +325,6 @@ export class MarqueeView extends React.Component } else { this.props.addDocument(newCollection, false); - SelectionManager.DeselectAll(); this.props.selectDocuments([newCollection]); } this.cleanupInteractions(false); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index aa29a7170..855c744e6 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -68,20 +68,21 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _currAnno: any = []; @observable private _interactive: boolean = false; - @observable private _loaded: boolean = false; @computed private get curPage() { return NumCast(this.Document.curPage, 1); } @computed private get thumbnailPage() { return NumCast(this.props.Document.thumbnailPage, -1); } componentDidMount() { - let wasSelected = this.props.isSelected(); + let wasSelected = this.props.active(); this._reactionDisposer = reaction( - () => [this.props.isSelected(), this.curPage], + () => [this.props.active(), this.curPage], () => { - if (this.curPage > 0 && !this.props.isTopMost && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) { - this.saveThumbnail(); - } - wasSelected = this._interactive = this.props.isSelected(); + setTimeout(action(() => { // this forces the active() check to happen after all changes in a transaction have occurred. + if (this.curPage > 0 && !this.props.isTopMost && this.curPage !== this.thumbnailPage && wasSelected && !this.props.active()) { + this.saveThumbnail(); + } + wasSelected = this._interactive = this.props.active(); + }), 0); }, { fireImmediately: true }); @@ -276,7 +277,6 @@ export class PDFBox extends DocComponent(PdfDocumen if (this._perPageInfo.length === 0) { //Makes sure it only runs once this._perPageInfo = [...Array(page._transport.numPages)]; } - this._loaded = true; } @action -- cgit v1.2.3-70-g09d2 From 24d2e6d7d24aff60c7a3084bc5d5d660ef6b4608 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 5 Jun 2019 14:49:35 -0400 Subject: PDF scroll/UI shows up only when its active now. --- src/client/util/SelectionManager.ts | 11 ----------- src/client/views/collections/CollectionPDFView.tsx | 2 +- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 5311841e1..6e0e6fd3a 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -21,14 +21,6 @@ export namespace SelectionManager { doc.props.whenActiveChanged(true); } } - @action - DeSelectDoc(doc: DocumentView): void { - if (manager.SelectedDocuments.indexOf(doc) !== -1) { - manager.SelectedDocuments.splice(manager.SelectedDocuments.indexOf(doc), 1); - doc.props.whenActiveChanged(false); - } - } - @action DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); @@ -46,9 +38,6 @@ export namespace SelectionManager { export function IsSelected(doc: DocumentView): boolean { return manager.SelectedDocuments.indexOf(doc) !== -1; } - export function DeSelectDoc(doc: DocumentView): void { - manager.DeSelectDoc(doc); - } export function DeselectAll(except?: Doc): void { let found: DocumentView | undefined = undefined; diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 25c1535b3..bf887ce7c 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -70,7 +70,7 @@ export class CollectionPDFView extends React.Component { return ( <> - {this.props.active() ? this.uIButtons : (null)} + {renderProps.active() ? this.uIButtons : (null)} ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 582f93c2e..d23bef2d3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -348,7 +348,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { - + ); } -- cgit v1.2.3-70-g09d2 From 2ce189fdf66a0f01b38d28c73c8f6f1247933b3d Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 5 Jun 2019 15:35:48 -0400 Subject: fixed undo for ink strokes. --- src/client/views/InkingCanvas.tsx | 14 +++++++++----- src/client/views/InkingStroke.tsx | 4 +++- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 42ab08001..5d4ea76cd 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -60,7 +60,7 @@ export class InkingCanvas extends React.Component { } set inkData(value: Map) { - Doc.SetOnPrototype(this.props.Document, "ink", new InkField(value)); + Doc.GetProto(this.props.Document).ink = new InkField(value); } @action @@ -74,7 +74,7 @@ export class InkingCanvas extends React.Component { e.stopPropagation(); e.preventDefault(); - this.previousState = this.inkData; + this.previousState = new Map(this.inkData); if (InkingControl.Instance.selectedTool !== InkTool.Eraser) { // start the new line, saves a uuid to represent the field of the stroke @@ -106,10 +106,10 @@ export class InkingCanvas extends React.Component { const batch = UndoManager.StartBatch("One ink stroke"); const oldState = this.previousState || new Map; this.previousState = undefined; - const newState = this.inkData; + const newState = new Map(this.inkData); UndoManager.AddEvent({ undo: () => this.inkData = oldState, - redo: () => this.inkData = newState, + redo: () => this.inkData = newState }); batch.end(); } @@ -134,9 +134,13 @@ export class InkingCanvas extends React.Component { return { x, y }; } - @undoBatch @action removeLine = (id: string): void => { + if (!this.previousState) { + this.previousState = new Map(this.inkData); + document.addEventListener("pointermove", this.onPointerMove, true); + document.addEventListener("pointerup", this.onPointerUp, true); + } let data = this.inkData; data.delete(id); this.inkData = data; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 37b1f5899..b8d428d31 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -28,6 +28,8 @@ export class InkingStroke extends React.Component { deleteStroke = (e: React.PointerEvent): void => { if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) { this.props.deleteCallback(this.props.id); + e.stopPropagation(); + e.preventDefault(); } } @@ -50,7 +52,7 @@ export class InkingStroke extends React.Component { render() { let pathStyle = this.createStyle(); let pathData = this.parseData(this.props.line); - let pathlength = this.props.count; // bcz: this is needed to force reactions to the line data changes + let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes let marker = this.props.tool === InkTool.Highlighter ? "-marker" : ""; let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none"; -- cgit v1.2.3-70-g09d2 From 1870fbb3cc9ec3fc1054e4afb6b5bf54326e3a25 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 5 Jun 2019 18:41:10 -0400 Subject: small fixes to stacking view and bullet sorting. --- src/client/views/DocumentDecorations.tsx | 10 +++++----- src/client/views/collections/CollectionStackingView.tsx | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cdd537760..e845e0407 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -521,11 +521,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let templates: Map = new Map(); Array.from(Object.values(Templates.TemplateList)).map(template => { - let sorted = SelectionManager.ViewsSortedVertically().slice().sort((doc1, doc2) => { - if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1; - if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1; - return 0; - }); + let sorted = SelectionManager.ViewsSortedVertically(); // slice().sort((doc1, doc2) => { + // if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.x)) return 1; + // if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1; + // return 0; + // }); let docTemps = sorted.reduce((res: string[], doc: DocumentView, i) => { let temps = doc.props.Document.templates; if (temps instanceof List) { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index da7ea50c6..b2a7955ee 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { action, computed, IReactionDisposer, reaction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; @@ -16,13 +16,13 @@ import { CollectionSubView } from "./CollectionSubView"; export class CollectionStackingView extends CollectionSubView(doc => doc) { _masonryGridRef: HTMLDivElement | null = null; _heightDisposer?: IReactionDisposer; - get gridGap() { return 10; } - get gridSize() { return 20; } - get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } - get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 4 * this.gridGap : NumCast(this.props.Document.columnWidth, 250); } + @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } + @computed get gridSize() { return NumCast(this.props.Document.gridSize, 20); } + @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } + @computed get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 4 * this.gridGap : NumCast(this.props.Document.columnWidth, 250); } componentDidMount() { - this._heightDisposer = reaction(() => [this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized]), this.columnWidth, this.props.PanelHeight()], + this._heightDisposer = reaction(() => [this.props.Document.gridGap, this.gridSize, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], () => { if (this.singleColumn) { this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => { -- cgit v1.2.3-70-g09d2 From 404dcc71558d8de69369aa499227e5168091351d Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 5 Jun 2019 22:42:43 -0400 Subject: Can now pass list to schema function to get list of that type Mapped type of a schema can be another schema Added tests for schemas Other minor fixes --- package.json | 1 + src/client/views/MainOverlayTextBox.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/new_fields/Schema.ts | 34 +++++- src/new_fields/Types.ts | 8 +- test/test.ts | 127 ++++++++++++++++++++- 6 files changed, 163 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/package.json b/package.json index df0767b50..438572b57 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "css-loader": "^2.1.1", "file-loader": "^3.0.1", "fork-ts-checker-webpack-plugin": "^1.0.2", + "jsdom": "^15.1.1", "mocha": "^5.2.0", "sass-loader": "^7.1.0", "scss-loader": "0.0.1", diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 24327b995..d1224febe 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -102,6 +102,6 @@ export class MainOverlayTextBox extends React.Component ; } - else return (null); Z + else return (null); } } \ 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 81ca4c29a..c699b3437 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -92,7 +92,7 @@ export class MarqueeView extends React.Component this.pasteTable(ns, x, y); } }); - } else { + } else if (!e.ctrlKey) { let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); this.props.addLiveTextDocument(newBox); } diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts index 250f3c975..40ffaecd5 100644 --- a/src/new_fields/Schema.ts +++ b/src/new_fields/Schema.ts @@ -1,5 +1,7 @@ import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType, DefaultFieldConstructor } from "./Types"; import { Doc, Field } from "./Doc"; +import { ObjectField } from "./ObjectField"; +import { RefField } from "./RefField"; type AllToInterface = { 1: ToInterface> & AllToInterface>, @@ -10,10 +12,16 @@ export const emptySchema = createSchema({}); export const Document = makeInterface(emptySchema); export type Document = makeInterface<[typeof emptySchema]>; +export interface InterfaceFunc { + (docs: Doc[]): makeInterface[]; + (): makeInterface; + (doc: Doc): makeInterface; +} + export type makeInterface = AllToInterface & Doc & { proto: Doc | undefined }; // export function makeInterface(schemas: T): (doc: U) => All; // export function makeInterface(schema: T): (doc: U) => makeInterface; -export function makeInterface(...schemas: T): (doc?: Doc) => makeInterface { +export function makeInterface(...schemas: T): InterfaceFunc { let schema: Interface = {}; for (const s of schemas) { for (const key in s) { @@ -25,10 +33,19 @@ export function makeInterface(...schemas: T): (doc?: Doc) const field = receiver.doc[prop]; if (prop in schema) { const desc = (schema as any)[prop]; - if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) { + if (typeof desc === "object" && "defaultVal" in desc && "type" in desc) {//defaultSpec return Cast(field, desc.type, desc.defaultVal); + } else if (typeof desc === "function" && !ObjectField.isPrototypeOf(desc) && !RefField.isPrototypeOf(desc)) { + const doc = Cast(field, Doc); + if (doc === undefined) { + return undefined; + } else if (doc instanceof Doc) { + return desc(doc); + } else { + return doc.then(doc => doc && desc(doc)); + } } else { - return Cast(field, (schema as any)[prop]); + return Cast(field, desc); } } return field; @@ -38,14 +55,21 @@ export function makeInterface(...schemas: T): (doc?: Doc) return true; } }); - return function (doc?: Doc) { - doc = doc || new Doc; + const fn = (doc: Doc) => { if (!(doc instanceof Doc)) { throw new Error("Currently wrapping a schema in another schema isn't supported"); } const obj = Object.create(proto, { doc: { value: doc, writable: false } }); return obj; }; + return function (doc?: Doc | Doc[]) { + doc = doc || new Doc; + if (doc instanceof Doc) { + return fn(doc); + } else { + return doc.map(fn); + } + }; } export type makeStrictInterface = Partial>; diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts index c04dd5e6d..8dd893aa4 100644 --- a/src/new_fields/Types.ts +++ b/src/new_fields/Types.ts @@ -2,7 +2,7 @@ import { Field, Opt, FieldResult, Doc } from "./Doc"; import { List } from "./List"; import { RefField } from "./RefField"; -export type ToType | ListSpec | DefaultFieldConstructor> = +export type ToType = T extends "string" ? string : T extends "number" ? number : T extends "boolean" ? boolean : @@ -10,7 +10,8 @@ export type ToType | ListSpec | DefaultFie // T extends { new(...args: any[]): infer R } ? (R | Promise) : never; T extends DefaultFieldConstructor ? never : T extends { new(...args: any[]): List } ? never : - T extends { new(...args: any[]): infer R } ? R : never; + T extends { new(...args: any[]): infer R } ? R : + T extends (doc?: Doc) => infer R ? R : never; export type ToConstructor = T extends string ? "string" : @@ -38,9 +39,10 @@ export type Tail = ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : []; export type HasTail = T extends ([] | [any]) ? false : true; +export type InterfaceValue = ToConstructor | ListSpec | DefaultFieldConstructor | ((doc?: Doc) => any); //TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial export interface Interface { - [key: string]: ToConstructor | ListSpec | DefaultFieldConstructor; + [key: string]: InterfaceValue; // [key: string]: ToConstructor | ListSpec; } diff --git a/test/test.ts b/test/test.ts index 91dc43379..a7d453af3 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1,9 +1,17 @@ import { expect } from 'chai'; import 'mocha'; +const { JSDOM } = require('jsdom'); +const dom = new JSDOM("", { + url: "http://localhost:1050" +}); +(global as any).window = dom.window; + + import { autorun, reaction } from "mobx"; import { Doc } from '../src/new_fields/Doc'; import { Cast } from '../src/new_fields/Types'; - +import { createSchema, makeInterface, defaultSpec } from '../src/new_fields/Schema'; +import { ImageField } from '../src/new_fields/URLField'; describe("Document", () => { it('should hold fields', () => { let key = "Test"; @@ -33,3 +41,120 @@ describe("Document", () => { expect(ran).to.equal(true); }); }); + +const testSchema1 = createSchema({ + a: "number", + b: "string", + c: "boolean", + d: ImageField, + e: Doc +}); + +type TestDoc = makeInterface<[typeof testSchema1]>; +const TestDoc = makeInterface(testSchema1); + +const testSchema2 = createSchema({ + a: defaultSpec("boolean", true), + b: defaultSpec("number", 5), + c: defaultSpec("string", "hello world") +}); + +type TestDoc2 = makeInterface<[typeof testSchema2]>; +const TestDoc2 = makeInterface(testSchema2); + +const testSchema3 = createSchema({ + a: TestDoc2 +}); + +type TestDoc3 = makeInterface<[typeof testSchema3]>; +const TestDoc3 = makeInterface(testSchema3); + +describe("Schema", () => { + it("should do the right thing 1", () => { + const test1 = new Doc; + const test2 = new Doc; + const ifield = new ImageField(new URL("http://google.com")); + test1.a = 5; + test1.b = "hello"; + test1.c = true; + test1.d = ifield; + test1.e = test2; + const doc = TestDoc(test1); + expect(doc.a).to.equal(5); + expect(doc.b).to.equal("hello"); + expect(doc.c).to.equal(true); + expect(doc.d).to.equal(ifield); + expect(doc.e).to.equal(test2); + }); + + it("should do the right thing 2", () => { + const test1 = new Doc; + const test2 = new Doc; + const ifield = new ImageField(new URL("http://google.com")); + test1.a = "hello"; + test1.b = 5; + test1.c = test2; + test1.d = true; + test1.e = ifield; + const doc = TestDoc(test1); + expect(doc.a).to.equal(undefined); + expect(doc.b).to.equal(undefined); + expect(doc.c).to.equal(undefined); + expect(doc.d).to.equal(undefined); + expect(doc.e).to.equal(undefined); + }); + + it("should do the right thing 2", () => { + const test1 = new Doc; + const test2 = new Doc; + const ifield = new ImageField(new URL("http://google.com")); + test1.a = "hello"; + test1.b = 5; + test1.c = test2; + test1.d = true; + test1.e = ifield; + const doc = TestDoc(test1); + expect(doc.a).to.equal(undefined); + expect(doc.b).to.equal(undefined); + expect(doc.c).to.equal(undefined); + expect(doc.d).to.equal(undefined); + expect(doc.e).to.equal(undefined); + }); + + it("should do the right thing 3", () => { + const doc = TestDoc2(); + expect(doc.a).to.equal(true); + expect(doc.b).to.equal(5); + expect(doc.c).to.equal("hello world"); + + const d2 = new Doc; + d2.a = false; + d2.b = 4; + d2.c = "goodbye"; + const doc2 = TestDoc2(d2); + expect(doc2.a).to.equal(false); + expect(doc2.b).to.equal(4); + expect(doc2.c).to.equal("goodbye"); + + const d3 = new Doc; + d3.a = "hello"; + d3.b = false; + d3.c = 5; + const doc3 = TestDoc2(d3); + expect(doc3.a).to.equal(true); + expect(doc3.b).to.equal(5); + expect(doc3.c).to.equal("hello world"); + }); + + it("should do the right thing 4", async () => { + const test1 = new Doc; + const test2 = new Doc; + const doc = TestDoc3(test1); + expect(doc.a).to.equal(undefined); + test1.a = test2; + const doc2 = doc.a as TestDoc2;//(await doc.a)!; + expect(doc2.a).to.equal(true); + expect(doc2.b).to.equal(5); + expect(doc2.c).to.equal("hello world"); + }); +}); -- cgit v1.2.3-70-g09d2 From dee6c838fe4dcfab3f6ea22c40506d7a34259757 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 6 Jun 2019 15:33:02 -0400 Subject: removed zoomBasis from resizing. adjusted stackpanel. --- src/client/views/DocumentDecorations.tsx | 7 +++++-- .../views/collections/CollectionSchemaView.tsx | 5 ++--- .../views/collections/CollectionStackingView.tsx | 24 ++++++++++++++-------- 3 files changed, 22 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index e845e0407..ee76f718d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -445,11 +445,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> doc.y = (doc.y || 0) + dY * (actualdH - height); if (nwidth > 0 && nheight > 0) { if (Math.abs(dW) > Math.abs(dH)) { - doc.zoomBasis = zoomBasis * width / actualdW; + doc.width = zoomBasis * actualdW; + // doc.zoomBasis = zoomBasis * width / actualdW; } else { - doc.zoomBasis = zoomBasis * height / actualdH; + doc.width = nwidth / nheight * zoomBasis * actualdH; + //doc.zoomBasis = zoomBasis * height / actualdH; } + doc.height = nheight / nwidth * doc.width; } else { doc.width = zoomBasis * actualdW; if (docHeightBefore === doc.height) doc.height = zoomBasis * actualdH; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 26ce4cc54..b9e5a5b65 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -431,13 +431,12 @@ export class CollectionSchemaPreview extends React.Component; - return (
+ return (
{!this.props.Document || !this.props.width ? (null) : ( -
+
doc) { @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } @computed get gridSize() { return NumCast(this.props.Document.gridSize, 20); } @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } - @computed get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 4 * this.gridGap : NumCast(this.props.Document.columnWidth, 250); } + @computed get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 4 * this.gridGap : Math.min(this.props.PanelWidth() - 4 * this.gridGap, NumCast(this.props.Document.columnWidth, 250)); } + singleColDocHeight(d: Doc) { + let nw = NumCast(d.nativeWidth); + let nh = NumCast(d.nativeHeight); + let aspect = nw && nh ? nh / nw : 1; + let wid = Math.min(d[WidthSym](), this.columnWidth); + return (nw && nh) ? wid * aspect : d[HeightSym](); + } componentDidMount() { this._heightDisposer = reaction(() => [this.props.Document.gridGap, this.gridSize, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], () => { if (this.singleColumn) { this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => { - let hgt = d[HeightSym](); - let wid = d[WidthSym](); let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); - if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, wid); + let aspect = nw && nh ? nh / nw : 1; + let wid = Math.min(d[WidthSym](), this.columnWidth); + let hgt = (nw && nh) ? wid * aspect : d[HeightSym](); return height + hgt + 2 * this.gridGap; }, this.gridGap * 2); } @@ -79,11 +86,11 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth; let margin = colWidth() < this.columnWidth ? "auto" : undefined; let rowHeight = () => { - let hgt = d[HeightSym](); let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); - if (nw && nh) hgt = nh / nw * colWidth(); - return hgt; + let aspect = nw && nh ? nh / nw : 1; + let wid = Math.min(d[WidthSym](), this.columnWidth); + return (nw && nh) ? wid * aspect : d[HeightSym](); } let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]()); return
doc) { return this.childDocs.filter(d => !d.isMinimized).map(d => { let dref = React.createRef(); let dxf = () => this.getDocTransform(d, dref.current!); - let colSpan = Math.ceil(Math.min(d[WidthSym](), this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap)); + let colSpan = Math.ceil((this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap)); let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap)); let childFocus = (doc: Doc) => { doc.libraryBrush = true; @@ -163,7 +170,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { padding: `${topMargin}px 0px 0px ${leftMargin}px`, width: this.singleColumn ? "100%" : `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`, height: "100%", - overflow: "hidden", marginRight: "auto", position: "relative", gridGap: this.gridGap, -- cgit v1.2.3-70-g09d2 From 3ed872edc6478a6106c61a99cbcc94a9412136a8 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 6 Jun 2019 16:11:41 -0400 Subject: cleaning up details. --- .../views/collections/CollectionStackingView.tsx | 49 +++++++++------------- 1 file changed, 20 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index b85023955..50194ba95 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -16,10 +16,12 @@ import { CollectionSubView } from "./CollectionSubView"; export class CollectionStackingView extends CollectionSubView(doc => doc) { _masonryGridRef: HTMLDivElement | null = null; _heightDisposer?: IReactionDisposer; + _gridSize = 1; + @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); } + @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); } @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } - @computed get gridSize() { return NumCast(this.props.Document.gridSize, 20); } @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } - @computed get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 4 * this.gridGap : Math.min(this.props.PanelWidth() - 4 * this.gridGap, NumCast(this.props.Document.columnWidth, 250)); } + @computed get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 2 * this.xMargin : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); } singleColDocHeight(d: Doc) { let nw = NumCast(d.nativeWidth); @@ -29,17 +31,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return (nw && nh) ? wid * aspect : d[HeightSym](); } componentDidMount() { - this._heightDisposer = reaction(() => [this.props.Document.gridGap, this.gridSize, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], + this._heightDisposer = reaction(() => [this.yMargin, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], () => { if (this.singleColumn) { - this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => { - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); - let aspect = nw && nh ? nh / nw : 1; - let wid = Math.min(d[WidthSym](), this.columnWidth); - let hgt = (nw && nh) ? wid * aspect : d[HeightSym](); - return height + hgt + 2 * this.gridGap; - }, this.gridGap * 2); + this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => + height + this.singleColDocHeight(d) + this.yMargin + , this.yMargin); } }, { fireImmediately: true }); } @@ -85,18 +82,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { let script = undefined; let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth; let margin = colWidth() < this.columnWidth ? "auto" : undefined; - let rowHeight = () => { - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); - let aspect = nw && nh ? nh / nw : 1; - let wid = Math.min(d[WidthSym](), this.columnWidth); - return (nw && nh) ? wid * aspect : d[HeightSym](); - } + let rowHeight = () => this.singleColDocHeight(d); let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]()); return
+ style={{ marginTop: `${i ? this.yMargin : 0}px`, width: colWidth(), height: rowHeight(), marginLeft: margin, marginRight: margin }} > doc) { return this.childDocs.filter(d => !d.isMinimized).map(d => { let dref = React.createRef(); let dxf = () => this.getDocTransform(d, dref.current!); - let colSpan = Math.ceil((this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap)); - let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this.gridSize + this.gridGap)); + let colSpan = Math.ceil((this.columnWidth + this.gridGap) / (this._gridSize + this.gridGap)); + let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this._gridSize + this.gridGap)); let childFocus = (doc: Doc) => { doc.libraryBrush = true; this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered. @@ -158,23 +149,23 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { }) } render() { - let leftMargin = 2 * this.gridGap; - let topMargin = 2 * this.gridGap; - let itemCols = Math.ceil(this.columnWidth / (this.gridSize + this.gridGap)); - let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap))); + let leftMargin = this.xMargin; + let topMargin = this.yMargin; + let itemCols = Math.ceil(this.columnWidth / (this._gridSize + this.gridGap)); + let cells = Math.floor((this.props.PanelWidth() - 2 * leftMargin) / (itemCols * (this._gridSize + this.gridGap))); return (
e.stopPropagation()}>
{this.singleColumn ? this.singleColumnChildren : this.children} -- cgit v1.2.3-70-g09d2 From c97070d2a754aa30a1352cf938add2a2b2a4060a Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 6 Jun 2019 16:28:04 -0400 Subject: from last --- src/client/views/collections/CollectionStackingView.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 50194ba95..ddd9471ab 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -81,13 +81,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { let dref = React.createRef(); let script = undefined; let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth; - let margin = colWidth() < this.columnWidth ? "auto" : undefined; let rowHeight = () => this.singleColDocHeight(d); let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]()); return
+ style={{ marginTop: `${i ? this.yMargin : 0}px`, width: colWidth(), height: rowHeight(), marginLeft: "auto", marginRight: "auto" }} > doc) { render() { let leftMargin = this.xMargin; let topMargin = this.yMargin; - let itemCols = Math.ceil(this.columnWidth / (this._gridSize + this.gridGap)); + let itemCols = Math.floor(this.columnWidth / (this._gridSize + this.gridGap)); let cells = Math.floor((this.props.PanelWidth() - 2 * leftMargin) / (itemCols * (this._gridSize + this.gridGap))); return (
e.stopPropagation()}>
Date: Thu, 6 Jun 2019 18:01:16 -0400 Subject: final changes to stack collection? --- src/client/views/collections/CollectionStackingView.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ddd9471ab..af7e02e06 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -106,10 +106,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @computed get children() { - return this.childDocs.filter(d => !d.isMinimized).map(d => { + return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { let dref = React.createRef(); let dxf = () => this.getDocTransform(d, dref.current!); - let colSpan = Math.ceil((this.columnWidth + this.gridGap) / (this._gridSize + this.gridGap)); + let colSpan = 1;//Math.ceil((this.columnWidth + this.gridGap) / (this._gridSize + this.gridGap)); let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this._gridSize + this.gridGap)); let childFocus = (doc: Doc) => { doc.libraryBrush = true; @@ -148,22 +148,21 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { }) } render() { - let leftMargin = this.xMargin; - let topMargin = this.yMargin; - let itemCols = Math.floor(this.columnWidth / (this._gridSize + this.gridGap)); - let cells = Math.floor((this.props.PanelWidth() - 2 * leftMargin) / (itemCols * (this._gridSize + this.gridGap))); + let cols = this.singleColumn ? 1 : Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + 2 * this.gridGap)); + let templatecols = ""; + for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `; return (
e.stopPropagation()}>
-- cgit v1.2.3-70-g09d2 From cdc7c701cb27e94b7fd5edf2cb088f5357199900 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 6 Jun 2019 18:47:26 -0400 Subject: sort vertical/horizontal switched. --- src/client/util/SelectionManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 6e0e6fd3a..0e22d576c 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -54,7 +54,7 @@ export namespace SelectionManager { export function SelectedDocuments(): Array { return manager.SelectedDocuments; } - export function ViewsSortedVertically(): DocumentView[] { + export function ViewsSortedHorizontally(): DocumentView[] { let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => { if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1; if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1; @@ -62,7 +62,7 @@ export namespace SelectionManager { }); return sorted; } - export function ViewsSortedHorizontally(): DocumentView[] { + export function ViewsSortedVertically(): DocumentView[] { let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => { if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1; if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1; -- cgit v1.2.3-70-g09d2 From 2906c86289825ba92cccc3ccc50434ea4a4326d6 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Thu, 6 Jun 2019 21:02:14 -0400 Subject: Added computed fields --- src/client/util/Scripting.ts | 3 +- src/client/util/SerializationHelper.ts | 14 ++- src/client/util/type_decls.d | 49 ++++++---- src/debug/Repl.tsx | 5 +- src/fields/ScriptField.ts | 174 ++++++++++++++++----------------- src/new_fields/util.ts | 29 ++---- 6 files changed, 140 insertions(+), 134 deletions(-) (limited to 'src') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index beaf5cb03..40e2ad6bb 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -12,6 +12,7 @@ import { Doc, Field } from '../../new_fields/Doc'; import { ImageField, PdfField, VideoField, AudioField } from '../../new_fields/URLField'; import { List } from '../../new_fields/List'; import { RichTextField } from '../../new_fields/RichTextField'; +import { ScriptField, ComputedField } from '../../fields/ScriptField'; export interface ScriptSucccess { success: true; @@ -45,7 +46,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an return { compiled: false, errors: diagnostics }; } - let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField]; + let fieldTypes = [Doc, ImageField, PdfField, VideoField, AudioField, List, RichTextField, ScriptField, ComputedField, CompileScript]; let paramNames = ["Docs", ...fieldTypes.map(fn => fn.name)]; let params: any[] = [Docs, ...fieldTypes]; let compiledFunction = new Function(...paramNames, `return ${script}`); diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 7ded85e43..a7246d7c4 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -45,13 +45,17 @@ export namespace SerializationHelper { throw Error(`type '${obj.__type}' not registered. Make sure you register it using a @Deserializable decorator`); } - const value = deserialize(serializationTypes[obj.__type], obj); + const type = serializationTypes[obj.__type]; + const value = deserialize(type.ctor, obj); + if (type.afterDeserialize) { + type.afterDeserialize(value); + } serializing -= 1; return value; } } -let serializationTypes: { [name: string]: any } = {}; +let serializationTypes: { [name: string]: { ctor: { new(): any }, afterDeserialize?: (obj: any) => void } } = {}; let reverseMap: { [ctor: string]: string } = {}; export interface DeserializableOpts { @@ -59,9 +63,9 @@ export interface DeserializableOpts { withFields(fields: string[]): Function; } -export function Deserializable(name: string): DeserializableOpts; +export function Deserializable(name: string, afterDeserialize?: (obj: any) => void): DeserializableOpts; export function Deserializable(constructor: { new(...args: any[]): any }): void; -export function Deserializable(constructor: { new(...args: any[]): any } | string): DeserializableOpts | void { +export function Deserializable(constructor: { new(...args: any[]): any } | string, afterDeserialize?: (obj: any) => void): DeserializableOpts | void { function addToMap(name: string, ctor: { new(...args: any[]): any }) { const schema = getDefaultModelSchema(ctor) as any; if (schema.targetClass !== ctor) { @@ -69,7 +73,7 @@ export function Deserializable(constructor: { new(...args: any[]): any } | strin setDefaultModelSchema(ctor, newSchema); } if (!(name in serializationTypes)) { - serializationTypes[name] = ctor; + serializationTypes[name] = { ctor, afterDeserialize }; reverseMap[ctor.name] = name; } else { throw new Error(`Name ${name} has already been registered as deserializable`); diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 557f6f574..b3c049d02 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -141,17 +141,12 @@ declare abstract class RefField { readonly [Id]: FieldId; constructor(); - // protected [HandleUpdate]?(diff: any): void; - - // abstract [ToScriptString](): string; } -declare abstract class ObjectField { - protected [OnUpdate](diff?: any): void; - private [Parent]?: RefField | ObjectField; - // abstract [Copy](): ObjectField; +declare type FieldId = string; - // abstract [ToScriptString](): string; +declare abstract class ObjectField { + abstract [Copy](): ObjectField; } declare abstract class URLField extends ObjectField { @@ -161,32 +156,48 @@ declare abstract class URLField extends ObjectField { constructor(url: URL); } -declare class AudioField extends URLField { } -declare class VideoField extends URLField { } -declare class ImageField extends URLField { } -declare class WebField extends URLField { } -declare class PdfField extends URLField { } - -declare type FieldId = string; +declare class AudioField extends URLField { [Copy](): ObjectField; } +declare class VideoField extends URLField { [Copy](): ObjectField; } +declare class ImageField extends URLField { [Copy](): ObjectField; } +declare class WebField extends URLField { [Copy](): ObjectField; } +declare class PdfField extends URLField { [Copy](): ObjectField; } +// @ts-ignore +declare type Extract = T extends U ? T : never; declare type Field = number | string | boolean | ObjectField | RefField; +declare type FieldWaiting = T extends undefined ? never : Promise; +declare type FieldResult = Opt | FieldWaiting>; declare type Opt = T | undefined; declare class Doc extends RefField { constructor(); - [key: string]: Field | undefined; + [key: string]: FieldResult; // [ToScriptString](): string; } declare class ListImpl extends ObjectField { constructor(fields?: T[]); [index: number]: T | (T extends RefField ? Promise : never); - // [ToScriptString](): string; - // [Copy](): ObjectField; + [Copy](): ObjectField; } // @ts-ignore declare const console: any; -declare const Documents: any; +interface DocumentOptions { } + +declare const Docs: { + ImageDocument(url: string, options?: DocumentOptions): Doc; + VideoDocument(url: string, options?: DocumentOptions): Doc; + // HistogramDocument(url:string, options?:DocumentOptions); + TextDocument(options?: DocumentOptions): Doc; + PdfDocument(url: string, options?: DocumentOptions): Doc; + WebDocument(url: string, options?: DocumentOptions): Doc; + HtmlDocument(html: string, options?: DocumentOptions): Doc; + KVPDocument(document: Doc, options?: DocumentOptions): Doc; + FreeformDocument(documents: Doc[], options?: DocumentOptions): Doc; + SchemaDocument(columns: string[], documents: Doc[], options?: DocumentOptions): Doc; + TreeDocument(documents: Doc[], options?: DocumentOptions): Doc; + StackingDocument(documents: Doc[], options?: DocumentOptions): Doc; +}; diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx index c2db3bdcb..91b711c79 100644 --- a/src/debug/Repl.tsx +++ b/src/debug/Repl.tsx @@ -4,6 +4,8 @@ import { observer } from 'mobx-react'; import { observable, computed } from 'mobx'; import { CompileScript } from '../client/util/Scripting'; import { makeInterface } from '../new_fields/Schema'; +import { ObjectField } from '../new_fields/ObjectField'; +import { RefField } from '../new_fields/RefField'; @observer class Repl extends React.Component { @@ -42,7 +44,8 @@ class Repl extends React.Component { return (

{command.command}

-
{JSON.stringify(command.result, null, 2)}
+ {/*
{JSON.stringify(command.result, null, 2)}
*/} +
{command.result instanceof RefField || command.result instanceof ObjectField ? "object" : String(command.result)}
); }); diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index ae532c9e2..05ed57ea5 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -1,101 +1,97 @@ -// import { Field, FieldId } from "./Field"; -// import { Types } from "../server/Message"; -// import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting"; -// import { Server } from "../client/Server"; -// import { Without } from "../Utils"; +import { ObjectField } from "../new_fields/ObjectField"; +import { CompiledScript, CompileScript } from "../client/util/Scripting"; +import { Copy, ToScriptString, Parent, SelfProxy } from "../new_fields/FieldSymbols"; +import { serializable, createSimpleSchema, map, primitive, object, deserialize, PropSchema, custom, SKIP } from "serializr"; +import { Deserializable } from "../client/util/SerializationHelper"; +import { computed } from "mobx"; -// export interface SerializableOptions extends Without { -// capturedIds: { [id: string]: string }; -// } +function optional(propSchema: PropSchema) { + return custom(value => { + if (value !== undefined) { + return propSchema.serializer(value); + } + return SKIP; + }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => { + if (jsonValue !== undefined) { + return propSchema.deserializer(jsonValue, callback, context, oldValue); + } + return SKIP; + }); +} -// export interface ScriptData { -// script: string; -// options: SerializableOptions; -// } +const optionsSchema = createSimpleSchema({ + requiredType: true, + addReturn: true, + typecheck: true, + params: optional(map(primitive())) +}); -// export class ScriptField extends Field { -// private _script?: CompiledScript; -// get script(): CompiledScript { -// return this._script!; -// } -// private options?: ScriptData; +function deserializeScript(script: ScriptField) { + const comp = CompileScript(script.scriptString, script.options); + if (!comp.compiled) { + throw new Error("Couldn't compile loaded script"); + } + (script as any)._script = comp; +} -// constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) { -// super(id); +@Deserializable("script", deserializeScript) +export class ScriptField extends ObjectField { + protected readonly _script: CompiledScript; -// this._script = script; + constructor(script: CompiledScript) { + super(); -// if (save) { -// Server.UpdateField(this); -// } -// } + this._script = script; + } -// ToScriptString() { -// return "new ScriptField(...)"; -// } + @serializable(object(optionsSchema)) + get options() { + return this._script.options; + } -// GetValue() { -// return this.script; -// } + @serializable(true) + get scriptString(): string { + return this._script.originalScript; + } -// TrySetValue(): boolean { -// throw new Error("Script fields currently can't be modified"); -// } + // init(callback: (res: Field) => any) { + // const options = this.options!; + // const keys = Object.keys(options.options.capturedIds); + // Server.GetFields(keys).then(fields => { + // let captured: { [name: string]: Field } = {}; + // keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); + // const opts: ScriptOptions = { + // addReturn: options.options.addReturn, + // params: options.options.params, + // requiredType: options.options.requiredType, + // capturedVariables: captured + // }; + // const script = CompileScript(options.script, opts); + // if (!script.compiled) { + // throw new Error("Can't compile script"); + // } + // this._script = script; + // callback(this); + // }); + // } -// UpdateFromServer() { -// throw new Error("Script fields currently can't be updated"); -// } + [Copy](): ObjectField { + return new ScriptField(this._script); + } -// static FromJson(id: string, data: ScriptData): ScriptField { -// let field = new ScriptField(undefined, id, false); -// field.options = data; -// return field; -// } + [ToScriptString]() { + return "script field"; + } +} -// init(callback: (res: Field) => any) { -// const options = this.options!; -// const keys = Object.keys(options.options.capturedIds); -// Server.GetFields(keys).then(fields => { -// let captured: { [name: string]: Field } = {}; -// keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); -// const opts: ScriptOptions = { -// addReturn: options.options.addReturn, -// params: options.options.params, -// requiredType: options.options.requiredType, -// capturedVariables: captured -// }; -// const script = CompileScript(options.script, opts); -// if (!script.compiled) { -// throw new Error("Can't compile script"); -// } -// this._script = script; -// callback(this); -// }); -// } - -// ToJson() { -// const { options, originalScript } = this.script; -// let capturedIds: { [id: string]: string } = {}; -// for (const capt in options.capturedVariables) { -// capturedIds[options.capturedVariables[capt].Id] = capt; -// } -// const opts: SerializableOptions = { -// ...options, -// capturedIds -// }; -// delete (opts as any).capturedVariables; -// return { -// id: this.Id, -// type: Types.Script, -// data: { -// script: originalScript, -// options: opts, -// }, -// }; -// } - -// Copy(): Field { -// //Script fields are currently immutable, so we can fake copy them -// return this; -// } -// } \ No newline at end of file +@Deserializable("computed") +export class ComputedField extends ScriptField { + @computed + get value() { + const val = this._script.run({ this: (this[Parent] as any)[SelfProxy] }); + if (val.success) { + return val.result; + } + return undefined; + } +} \ No newline at end of file diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 2b304c373..ad07d05a3 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -7,6 +7,7 @@ import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; import { action } from "mobx"; import { Parent, OnUpdate, Update, Id } from "./FieldSymbols"; +import { ComputedField } from "../fields/ScriptField"; export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { if (SerializationHelper.IsSerializing()) { @@ -60,33 +61,22 @@ export function getter(target: any, prop: string | symbol | number, receiver: an } return getField(target, prop); } -function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) { - if (!protoField) return undefined; - let field = protoField[prop]; - if (field instanceof Promise) { - cb && field.then(cb); - return field; - } else { - cb && cb(field); - return field; - } -} -//TODO The callback parameter is never being passed in currently, so we should be able to get rid of it. -export function getField(target: any, prop: string | number, ignoreProto: boolean = false, callback?: (field: Field | undefined) => void): any { +export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any { const field = target.__fields[prop]; if (field instanceof ProxyField) { - return field.value(callback); + return field.value(); + } + if (field instanceof ComputedField) { + return field.value; } if (field === undefined && !ignoreProto && prop !== "proto") { const proto = getField(target, "proto", true); if (proto instanceof Doc) { - return getProtoField(proto, prop, callback); - } else if (proto instanceof Promise) { - return proto.then(async proto => getProtoField(proto, prop, callback)); + return proto[prop]; } + return undefined; } - callback && callback(field); return field; } @@ -95,7 +85,8 @@ export function deleteProperty(target: any, prop: string | number | symbol) { delete target[prop]; return true; } - throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); + target[prop] = undefined; + return true; } export function updateFunction(target: any, prop: any, value: any, receiver: any) { -- cgit v1.2.3-70-g09d2 From 852bd5c7b7e8d6c01ff0e6dbb5cef35f4bfd9617 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 6 Jun 2019 22:49:25 -0400 Subject: added unfreeze for images, pdfs, etc. allows margins. improved rotate for images. added ctrl-drag to resize text. fixed minimizing to work with scaled views --- src/client/views/DocumentDecorations.tsx | 20 +++++++++++++--- .../views/collections/CollectionStackingView.tsx | 13 +++++++++- src/client/views/nodes/DocumentView.tsx | 28 +++++++--------------- src/client/views/nodes/ImageBox.tsx | 14 +++++++---- 4 files changed, 47 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index ee76f718d..f22fecb98 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; import { Doc } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { listSpec } from "../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../new_fields/Types"; import { emptyFunction, Utils } from "../../Utils"; import { Docs } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; @@ -443,16 +443,30 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let actualdH = Math.max(height + (dH * scale), 20); doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); + let proto = Doc.GetProto(doc); + let fixedAspect = e.ctrlKey || (!BoolCast(proto.ignoreAspect, false) && nwidth && nheight); + if (fixedAspect && (!nwidth || !nheight)) { + proto.nativeWidth = doc.width; + proto.nativeHeight = doc.height; + proto.ignoreAspect = true; + } if (nwidth > 0 && nheight > 0) { if (Math.abs(dW) > Math.abs(dH)) { + if (!fixedAspect) proto.nativeWidth = zoomBasis * actualdW / (doc.width || 1) * NumCast(proto.nativeWidth); doc.width = zoomBasis * actualdW; // doc.zoomBasis = zoomBasis * width / actualdW; + if (fixedAspect) doc.height = nheight / nwidth * doc.width; + else doc.height = zoomBasis * actualdH; + proto.nativeHeight = (doc.height || 0) / doc.width * NumCast(proto.nativeWidth); } else { - doc.width = nwidth / nheight * zoomBasis * actualdH; + if (!fixedAspect) proto.nativeHeight = zoomBasis * actualdH / (doc.height || 1) * NumCast(proto.nativeHeight); + doc.height = zoomBasis * actualdH; //doc.zoomBasis = zoomBasis * height / actualdH; + if (fixedAspect) doc.width = nwidth / nheight * doc.height; + else doc.width = zoomBasis * actualdW; + proto.nativeWidth = (doc.width || 0) / doc.height * NumCast(proto.nativeHeight); } - doc.height = nheight / nwidth * doc.width; } else { doc.width = zoomBasis * actualdW; if (docHeightBefore === doc.height) doc.height = zoomBasis * actualdH; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index af7e02e06..eeae498a6 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -11,6 +11,7 @@ import { DocumentView } from "../nodes/DocumentView"; import { CollectionSchemaPreview } from "./CollectionSchemaView"; import "./CollectionStackingView.scss"; import { CollectionSubView } from "./CollectionSubView"; +import { ContextMenu } from "../ContextMenu"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { @@ -147,12 +148,22 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
); }) } + onContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + ContextMenu.Instance.addItem({ + description: "Toggle multi-column", + event: () => this.props.Document.singleColumn = !BoolCast(this.props.Document.singleColumn, false), icon: "file-pdf" + }); + } + } render() { - let cols = this.singleColumn ? 1 : Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + 2 * this.gridGap)); + let cols = this.singleColumn ? 1 : Math.min(this.childDocs.filter(d => !d.isMinimized).length, + Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + 2 * this.gridGap))); let templatecols = ""; for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `; return (
e.stopPropagation()}>
(Docu toggleMinimized = async () => { let minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc); if (minimizedDoc) { - let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint( + let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint( NumCast(minimizedDoc.x) - NumCast(this.Document.x), NumCast(minimizedDoc.y) - NumCast(this.Document.y)); this.props.collapseToPoint && this.props.collapseToPoint(scrpt, await DocListCastAsync(minimizedDoc.maximizedDocs)); } @@ -246,7 +246,7 @@ export class DocumentView extends DocComponent(Docu this.props.addDocTab(getDispDoc(maxDoc), maxLocation))); } } else { - let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); + let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); this.props.collapseToPoint && this.props.collapseToPoint(scrpt, expandedProtoDocs); } } @@ -374,23 +374,13 @@ export class DocumentView extends DocComponent(Docu } freezeNativeDimensions = (e: React.MouseEvent): void => { - if (NumCast(this.props.Document.nativeWidth)) { - let proto = Doc.GetProto(this.props.Document); - let nw = proto.nativeWidth; - let nh = proto.nativeHeight; - proto.nativeWidth = proto.nativeHeight = undefined; - this.props.Document.width = this.props.Document.frozenWidth; - this.props.Document.height = this.props.Document.frozenHeight; - } - else { - let scale = this.props.ScreenToLocalTransform().Scale * NumCast(this.props.Document.zoomBasis, 1); - let scr = this.screenRect(); - let proto = Doc.GetProto(this.props.Document); - this.props.Document.frozenWidth = this.props.Document.width; - this.props.Document.frozenHeight = this.props.Document.height; - this.props.Document.height = proto.nativeHeight = scr.height * scale; - this.props.Document.width = proto.nativeWidth = scr.width * scale; + let proto = Doc.GetProto(this.props.Document); + if (proto.ignoreAspect === undefined && !proto.nativeWidth) { + proto.nativeWidth = this.props.PanelWidth(); + proto.nativeHeight = this.props.PanelHeight(); + proto.ignoreAspect = true; } + proto.ignoreAspect = !BoolCast(proto.ignoreAspect, false); } @action @@ -412,7 +402,7 @@ export class DocumentView extends DocComponent(Docu subitems.push({ description: "Open Right Alias", event: () => this.props.addDocTab && this.props.addDocTab(Doc.MakeAlias(this.props.Document), "onRight"), icon: "caret-square-right" }); subitems.push({ description: "Open Fields", event: this.fieldsClicked, icon: "layer-group" }); cm.addItem({ description: "Open...", subitems: subitems }); - cm.addItem({ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze", event: this.freezeNativeDimensions, icon: "edit" }); + cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "edit" }); cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" }); cm.addItem({ diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 06ecc794b..0d19508fa 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -136,12 +136,15 @@ export class ImageBox extends DocComponent(ImageD subitems.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" }); subitems.push({ description: "Rotate", event: action(() => { - this.props.Document.rotation = (NumCast(this.props.Document.rotation) + 90) % 360; + let proto = Doc.GetProto(this.props.Document); let nw = this.props.Document.nativeWidth; - this.props.Document.nativeWidth = this.props.Document.nativeHeight; - this.props.Document.nativeHeight = nw; + let nh = this.props.Document.nativeHeight; let w = this.props.Document.width; - this.props.Document.width = this.props.Document.height; + let h = this.props.Document.height; + proto.rotation = (NumCast(this.props.Document.rotation) + 90) % 360; + proto.nativeWidth = nh; + proto.nativeHeight = nw; + this.props.Document.width = h; this.props.Document.height = w; }), icon: "expand-arrows-alt" }); @@ -198,6 +201,7 @@ export class ImageBox extends DocComponent(ImageD let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx let nativeWidth = FieldValue(this.Document.nativeWidth, pw); + let nativeHeight = FieldValue(this.Document.nativeHeight, 0); let paths: string[] = ["http://www.cs.brown.edu/~bcz/noImage.png"]; // this._curSuffix = ""; // if (w > 20) { @@ -211,7 +215,7 @@ export class ImageBox extends DocComponent(ImageD let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive"; let rotation = NumCast(this.props.Document.rotation, 0); let aspect = (rotation % 180) ? this.props.Document[HeightSym]() / this.props.Document[WidthSym]() : 1; - let shift = (rotation % 180) ? (this.props.Document[HeightSym]() - this.props.Document[WidthSym]() / aspect) / 2 : 0; + let shift = (rotation % 180) ? (nativeHeight - nativeWidth / aspect) / 2 : 0; return (
Date: Fri, 7 Jun 2019 10:21:55 -0400 Subject: fixed some pinterest layout issues. --- .../views/collections/CollectionBaseView.scss | 11 ++++++++ .../views/collections/CollectionBaseView.tsx | 4 +-- .../views/collections/CollectionStackingView.scss | 20 ++++--------- .../views/collections/CollectionStackingView.tsx | 33 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 3 +- 5 files changed, 36 insertions(+), 35 deletions(-) create mode 100644 src/client/views/collections/CollectionBaseView.scss (limited to 'src') diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss new file mode 100644 index 000000000..1f5acb96a --- /dev/null +++ b/src/client/views/collections/CollectionBaseView.scss @@ -0,0 +1,11 @@ +@import "../globalCssVariables"; +#collectionBaseView { + border-width: 0; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; + border-color: $light-color-secondary; + border-style: solid; + border-radius: 0 0 $border-radius $border-radius; + box-sizing: border-box; + border-radius: inherit; + pointer-events: all; +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index cb92464e5..ddec587a9 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -9,6 +9,7 @@ import { Cast, FieldValue, NumCast, PromiseValue } from '../../../new_fields/Typ import { SelectionManager } from '../../util/SelectionManager'; import { ContextMenu } from '../ContextMenu'; import { FieldViewProps } from '../nodes/FieldView'; +import './CollectionBaseView.scss'; export enum CollectionViewType { Invalid, @@ -178,8 +179,7 @@ export class CollectionBaseView extends React.Component { }; const viewtype = this.collectionViewType; return ( -
{viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 4d84aaaa9..af194aec9 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -1,19 +1,6 @@ @import "../globalCssVariables"; - .collectionStackingView { - top: 0; - left: 0; - display: flex; - flex-direction: column; - width: 100%; - position: absolute; overflow-y: auto; - border-width: 0; - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; - border-color: $light-color-secondary; - border-style: solid; - border-radius: 0 0 $border-radius $border-radius; - box-sizing: border-box; .collectionStackingView-docView-container { width: 45%; @@ -33,9 +20,12 @@ width:100%; height:100%; position: absolute; - } - .collectionStackingView-masonryGrid { display:grid; + top: 0; + left: 0; + width: 100%; + position: absolute; + } .collectionStackingView-description { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index eeae498a6..b8eb4ac84 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -32,11 +32,12 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return (nw && nh) ? wid * aspect : d[HeightSym](); } componentDidMount() { - this._heightDisposer = reaction(() => [this.yMargin, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], + this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], () => { if (this.singleColumn) { - this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => - height + this.singleColDocHeight(d) + this.yMargin + let children = this.childDocs.filter(d => !d.isMinimized); + this.props.Document.height = children.reduce((height, d, i) => + height + this.singleColDocHeight(d) + (i === children.length - 1 ? this.yMargin : this.gridGap) , this.yMargin); } }, { fireImmediately: true }); @@ -78,7 +79,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @computed get singleColumnChildren() { - return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { + let children = this.childDocs.filter(d => !d.isMinimized); + return children.map((d, i) => { let dref = React.createRef(); let script = undefined; let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth; @@ -87,7 +89,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return
+ style={{ width: colWidth(), height: rowHeight(), marginLeft: "auto", marginRight: "auto" }} > doc) { } @computed get children() { + trace(); return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { let dref = React.createRef(); let dxf = () => this.getDocTransform(d, dref.current!); - let colSpan = 1;//Math.ceil((this.columnWidth + this.gridGap) / (this._gridSize + this.gridGap)); let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this._gridSize + this.gridGap)); let childFocus = (doc: Doc) => { doc.libraryBrush = true; @@ -124,7 +126,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { height: NumCast(d.nativeHeight, d[HeightSym]()), transformOrigin: "top left", gridRowEnd: `span ${rowSpan}`, - gridColumnEnd: `span ${colSpan}`, + gridColumnEnd: `span 1`, transform: `scale(${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())})` }} > doc) { } } render() { - let cols = this.singleColumn ? 1 : Math.min(this.childDocs.filter(d => !d.isMinimized).length, - Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + 2 * this.gridGap))); + let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.childDocs.filter(d => !d.isMinimized).length, + Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); let templatecols = ""; for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `; return ( -
e.stopPropagation()}> +
e.stopPropagation()} style={{ - padding: `${this.yMargin}px ${this.xMargin}px 0px ${this.xMargin}px`, + padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`, margin: "auto", - width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap)}px`, + width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`, height: "100%", position: "relative", gridGap: this.gridGap, @@ -178,8 +180,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { }} > {this.singleColumn ? this.singleColumnChildren : this.children} -
-
+
); } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 36a5358f0..ff2b9842f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; @@ -439,7 +439,6 @@ export class DocumentView extends DocComponent(Docu var scaling = this.props.ContentScaling(); var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%"; - return (
Date: Fri, 7 Jun 2019 11:18:28 -0400 Subject: fixes for computed script deserialization --- src/client/util/type_decls.d | 3 +++ src/fields/ScriptField.ts | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index b3c049d02..2cbe1dd40 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -162,6 +162,9 @@ declare class ImageField extends URLField { [Copy](): ObjectField; } declare class WebField extends URLField { [Copy](): ObjectField; } declare class PdfField extends URLField { [Copy](): ObjectField; } +declare const ComputedField: any; +declare const CompileScript: any; + // @ts-ignore declare type Extract = T extends U ? T : never; declare type Field = number | string | boolean | ObjectField | RefField; diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 05ed57ea5..ac46ccf90 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -44,14 +44,14 @@ export class ScriptField extends ObjectField { this._script = script; } - @serializable(object(optionsSchema)) + @serializable(custom(object(optionsSchema).serializer, () => SKIP)) get options() { - return this._script.options; + return this._script && this._script.options; } - @serializable(true) + @serializable(custom(primitive().serializer, () => SKIP)) get scriptString(): string { - return this._script.originalScript; + return this._script && this._script.originalScript; } // init(callback: (res: Field) => any) { @@ -84,7 +84,7 @@ export class ScriptField extends ObjectField { } } -@Deserializable("computed") +@Deserializable("computed", deserializeScript) export class ComputedField extends ScriptField { @computed get value() { -- cgit v1.2.3-70-g09d2 From e1b7feda380f540e677e69e306d91d6b57ce03e7 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 7 Jun 2019 19:42:43 -0400 Subject: tree view reordering. --- src/client/documents/Documents.ts | 2 +- src/client/util/DragManager.ts | 16 +++-- .../views/collections/CollectionDockingView.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 1 - src/client/views/collections/CollectionSubView.tsx | 24 ++----- .../views/collections/CollectionTreeView.scss | 79 ++++++++++++---------- src/new_fields/Doc.ts | 6 +- .../authentication/models/current_user_utils.ts | 1 + 8 files changed, 70 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ab61b915c..5f2d729cf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -30,7 +30,7 @@ import { Cast, NumCast } from "../../new_fields/Types"; import { IconField } from "../../new_fields/IconField"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; -import { StrokeData, InkField } from "../../new_fields/InkField"; +import { InkField } from "../../new_fields/InkField"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 1e84a0db0..32476b785 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -6,7 +6,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie import * as globalCssVariables from "../views/globalCssVariables.scss"; export type dropActionType = "alias" | "copy" | undefined; -export function SetupDrag(_reference: React.RefObject, docFunc: () => Doc | Promise, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) { +export function SetupDrag(_reference: React.RefObject, docFunc: () => Doc | Promise, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, options?: any, dontHideOnDrop?: boolean) { let onRowMove = async (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); @@ -16,6 +16,8 @@ export function SetupDrag(_reference: React.RefObject, docFunc: () var dragData = new DragManager.DocumentDragData([await docFunc()]); dragData.dropAction = dropAction; dragData.moveDocument = moveFunc; + dragData.options = options; + dragData.dontHideOnDrop = dontHideOnDrop; DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y); }; let onRowUp = (): void => { @@ -185,6 +187,7 @@ export namespace DragManager { export let AbortDrag: () => void = emptyFunction; function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { + eles = eles.filter(e => e); if (!dragDiv) { dragDiv = document.createElement("div"); dragDiv.className = "dragManager-dragDiv"; @@ -241,6 +244,13 @@ export namespace DragManager { // pdfBox.replaceChild(img, pdfBox.children[0]) // } // } + let set = dragElement.getElementsByTagName('*'); + for (let i = 0; i < set.length; i++) + if (set[i].hasAttribute("style")) { + let s = set[i]; + (s as any).style.pointerEvents = "none"; + } + dragDiv.appendChild(dragElement); return dragElement; @@ -259,8 +269,6 @@ export namespace DragManager { let lastX = downX; let lastY = downY; const moveHandler = (e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined; } @@ -309,7 +317,7 @@ export namespace DragManager { } function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { - let removed = dragEles.map(dragEle => { + let removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => { // let parent = dragEle.parentElement; // if (parent) parent.removeChild(dragEle); let ret = [dragEle, dragEle.style.width, dragEle.style.height]; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 8f6c9b1fc..51e29cb54 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -23,6 +23,7 @@ import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; import { ParentDocSelector } from './ParentDocumentSelector'; import React = require("react"); +import { MainView } from '../MainView'; @observer export class CollectionDockingView extends React.Component { @@ -457,7 +458,9 @@ export class DockedFrameRenderer extends React.Component { get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } addDocTab = (doc: Doc, location: string) => { - if (location === "onRight") { + if (doc.dockingConfig) { + MainView.Instance.openWorkspace(doc); + } else if (location === "onRight") { CollectionDockingView.Instance.AddRightSplit(doc); } else { CollectionDockingView.Instance.AddTab(this._stack, doc); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index b8eb4ac84..0a119a3b4 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -109,7 +109,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @computed get children() { - trace(); return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { let dref = React.createRef(); let dxf = () => this.getDocTransform(d, dref.current!); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 06d77fec5..762955a08 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -18,6 +18,7 @@ import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; import React = require("react"); import { FormattedTextBox } from "../nodes/FormattedTextBox"; +import { Id } from "../../../new_fields/FieldSymbols"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; @@ -81,28 +82,15 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @action protected drop(e: Event, de: DragManager.DropEvent): boolean { if (de.data instanceof DragManager.DocumentDragData) { - if (de.data.dropAction || de.data.userDropAction) { - ["width", "height", "curPage"].map(key => - de.data.draggedDocuments.map((draggedDocument: Doc, i: number) => - PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f)))); - } let added = false; if (de.data.dropAction || de.data.userDropAction) { - added = de.data.droppedDocuments.reduce((added: boolean, d) => { - let moved = this.props.addDocument(d); - return moved || added; - }, false); + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (de.data.moveDocument) { - const move = de.data.moveDocument; - added = de.data.droppedDocuments.reduce((added: boolean, d) => { - let moved = move(d, this.props.Document, this.props.addDocument); - return moved || added; - }, false); + let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; + added = movedDocs.reduce((added: boolean, d) => + de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false); } else { - added = de.data.droppedDocuments.reduce((added: boolean, d) => { - let moved = this.props.addDocument(d); - return moved || added; - }, false); + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } e.stopPropagation(); return added; diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 458030b28..2dc4b2e80 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -19,10 +19,6 @@ padding-left: 20px; } - li { - margin: 5px 0; - } - .no-indent { padding-left: 0; @@ -34,30 +30,9 @@ width: 15px; display: block; color: $intermediate-color; - margin-top: 3px; + margin-top: 8px; transform: scale(1.3, 1.3); } - - .docContainer { - margin-left: 10px; - display: block; - // width:100%;//width: max-content; - } - - .docContainer:hover { - .treeViewItem-openRight { - display: inline-block; - height:13px; - // display: inline; - svg { - display:block; - padding:0px; - margin: 0px; - } - } - } - - .editableView-container { font-weight: bold; } @@ -70,10 +45,6 @@ display: inline; } - .treeViewItem-openRight { - margin-left: 5px; - display: none; - } .docContainer:hover { .delete-button { @@ -88,13 +59,51 @@ font-size: 24px; } - .collection-child { - margin-top: 10px; - margin-bottom: 10px; - } - .collectionTreeView-keyHeader { font-style: italic; font-size: 8pt; } +} + +.docContainer { + margin-left: 10px; + display: block; + // width:100%;//width: max-content; + + .treeViewItem-openRight { + margin-left: 5px; + display: none; + } +} +#docContainer-data { + margin-top: 5px; +} + +.docContainer:hover { + .treeViewItem-openRight { + display: inline-block; + height:13px; + // display: inline; + svg { + display:block; + padding:0px; + margin: 0px; + } + } +} + +.treeViewItem-header { + border: transparent 1px solid; +} +.treeViewItem-header-above { + border: transparent 1px solid; + border-top: black 1px solid; +} +.treeViewItem-header-below { + border: transparent 1px solid; + border-bottom: black 1px solid; +} +.treeViewItem-header-inside { + border: transparent 1px solid; + border: black 1px solid; } \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 7f7263cf1..7e02a5bc5 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -211,13 +211,11 @@ export namespace Doc { return Array.from(results); } - export function MakeAlias(doc: Doc) { - const alias = new Doc; if (!GetT(doc, "isPrototype", "boolean", true)) { - alias.proto = doc.proto; + return Doc.MakeCopy(doc); } - return alias; + return new Doc; } export function MakeCopy(doc: Doc, copyProto: boolean = false): Doc { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index e5b7a025b..816c5f269 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -29,6 +29,7 @@ export class CurrentUserUtils { private static createUserDocument(id: string): Doc { let doc = new Doc(id, true); doc.viewType = CollectionViewType.Tree; + doc.dropAction = "alias"; doc.layout = CollectionView.LayoutString(); doc.title = this.email; doc.data = new List(); -- cgit v1.2.3-70-g09d2 From ecd6d50cf6dd89f7d860096037efee81230eb535 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 7 Jun 2019 19:42:52 -0400 Subject: from last --- .../views/collections/CollectionTreeView.tsx | 188 +++++++++++++++------ 1 file changed, 137 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 9fb1da716..8dc10bcd1 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; import { Doc, DocListCast } from '../../../new_fields/Doc'; import { Id } from '../../../new_fields/FieldSymbols'; import { Document, listSpec } from '../../../new_fields/Schema'; -import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types'; +import { BoolCast, Cast, NumCast, StrCast, PromiseValue } from '../../../new_fields/Types'; import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; @@ -19,6 +19,7 @@ import { CollectionDockingView } from './CollectionDockingView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); +import { Transform } from '../../util/Transform'; export interface TreeViewProps { @@ -27,6 +28,10 @@ export interface TreeViewProps { moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; addDocTab: (doc: Doc, where: string) => void; + addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean; + ScreenToLocalTransform: () => Transform; + treeViewId: string; + parentKey: string; } export enum BulletType { @@ -45,23 +50,49 @@ library.add(faCaretRight); * Component that takes in a document prop and a boolean whether it's collapsed or not. */ class TreeView extends React.Component { + private _header?: React.RefObject = React.createRef(); + private treedropDisposer?: DragManager.DragDropDisposer; + protected createTreeDropTarget = (ele: HTMLDivElement) => { + this.treedropDisposer && this.treedropDisposer(); + if (ele) { + this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }); + } + } + @observable _isOver: boolean = false; @observable _collapsed: boolean = true; @undoBatch delete = () => this.props.deleteDoc(this.props.document); + @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "openRight"); + + @action onMouseEnter = () => { this._isOver = true; } + @action onMouseLeave = () => { this._isOver = false; } - @undoBatch openRight = async () => { - if (this.props.document.dockingConfig) { - MainView.Instance.openWorkspace(this.props.document); - } else { - this.props.addDocTab(this.props.document, "openRight"); + onPointerEnter = (e: React.PointerEvent): void => { + this.props.document.libraryBrush = true; + if (e.buttons === 1) { + this._header!.current!.className = "treeViewItem-header"; + document.addEventListener("pointermove", this.onDragMove, true); } } - - get children() { - return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed? .filter(doc => FieldValue(doc)); + onPointerLeave = (e: React.PointerEvent): void => { + this.props.document.libraryBrush = false; + this._header!.current!.className = "treeViewItem-header"; + document.removeEventListener("pointermove", this.onDragMove, true); + } + onDragMove = (e: PointerEvent): void => { + this.props.document.libraryBrush = false; + let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); + let rect = this._header!.current!.getBoundingClientRect(); + let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); + let before = x[1] < bounds[1]; + let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible); + this._header!.current!.className = "treeViewItem-header" + if (inside && this._bulletType != BulletType.List) this._header!.current!.className = "treeViewItem-header-inside"; + else if (before) this._header!.current!.className = "treeViewItem-header-above"; + else if (!before) this._header!.current!.className = "treeViewItem-header-below"; + e.stopPropagation(); } - onPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); } @@ -69,19 +100,17 @@ class TreeView extends React.Component { @action remove = (document: Document, key: string) => { let children = Cast(this.props.document[key], listSpec(Doc), []); - if (children) { - children.splice(children.indexOf(document), 1); - } + children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1); } @action - move: DragManager.MoveFunction = (document, target, addDoc) => { - if (this.props.document === target) { - return true; + move: DragManager.MoveFunction = (document: Doc, target: Doc, addDoc) => { + if (this.props.document !== target) { + //TODO This should check if it was removed + this.props.deleteDoc(document); + return addDoc(document); } - //TODO This should check if it was removed - this.remove(document, "data"); - return addDoc(document); + return true; } renderBullet(type: BulletType) { @@ -93,22 +122,12 @@ class TreeView extends React.Component { } return
{bullet ? : ""}
; } - - @action - onMouseEnter = () => { - this._isOver = true; - } - @observable _isOver: boolean = false; - @action - onMouseLeave = () => { - this._isOver = false; - } /** * Renders the EditableView title element for placement into the tree. */ renderTitle() { let reference = React.createRef(); - let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction); + let onItemDown = SetupDrag(reference, () => this.props.document, this.move, this.props.dropAction, this.props.treeViewId, true); let editableView = (titleString: string) => ( { {/* */}
); return ( -
+ > {editableView(StrCast(this.props.document.title))} {openRight} {/* {
} */} @@ -152,14 +171,51 @@ class TreeView extends React.Component { } else { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); } - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + ContextMenu.Instance.displayMenu(e.pageX - 156, e.pageY - 15); + e.stopPropagation(); + } + } + treeDrop = (e: Event, de: DragManager.DropEvent) => { + let x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y); + let rect = this._header!.current!.getBoundingClientRect(); + let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); + let before = x[1] < bounds[1]; + let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible); + if (de.data instanceof DragManager.DocumentDragData) { + let addDoc = (doc: Doc) => this.props.addDocument(doc, this.props.document, before); + if (inside) { + let docList = Cast(this.props.document.data, listSpec(Doc)); + if (docList !== undefined) { + addDoc = (doc: Doc) => { docList && docList.push(doc); return true; } + } + } + let added = false; + if (de.data.dropAction || de.data.userDropAction) { + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before) || added, false); + } else if (de.data.moveDocument) { + let movedDocs = de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments; + added = movedDocs.reduce((added: boolean, d) => + de.data.moveDocument(d, this.props.document, addDoc) || added, false); + } else { + added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before), false); + } e.stopPropagation(); + return added; } + return false; } - onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; }; - onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; }; + public static AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean) { + let list = Cast(target[key], listSpec(Doc)); + if (list) { + let ind = relativeTo ? list.indexOf(relativeTo) : -1; + if (ind === -1) list.push(doc); + else list.splice(before ? ind : ind + 1, 0, doc); + } + return true; + } + _bulletType: BulletType = BulletType.List; render() { let bulletType = BulletType.List; let contentElement: (JSX.Element | null)[] = []; @@ -170,16 +226,17 @@ class TreeView extends React.Component { } keys.map(key => { let docList = DocListCast(this.props.document[key]); + let remDoc = (doc: Doc) => this.remove(doc, key); + let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before); let doc = Cast(this.props.document[key], Doc); if (doc instanceof Doc || docList.length) { if (!this._collapsed) { bulletType = BulletType.Collapsible; - let spacing = (key === "data") ? 0 : -10; contentElement.push(
    {(key === "data") ? (null) : {key}} -
    - {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction, this.props.addDocTab)} +
    + {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, this.props.treeViewId, key, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform)}
); } else { @@ -187,29 +244,52 @@ class TreeView extends React.Component { } } }); + this._bulletType = bulletType; return
  • - {this.renderBullet(bulletType)} - {this.renderTitle()} +
    + {this.renderBullet(bulletType)} + {this.renderTitle()} +
    {contentElement}
  • ; } - public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void) { - return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child => - ); + public static GetChildElements( + docs: Doc[], + treeViewId: string, + key: string, + add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean, + remove: ((doc: Doc) => void), + move: DragManager.MoveFunction, + dropAction: dropActionType, + addDocTab: (doc: Doc, where: string) => void, + screenToLocalXf: () => Transform + ) { + return docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized)).map(child => + ); } } @observer export class CollectionTreeView extends CollectionSubView(Document) { + private treedropDisposer?: DragManager.DragDropDisposer; + protected createTreeDropTarget = (ele: HTMLDivElement) => { + if (this.treedropDisposer) { + this.treedropDisposer(); + } + if (ele) { + this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + @action remove = (document: Document) => { - let children = Cast(this.props.Document.data, listSpec(Doc), []); - if (children) { - children.splice(children.indexOf(document), 1); - } + let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); + children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1); } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout @@ -218,20 +298,26 @@ export class CollectionTreeView extends CollectionSubView(Document) { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) }); } } + + onTreeDrop = (e: React.DragEvent) => { + this.onDrop(e, {}); + } render() { - trace(); - let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType; + let dropAction = Cast(this.props.Document.dropAction, "string") as dropActionType; if (!this.childDocs) { return (null); } - let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction, this.props.addDocTab); + let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); + let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); + let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform); return (
    this.props.isSelected() && e.stopPropagation()} - onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> + onDrop={this.onTreeDrop} + ref={this.createTreeDropTarget}>
    Date: Fri, 7 Jun 2019 21:05:20 -0400 Subject: fixed initial image sizing --- src/client/documents/Documents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5f2d729cf..f0fb0c3ed 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -227,7 +227,7 @@ export namespace Docs { inst.proto!.nativeWidth = size.width; } inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect; - inst.proto!.height = NumCast(inst.proto!.width) * aspect; + inst.height = NumCast(inst.width) * aspect; }) .catch((err: any) => console.log(err)); return inst; -- cgit v1.2.3-70-g09d2 From 39be491bf3c750902250a0afe0321a05b7643026 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 8 Jun 2019 09:18:46 -0400 Subject: fixed deletion of keys to actually remove them on the server. added '=' syntax on kvp to set on proto. := sets a computed script. =:= sets computed script on proto --- src/client/views/nodes/KeyValueBox.tsx | 49 ++++++++++++++++++++------------- src/client/views/nodes/KeyValuePair.tsx | 20 ++++---------- src/new_fields/util.ts | 3 +- 3 files changed, 37 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 849f17aa4..917be734d 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -2,13 +2,14 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { CompileScript } from "../../util/Scripting"; +import { CompileScript, ScriptOptions } from "../../util/Scripting"; import { FieldView, FieldViewProps } from './FieldView'; import "./KeyValueBox.scss"; import { KeyValuePair } from "./KeyValuePair"; import React = require("react"); import { NumCast, Cast, FieldValue } from "../../../new_fields/Types"; import { Doc, Field } from "../../../new_fields/Doc"; +import { ComputedField } from "../../../fields/ScriptField"; @observer export class KeyValueBox extends React.Component { @@ -27,28 +28,38 @@ export class KeyValueBox extends React.Component { @action onEnterKey = (e: React.KeyboardEvent): void => { if (e.key === 'Enter') { - if (this._keyInput && this._valueInput) { - let doc = this.fieldDocToLayout; - if (!doc) { - return; + if (this._keyInput && this._valueInput && this.fieldDocToLayout) { + if (KeyValueBox.SetField(this.fieldDocToLayout, this._keyInput, this._valueInput)) { + this._keyInput = ""; + this._valueInput = ""; } - let realDoc = doc; - - let script = CompileScript(this._valueInput, { addReturn: true }); - if (!script.compiled) { - return; - } - let res = script.run(); - if (!res.success) return; - const field = res.result; - if (Field.IsField(field)) { - realDoc[this._keyInput] = field; - } - this._keyInput = ""; - this._valueInput = ""; } } } + public static SetField(doc: Doc, key: string, value: string) { + let eq = value.startsWith("="); + value = eq ? value.substr(1) : value; + let dubEq = value.startsWith(":="); + value = dubEq ? value.substr(2) : value; + let options: ScriptOptions = { addReturn: true }; + if (dubEq) options.typecheck = false; + let script = CompileScript(value, options); + if (!script.compiled) { + return false; + } + let field = new ComputedField(script); + if (!dubEq) { + let res = script.run(); + if (!res.success) return false; + field = res.result; + } + if (Field.IsField(field, true)) { + let target = !eq ? doc : Doc.GetProto(doc); + target[key] = field; + return true; + } + return false; + } onPointerDown = (e: React.PointerEvent): void => { if (e.buttons === 1 && this.props.isSelected()) { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 228d07018..e8bc17532 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -2,7 +2,7 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import { emptyFunction, returnFalse, returnZero, returnTrue } from '../../../Utils'; -import { CompileScript } from "../../util/Scripting"; +import { CompileScript, CompiledScript, ScriptOptions } from "../../util/Scripting"; import { Transform } from '../../util/Transform'; import { EditableView } from "../EditableView"; import { FieldView, FieldViewProps } from './FieldView'; @@ -11,6 +11,8 @@ import "./KeyValuePair.scss"; import React = require("react"); import { Doc, Opt, Field } from '../../../new_fields/Doc'; import { FieldValue } from '../../../new_fields/Types'; +import { ComputedField } from '../../../fields/ScriptField'; +import { KeyValueBox } from './KeyValueBox'; // Represents one row in a key value plane @@ -66,20 +68,8 @@ export class KeyValuePair extends React.Component { } return ""; }} - SetValue={(value: string) => { - let script = CompileScript(value, { addReturn: true }); - if (!script.compiled) { - return false; - } - let res = script.run(); - if (!res.success) return false; - const field = res.result; - if (Field.IsField(field, true)) { - props.Document[props.fieldKey] = field; - return true; - } - return false; - }}> + SetValue={(value: string) => + KeyValueBox.SetField(props.Document, props.fieldKey, value)}> ); diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index ad07d05a3..fe0da2e7a 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -44,7 +44,8 @@ export const setter = action(function (target: any, prop: string | symbol | numb } else { target.__fields[prop] = value; } - target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); + if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } }); + else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } }); UndoManager.AddEvent({ redo: () => receiver[prop] = value, undo: () => receiver[prop] = curValue -- cgit v1.2.3-70-g09d2 From 2500204978e2dd15c439adfc400388b7bb3caa01 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 8 Jun 2019 20:48:10 -0400 Subject: several fixes to templates and a fix for link dragging. --- src/client/util/DragManager.ts | 1 + src/client/views/MainOverlayTextBox.tsx | 2 +- src/client/views/Templates.tsx | 48 +++++++++++++++++------------ src/client/views/nodes/DocumentView.tsx | 16 ++++++++-- src/client/views/nodes/FormattedTextBox.tsx | 15 ++++++--- 5 files changed, 55 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 32476b785..4c9f798a8 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -269,6 +269,7 @@ export namespace DragManager { let lastX = downX; let lastY = downY; const moveHandler = (e: PointerEvent) => { + e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined; } diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index d1224febe..6bbd70a25 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -96,7 +96,7 @@ export class MainOverlayTextBox extends React.Component return
    -
    diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 0cd367bcb..8943bbcaf 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -47,42 +47,52 @@ export namespace Templates {
    ` ); - export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop, - `
    + export const ImageTitle = new Template("Image Title", TemplatePosition.InnerTop, + `
    {layout}
    {props.Document.title}
    ` ); - export const Title = new Template("Title", TemplatePosition.InnerTop, - `
    -
    {layout}
    -
    + export const TextTitle = new Template("Text Title", TemplatePosition.InnerTop, + `
    +
    {props.Document.title}
    +
    +
    {layout}
    +
    ` ); - export const Bullet = new Template("Bullet", TemplatePosition.InnerTop, - `
    -
    {layout}
    -
    - + export const Header = new Template("Header", TemplatePosition.InnerTop, + `< div style = "display:flex; flex-direction:column; height:100%;" > +
    +
    -
    ` +
    {layout}
    +
    ` ); + + export const Bullet = new Template("Bullet", TemplatePosition.InnerTop, + `< div > +
    {layout}
    +
    + +
    +
    ` ); export function ImageOverlay(width: number, height: number, field: string = "thumbnail") { - return (`
    -
    {layout}
    -
    - + return (`< div > +
    {layout}
    +
    +
    -
    `); +
    `); } - export const TemplateList: Template[] = [Title, TitleOverlay, Caption, Bullet]; + export const TemplateList: Template[] = [TextTitle, Header, ImageTitle, Caption, Bullet]; export function sortTemplates(a: Template, b: Template) { if (a.Position < b.Position) { return -1; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ff2b9842f..85b8a5596 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash, faConciergeBell, faFolder, faMapPin, faLink, faFingerprint, faCrosshairs, faDesktop } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; +import { action, computed, IReactionDisposer, reaction, trace, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; @@ -123,6 +123,12 @@ export class DocumentView extends DocComponent(Docu set templates(templates: List) { this.props.Document.templates = templates; } screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); + constructor(props: DocumentViewProps) { + super(props); + this.selectOnLoad = props.selectOnLoad; + } + + _reactionDisposer?: IReactionDisposer; @action componentDidMount() { @@ -429,11 +435,15 @@ export class DocumentView extends DocComponent(Docu onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; isSelected = () => SelectionManager.IsSelected(this); - select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); + @action select = (ctrlPressed: boolean) => { this.selectOnLoad = false; SelectionManager.SelectDoc(this, ctrlPressed); } + @observable selectOnLoad: boolean = false; @computed get nativeWidth() { return this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.Document.nativeHeight || 0; } - @computed get contents() { return (); } + @computed get contents() { + return ( + ); + } render() { var scaling = this.props.ContentScaling(); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 478e7ce93..559a9fbfc 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -52,6 +52,8 @@ library.add(faSmile); export interface FormattedTextBoxProps { isOverlay?: boolean; hideOnLeave?: boolean; + height?: string; + color?: string; } const richTextSchema = createSchema({ @@ -206,6 +208,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private setupEditor(config: any, doc?: Doc) { let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined; + let startup = StrCast(this.props.Document.documentText); + startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : ""; + if (!startup && !field && doc) { + startup = StrCast(doc[this.props.fieldKey]); + } if (this._proseRef.current) { this._editorView = new EditorView(this._proseRef.current, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), @@ -214,10 +221,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe image(node, view, getPos) { return new ImageResizeView(node, view, getPos); } } }); - let text = StrCast(this.props.Document.documentText); - if (text.startsWith("@@@")) { + if (startup) { this.props.Document.proto!.documentText = undefined; - this._editorView.dispatch(this._editorView.state.tr.insertText(text.replace("@@@", ""))); + this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); } } @@ -380,9 +386,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return (
    Date: Sun, 9 Jun 2019 20:32:06 -0400 Subject: added a link context for link targets. --- src/client/documents/Documents.ts | 30 ++++++++++------------ src/client/util/DocumentManager.ts | 29 ++++++++++++++++----- src/client/views/DocumentDecorations.tsx | 3 +-- .../views/collections/CollectionBaseView.tsx | 6 ++--- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 15 ++++++++--- 6 files changed, 52 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f0fb0c3ed..2ae127e21 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -69,31 +69,27 @@ export interface DocumentOptions { const delegateKeys = ["x", "y", "width", "height", "panX", "panY"]; export namespace DocUtils { - export function MakeLink(source: Doc, target: Doc) { + export function MakeLink(source: Doc, target: Doc, targetContext?: Doc) { let protoSrc = source.proto ? source.proto : source; let protoTarg = target.proto ? target.proto : target; UndoManager.RunInBatch(() => { let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 }); - //let linkDoc = new Doc; - linkDoc.proto!.title = "-link name-"; - linkDoc.proto!.linkDescription = ""; - linkDoc.proto!.linkTags = "Default"; + let linkDocProto = Doc.GetProto(linkDoc); + linkDocProto.title = source.title + " to " + target.title; + linkDocProto.linkDescription = ""; + linkDocProto.linkTags = "Default"; - linkDoc.proto!.linkedTo = target; - linkDoc.proto!.linkedToPage = target.curPage; - linkDoc.proto!.linkedFrom = source; - linkDoc.proto!.linkedFromPage = source.curPage; + linkDocProto.linkedTo = target; + linkDocProto.linkedFrom = source; + linkDocProto.linkedToPage = target.curPage; + linkDocProto.linkedFromPage = source.curPage; + linkDocProto.linkedToContext = targetContext; let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc)); - if (!linkedFrom) { - protoTarg.linkedFromDocs = linkedFrom = new List(); - } - linkedFrom.push(linkDoc); - let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc)); - if (!linkedTo) { - protoSrc.linkedToDocs = linkedTo = new List(); - } + !linkedFrom && (protoTarg.linkedFromDocs = linkedFrom = new List()); + !linkedTo && (protoSrc.linkedToDocs = linkedTo = new List()); + linkedFrom.push(linkDoc); linkedTo.push(linkDoc); return linkDoc; }, "make link"); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 65c4b9e4b..ff0c1560b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -115,7 +115,7 @@ export class DocumentManager { } @undoBatch - public jumpToDocument = async (docDelegate: Doc, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number): Promise => { + public jumpToDocument = async (docDelegate: Doc, forceDockFunc: boolean = false, dockFunc?: (doc: Doc) => void, linkPage?: number, docContext?: Doc): Promise => { let doc = Doc.GetProto(docDelegate); const contextDoc = await Cast(doc.annotationOn, Doc); if (contextDoc) { @@ -123,6 +123,7 @@ export class DocumentManager { const curPage = NumCast(contextDoc.curPage, page); if (page !== curPage) contextDoc.curPage = page; } + let docView: DocumentView | null; // using forceDockFunc as a flag for splitting linked to doc to the right...can change later if needed if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) { @@ -131,18 +132,34 @@ export class DocumentManager { docView.props.focus(docView.props.Document); } else { if (!contextDoc) { - const actualDoc = Doc.MakeAlias(docDelegate); - actualDoc.libraryBrush = true; - if (linkPage !== undefined) actualDoc.curPage = linkPage; - (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc); + if (docContext) { + let targetContextView: DocumentView | null; + if (!forceDockFunc && docContext && (targetContextView = DocumentManager.Instance.getDocumentView(docContext))) { + docContext.panTransformType = "Ease"; + targetContextView.props.focus(docDelegate); + } else { + (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext); + setTimeout(() => { + this.jumpToDocument(docDelegate, forceDockFunc, dockFunc, linkPage); + }, 10); + } + } else { + const actualDoc = Doc.MakeAlias(docDelegate); + actualDoc.libraryBrush = true; + if (linkPage !== undefined) actualDoc.curPage = linkPage; + (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc); + } } else { let contextView: DocumentView | null; docDelegate.libraryBrush = true; if (!forceDockFunc && (contextView = DocumentManager.Instance.getDocumentView(contextDoc))) { contextDoc.panTransformType = "Ease"; - contextView.props.focus(contextDoc); + contextView.props.focus(docDelegate); } else { (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc); + setTimeout(() => { + this.jumpToDocument(docDelegate, forceDockFunc, dockFunc, linkPage); + }, 10); } } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index f22fecb98..7260b00cf 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -432,7 +432,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (rect.width !== 0 && (dX != 0 || dY != 0 || dW != 0 || dH != 0)) { let doc = PositionDocument(element.props.Document); - let docHeightBefore = doc.height; let nwidth = doc.nativeWidth || 0; let nheight = doc.nativeHeight || 0; let zoomBasis = NumCast(doc.zoomBasis, 1); @@ -469,7 +468,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } else { doc.width = zoomBasis * actualdW; - if (docHeightBefore === doc.height) doc.height = zoomBasis * actualdH; + doc.height = zoomBasis * actualdH; } } }); diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index ddec587a9..d120c3a0c 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -101,9 +101,9 @@ export class CollectionBaseView extends React.Component { addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { let props = this.props; var curPage = NumCast(props.Document.curPage, -1); - Doc.SetOnPrototype(doc, "page", curPage); + Doc.GetProto(doc).page = curPage; if (curPage >= 0) { - Doc.SetOnPrototype(doc, "annotationOn", props.Document); + Doc.GetProto(doc).annotationOn = props.Document; } if (!this.createsCycle(doc, props.Document)) { //TODO This won't create the field if it doesn't already exist @@ -141,7 +141,7 @@ export class CollectionBaseView extends React.Component { break; } } - PromiseValue(Cast(doc.annotationOn, Doc)).then((annotationOn) => { + PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => { if (annotationOn === props.Document) { doc.annotationOn = undefined; } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 0a119a3b4..cad7cd50c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -153,7 +153,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "Toggle multi-column", - event: () => this.props.Document.singleColumn = !BoolCast(this.props.Document.singleColumn, false), icon: "file-pdf" + event: () => this.props.Document.singleColumn = !BoolCast(this.props.Document.singleColumn, true), icon: "file-pdf" }); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 85b8a5596..2ac6d12bf 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -6,7 +6,7 @@ import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from ".. import { List } from "../../../new_fields/List"; import { ObjectField } from "../../../new_fields/ObjectField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, Cast, FieldValue, StrCast, NumCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { emptyFunction, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; @@ -261,12 +261,17 @@ export class DocumentView extends DocComponent(Docu linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0], linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]]; + let linkedFwdContextDocs = [ + linkedToDocs.length ? await (linkedToDocs[0].linkedToContext) as Doc : linkedFromDocs.length ? await PromiseValue(linkedFromDocs[0].linkedFromContext) as Doc : undefined, + linkedFromDocs.length ? await (linkedFromDocs[0].linkedFromContext) as Doc : linkedToDocs.length ? await PromiseValue(linkedToDocs[0].linkedToContext) as Doc : undefined]; + let linkedFwdPage = [ linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : undefined, linkedFromDocs.length ? NumCast(linkedFromDocs[0].linkedFromPage, undefined) : linkedToDocs.length ? NumCast(linkedToDocs[0].linkedToPage, undefined) : undefined]; + if (!linkedFwdDocs.some(l => l instanceof Promise)) { let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab"); - DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0]); + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], linkedFwdContextDocs[altKey ? 1 : 0]); } } } @@ -333,6 +338,7 @@ export class DocumentView extends DocComponent(Docu let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; + e.stopPropagation(); if (de.mods === "AltKey") { const protoDest = destDoc.proto; const protoSrc = sourceDoc.proto; @@ -343,10 +349,11 @@ export class DocumentView extends DocComponent(Docu dst.nativeHeight = src.nativeHeight; } else { - DocUtils.MakeLink(sourceDoc, destDoc); + const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); + const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); + DocUtils.MakeLink(sourceDoc, destDoc, views.length ? views[0].props.Document : undefined); de.data.droppedDocuments.push(destDoc); } - e.stopPropagation(); } } -- cgit v1.2.3-70-g09d2 From c56851026c4054cde26ea36a9a24db14fac6c49c Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 10 Jun 2019 13:03:47 -0400 Subject: fixes for treeview highlighting, templates, field deletion, overlaytext box positioning --- src/Utils.ts | 2 +- src/client/util/DragManager.ts | 6 ++++-- src/client/util/SelectionManager.ts | 9 +++++--- src/client/views/MainOverlayTextBox.tsx | 8 +++++--- src/client/views/Templates.tsx | 16 ++++----------- .../views/collections/CollectionTreeView.tsx | 24 ++++++++++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +-- src/client/views/nodes/DocumentView.tsx | 6 +++--- src/new_fields/util.ts | 4 ++-- 9 files changed, 42 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 611c61135..e8a80bdc3 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -15,7 +15,7 @@ export class Utils { return v5(seed, v5.URL); } - public static GetScreenTransform(ele: HTMLElement): { scale: number, translateX: number, translateY: number } { + public static GetScreenTransform(ele?: HTMLElement): { scale: number, translateX: number, translateY: number } { if (!ele) { return { scale: 1, translateX: 1, translateY: 1 }; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 4c9f798a8..8ee1ad2af 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,9 +1,10 @@ -import { action, runInAction } from "mobx"; +import { action, runInAction, observable } from "mobx"; import { Doc, DocListCastAsync } from "../../new_fields/Doc"; import { Cast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import * as globalCssVariables from "../views/globalCssVariables.scss"; +import { SelectionManager } from "./SelectionManager"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag(_reference: React.RefObject, docFunc: () => Doc | Promise, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, options?: any, dontHideOnDrop?: boolean) { @@ -194,7 +195,7 @@ export namespace DragManager { dragDiv.style.pointerEvents = "none"; DragManager.Root().appendChild(dragDiv); } - + SelectionManager.SetIsDragging(true); let scaleXs: number[] = []; let scaleYs: number[] = []; let xs: number[] = []; @@ -293,6 +294,7 @@ export namespace DragManager { }; let hideDragElements = () => { + SelectionManager.SetIsDragging(false); dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); eles.map(ele => (ele.hidden = false)); }; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 0e22d576c..ddad4b818 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,4 +1,4 @@ -import { observable, action } from "mobx"; +import { observable, action, runInAction } from "mobx"; import { Doc } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; @@ -6,8 +6,8 @@ import { NumCast } from "../../new_fields/Types"; export namespace SelectionManager { class Manager { - @observable - SelectedDocuments: Array = []; + @observable IsDragging: boolean = false; + @observable SelectedDocuments: Array = []; @action SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { @@ -51,6 +51,9 @@ export namespace SelectionManager { if (found) manager.SelectDoc(found, false); } + export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } + export function GetIsDragging() { return manager.IsDragging; } + export function SelectedDocuments(): Array { return manager.SelectedDocuments; } diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 6bbd70a25..0eb3e9707 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -1,7 +1,7 @@ import { action, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnTrue, returnZero } from '../../Utils'; +import { emptyFunction, returnTrue, returnZero, Utils } from '../../Utils'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import "normalize.css"; @@ -29,7 +29,8 @@ export class MainOverlayTextBox extends React.Component MainOverlayTextBox.Instance = this; reaction(() => FormattedTextBox.InputBoxOverlay, (box?: FormattedTextBox) => { - if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, box.props.ScreenToLocalTransform); + let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined); + if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, () => new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale)); else this.setTextDoc(); }); } @@ -93,9 +94,10 @@ export class MainOverlayTextBox extends React.Component if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { let textRect = this._textTargetDiv.getBoundingClientRect(); let s = this._textXf().Scale; + let auto = FormattedTextBox.InputBoxOverlay.props.height; return
    + style={{ width: `${textRect.width * s}px`, height: auto ? "auto" : `${textRect.height * s}px` }}> diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 8943bbcaf..3cd525afa 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -41,21 +41,13 @@ export namespace Templates { export const Caption = new Template("Caption", TemplatePosition.OutterBottom, `
    -
    {layout}
    +
    {layout}
    - +
    ` ); - export const ImageTitle = new Template("Image Title", TemplatePosition.InnerTop, - `
    -
    {layout}
    -
    - {props.Document.title} -
    -
    ` ); - - export const TextTitle = new Template("Text Title", TemplatePosition.InnerTop, + export const Title = new Template("Title", TemplatePosition.InnerTop, `
    {props.Document.title} @@ -92,7 +84,7 @@ export namespace Templates {
    `); } - export const TemplateList: Template[] = [TextTitle, Header, ImageTitle, Caption, Bullet]; + export const TemplateList: Template[] = [Title, Header, Caption, Bullet]; export function sortTemplates(a: Template, b: Template) { if (a.Position < b.Position) { return -1; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 8dc10bcd1..8a6764c58 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -20,6 +20,7 @@ import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); import { Transform } from '../../util/Transform'; +import { SelectionManager } from '../../util/SelectionManager'; export interface TreeViewProps { @@ -32,6 +33,7 @@ export interface TreeViewProps { ScreenToLocalTransform: () => Transform; treeViewId: string; parentKey: string; + active: () => boolean; } export enum BulletType { @@ -69,7 +71,7 @@ class TreeView extends React.Component { @action onMouseLeave = () => { this._isOver = false; } onPointerEnter = (e: React.PointerEvent): void => { - this.props.document.libraryBrush = true; + this.props.active() && (this.props.document.libraryBrush = true); if (e.buttons === 1) { this._header!.current!.className = "treeViewItem-header"; document.addEventListener("pointermove", this.onDragMove, true); @@ -149,7 +151,10 @@ class TreeView extends React.Component {
    ); return (
    {editableView(StrCast(this.props.document.title))} {openRight} @@ -225,18 +230,19 @@ class TreeView extends React.Component { while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1); } keys.map(key => { - let docList = DocListCast(this.props.document[key]); + let docList = Cast(this.props.document[key], listSpec(Doc)); let remDoc = (doc: Doc) => this.remove(doc, key); let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before); let doc = Cast(this.props.document[key], Doc); - if (doc instanceof Doc || docList.length) { + if (doc instanceof Doc || docList) { if (!this._collapsed) { bulletType = BulletType.Collapsible; contentElement.push(
      {(key === "data") ? (null) : {key}}
      - {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, this.props.treeViewId, key, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform)} + {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, key, addDoc, remDoc, this.move, + this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active)}
    ); } else { @@ -266,11 +272,12 @@ class TreeView extends React.Component { move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void, - screenToLocalXf: () => Transform + screenToLocalXf: () => Transform, + active: () => boolean ) { return docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized)).map(child => ); + dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} active={active} />); } } @@ -309,7 +316,8 @@ export class CollectionTreeView extends CollectionSubView(Document) { } let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); - let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform); + let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove, + moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active); return (
    (Docu dst.nativeHeight = src.nativeHeight; } else { - const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); - const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); - DocUtils.MakeLink(sourceDoc, destDoc, views.length ? views[0].props.Document : undefined); + // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); + // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); + DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined); de.data.droppedDocuments.push(destDoc); } } diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index fe0da2e7a..8cb1db953 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -6,7 +6,7 @@ import { FieldValue } from "./Types"; import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; import { action } from "mobx"; -import { Parent, OnUpdate, Update, Id } from "./FieldSymbols"; +import { Parent, OnUpdate, Update, Id, SelfProxy } from "./FieldSymbols"; import { ComputedField } from "../fields/ScriptField"; export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { @@ -86,7 +86,7 @@ export function deleteProperty(target: any, prop: string | number | symbol) { delete target[prop]; return true; } - target[prop] = undefined; + target[SelfProxy][prop] = undefined; return true; } -- cgit v1.2.3-70-g09d2 From df9aef6a2026a05c02c9b6b4b1e0a7b10b4ba244 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 10 Jun 2019 13:48:04 -0400 Subject: fixed text overlay from lingering --- src/client/views/MainOverlayTextBox.tsx | 20 ++++++++++++++------ src/client/views/nodes/FormattedTextBox.tsx | 18 +++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 0eb3e9707..a0359419b 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -17,11 +17,12 @@ interface MainOverlayTextBoxProps { export class MainOverlayTextBox extends React.Component { public static Instance: MainOverlayTextBox; @observable _textXf: () => Transform = () => Transform.Identity(); - private _textFieldKey: string = "data"; + public TextFieldKey: string = "data"; private _textColor: string | null = null; private _textHideOnLeave?: boolean; private _textTargetDiv: HTMLDivElement | undefined; private _textProxyDiv: React.RefObject; + public TextDoc?: Doc; constructor(props: MainOverlayTextBoxProps) { super(props); @@ -29,9 +30,16 @@ export class MainOverlayTextBox extends React.Component MainOverlayTextBox.Instance = this; reaction(() => FormattedTextBox.InputBoxOverlay, (box?: FormattedTextBox) => { - let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined); - if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, () => new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale)); - else this.setTextDoc(); + if (box) { + this.TextDoc = box.props.Document; + let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined); + let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); }; + this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf) + } + else { + this.TextDoc = undefined; + this.setTextDoc(); + } }); } @@ -40,7 +48,7 @@ export class MainOverlayTextBox extends React.Component if (this._textTargetDiv) { this._textTargetDiv.style.color = this._textColor; } - this._textFieldKey = textFieldKey!; + this.TextFieldKey = textFieldKey!; this._textXf = tx ? tx : () => Transform.Identity(); this._textTargetDiv = div; this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; @@ -98,7 +106,7 @@ export class MainOverlayTextBox extends React.Component return
    -
    diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 559a9fbfc..4325fd48c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -28,6 +28,7 @@ import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); import { Id } from '../../../new_fields/FieldSymbols'; +import { MainOverlayTextBox } from '../MainOverlayTextBox'; library.add(faEdit); library.add(faSmile); @@ -181,7 +182,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._editorView) { this._editorView.destroy(); } - this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox + this.setupEditor(config, // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox + MainOverlayTextBox.Instance.TextDoc ? MainOverlayTextBox.Instance.TextDoc : this.props.Document, + MainOverlayTextBox.Instance.TextFieldKey ? MainOverlayTextBox.Instance.TextFieldKey : this.props.fieldKey); } ); } else { @@ -203,15 +206,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe field => field && this._editorView && !this._applyingChange && this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); - this.setupEditor(config, this.props.Document); + this.setupEditor(config, this.props.Document, this.props.fieldKey); } - private setupEditor(config: any, doc?: Doc) { - let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined; - let startup = StrCast(this.props.Document.documentText); + private setupEditor(config: any, doc: Doc, fieldKey: string) { + let field = doc ? Cast(doc[fieldKey], RichTextField) : undefined; + let startup = StrCast(doc.documentText); startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : ""; if (!startup && !field && doc) { - startup = StrCast(doc[this.props.fieldKey]); + startup = StrCast(doc[fieldKey]); } if (this._proseRef.current) { this._editorView = new EditorView(this._proseRef.current, { @@ -222,12 +225,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } }); if (startup) { - this.props.Document.proto!.documentText = undefined; + Doc.GetProto(doc).documentText = undefined; this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); } } if (this.props.selectOnLoad) { + console.log("Sel on load " + this.props.Document.title + " " + doc!.title); this.props.select(false); this._editorView!.focus(); } -- cgit v1.2.3-70-g09d2 From 06d5bb5c65da6f4a115ebf8118989115420bccef Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 11 Jun 2019 10:21:42 -0400 Subject: cleaned up some drop() code with text. fixed linkview titling. changed dragging to not deselect source collection. --- src/client/util/SelectionManager.ts | 23 ++++++++++++++++------ .../views/collections/CollectionBaseView.tsx | 5 +++-- .../CollectionFreeFormLinkView.tsx | 5 +++-- src/client/views/nodes/FormattedTextBox.tsx | 22 --------------------- 4 files changed, 23 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index ddad4b818..b26032b04 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -10,15 +10,23 @@ export namespace SelectionManager { @observable SelectedDocuments: Array = []; @action - SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { + SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it if (!ctrlPressed) { this.DeselectAll(); } - if (manager.SelectedDocuments.indexOf(doc) === -1) { - manager.SelectedDocuments.push(doc); - doc.props.whenActiveChanged(true); + if (manager.SelectedDocuments.indexOf(docView) === -1) { + manager.SelectedDocuments.push(docView); + docView.props.whenActiveChanged(true); + } + } + @action + DeselectDoc(docView: DocumentView): void { + let ind = manager.SelectedDocuments.indexOf(docView); + if (ind !== -1) { + manager.SelectedDocuments.splice(ind, 1); + docView.props.whenActiveChanged(false); } } @action @@ -31,8 +39,11 @@ export namespace SelectionManager { const manager = new Manager(); - export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { - manager.SelectDoc(doc, ctrlPressed); + export function DeselectDoc(docView: DocumentView): void { + manager.DeselectDoc(docView); + } + export function SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { + manager.SelectDoc(docView, ctrlPressed); } export function IsSelected(doc: DocumentView): boolean { diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index d120c3a0c..5238ad114 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -10,6 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import { ContextMenu } from '../ContextMenu'; import { FieldViewProps } from '../nodes/FieldView'; import './CollectionBaseView.scss'; +import { DocumentManager } from '../../util/DocumentManager'; export enum CollectionViewType { Invalid, @@ -129,7 +130,8 @@ export class CollectionBaseView extends React.Component { @action.bound removeDocument(doc: Doc): boolean { - SelectionManager.DeselectAll(); + let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView) + docView && SelectionManager.DeselectDoc(docView); const props = this.props; //TODO This won't create the field if it doesn't already exist const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []); @@ -163,7 +165,6 @@ export class CollectionBaseView extends React.Component { return true; } if (this.removeDocument(doc)) { - SelectionManager.DeselectAll(); return addDocument(doc); } return false; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index ca7c99f28..ba7e6cf9e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -44,8 +44,9 @@ export class CollectionFreeFormLinkView extends React.Component text += StrCast(l.title) + "(" + StrCast(l.linkDescription) + "), "); - text = text.substr(0, text.length - 2); + let first = this.props.LinkDocs[0]; + if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : ""); + else text = "-multiple-"; return ( <> ; private _inputReactionDisposer: Opt; @@ -136,25 +135,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - @undoBatch - @action - drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc = de.data.linkSourceDocument; - let destDoc = this.props.Document; - - DocUtils.MakeLink(sourceDoc, destDoc); - de.data.droppedDocuments.push(destDoc); - e.stopPropagation(); - } - } - componentDidMount() { - if (this._ref.current) { - this._dropDisposer = DragManager.MakeDropTarget(this._ref.current, { - handlers: { drop: this.drop.bind(this) } - }); - } const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -250,9 +231,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._proxyReactionDisposer) { this._proxyReactionDisposer(); } - if (this._dropDisposer) { - this._dropDisposer(); - } } onPointerDown = (e: React.PointerEvent): void => { -- cgit v1.2.3-70-g09d2 From fcae0da16f9413059d03638ba10ab30cdc460a42 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 11 Jun 2019 16:45:48 -0400 Subject: fixed text input to allow typing from the bottom (caption) and to allow text boxes to grow vertically (autoHeight) --- src/client/documents/Documents.ts | 2 +- src/client/views/DocumentDecorations.tsx | 7 +++--- src/client/views/MainOverlayTextBox.tsx | 22 ++++++++++++------ .../collectionFreeForm/CollectionFreeFormView.scss | 1 + .../collections/collectionFreeForm/MarqueeView.tsx | 3 ++- src/client/views/nodes/DocumentView.tsx | 8 +++---- src/client/views/nodes/FormattedTextBox.tsx | 27 +++++++++++++++++++--- 7 files changed, 51 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2ae127e21..b1df89dbd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -166,7 +166,7 @@ export namespace Docs { } function CreateTextPrototype(): Doc { let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" }); + { x: 0, y: 0, width: 300, backgroundColor: "#f1efeb" }); return textProto; } function CreatePdfPrototype(): Doc { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7260b00cf..1111cd2f5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -442,11 +442,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let actualdH = Math.max(height + (dH * scale), 20); doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); - let proto = Doc.GetProto(doc); + let proto = Doc.GetProto(element.props.Document); let fixedAspect = e.ctrlKey || (!BoolCast(proto.ignoreAspect, false) && nwidth && nheight); if (fixedAspect && (!nwidth || !nheight)) { - proto.nativeWidth = doc.width; - proto.nativeHeight = doc.height; + proto.nativeWidth = nwidth = doc.width || 0; + proto.nativeHeight = nheight = doc.height || 0; proto.ignoreAspect = true; } if (nwidth > 0 && nheight > 0) { @@ -469,6 +469,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else { doc.width = zoomBasis * actualdW; doc.height = zoomBasis * actualdH; + proto.autoHeight = undefined; } } }); diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index a0359419b..afa72d56e 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -9,6 +9,8 @@ import "./MainOverlayTextBox.scss"; import { FormattedTextBox } from './nodes/FormattedTextBox'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { Doc } from '../../new_fields/Doc'; +import { BoolCast } from '../../new_fields/Types'; +import { auto } from 'async'; interface MainOverlayTextBoxProps { } @@ -22,6 +24,7 @@ export class MainOverlayTextBox extends React.Component private _textHideOnLeave?: boolean; private _textTargetDiv: HTMLDivElement | undefined; private _textProxyDiv: React.RefObject; + private _textBottom: boolean | undefined; public TextDoc?: Doc; constructor(props: MainOverlayTextBoxProps) { @@ -49,10 +52,12 @@ export class MainOverlayTextBox extends React.Component this._textTargetDiv.style.color = this._textColor; } this.TextFieldKey = textFieldKey!; - this._textXf = tx ? tx : () => Transform.Identity(); + let txf = tx ? tx : () => Transform.Identity(); + this._textXf = txf; this._textTargetDiv = div; this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; if (div) { + this._textBottom = textFieldKey === "caption" ? true : false; // (getComputedStyle(div) as any).bottom; this._textColor = (getComputedStyle(div) as any).color; div.style.color = "transparent"; } @@ -102,13 +107,16 @@ export class MainOverlayTextBox extends React.Component if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { let textRect = this._textTargetDiv.getBoundingClientRect(); let s = this._textXf().Scale; - let auto = FormattedTextBox.InputBoxOverlay.props.height; - return
    + let bottom = this._textBottom ? textRect.bottom : textRect.top; + let hgt = 0; + return
    - + style={{ width: `${textRect.width * s}px`, height: hgt }}> +
    + +
    ; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index e10ba9d7e..5ac2e1f9c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -57,6 +57,7 @@ } >.jsx-parser { + position:absolute; z-index:0; } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index c699b3437..01bbbba4c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -93,7 +93,8 @@ export class MarqueeView extends React.Component } }); } else if (!e.ctrlKey) { - let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); + let newBox = Docs.TextDocument({ width: 200, height: 30, x: x, y: y, title: "-typed text-" }); + newBox.proto!.autoHeight = true; this.props.addLiveTextDocument(newBox); } e.stopPropagation(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 19f5c7d36..2428103d1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -317,13 +317,13 @@ export class DocumentView extends DocComponent(Docu makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); doc.isButton = !BoolCast(doc.isButton, false); - if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally - if (doc.isButton && !doc.nativeWidth) { + if (doc.isButton) { + if (!doc.nativeWidth) { doc.nativeWidth = this.props.Document[WidthSym](); doc.nativeHeight = this.props.Document[HeightSym](); - } else { - doc.nativeWidth = doc.nativeHeight = undefined; } + } else { + doc.nativeWidth = doc.nativeHeight = undefined; } } fullScreenClicked = (): void => { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 2923fd378..20e175031 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -10,7 +10,7 @@ import { EditorView } from "prosemirror-view"; import { Doc, Opt } from "../../../new_fields/Doc"; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; import { DocUtils, Docs } from '../../documents/Documents'; import { DocumentManager } from "../../util/DocumentManager"; @@ -29,6 +29,9 @@ import "./FormattedTextBox.scss"; import React = require("react"); import { Id } from '../../../new_fields/FieldSymbols'; import { MainOverlayTextBox } from '../MainOverlayTextBox'; +import { Utils } from '../../../Utils'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ContextMenu } from '../ContextMenu'; library.add(faEdit); library.add(faSmile); @@ -212,8 +215,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (this.props.selectOnLoad) { - console.log("Sel on load " + this.props.Document.title + " " + doc!.title); - this.props.select(false); + //this.props.select(false); this._editorView!.focus(); } } @@ -351,6 +353,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } + if (this.props.isOverlay && this.props.Document.autoHeight) { + let xf = this._ref.current!.getBoundingClientRect(); + let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height); + let nh = NumCast(this.props.Document.nativeHeight, 0); + let dh = NumCast(this.props.Document.height, 0); + let sh = scrBounds.height; + this.props.Document.height = nh ? dh / nh * sh : sh; + this.props.Document.proto!.nativeHeight = nh ? sh : undefined; + } } @action @@ -361,6 +372,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerLeave = (e: React.PointerEvent) => { this._entered = false; } + + specificContextMenu = (e: React.MouseEvent): void => { + let subitems: ContextMenuProps[] = []; + subitems.push({ + description: BoolCast(this.props.Document.autoHeight, false) ? "Manual Height" : "Auto Height", + event: action(() => this.props.Document.autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt" + }); + ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems }); + } render() { let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : ""; @@ -378,6 +398,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onKeyPress={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} + onContextMenu={this.specificContextMenu} onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} -- cgit v1.2.3-70-g09d2 From b0390a9d3e9201a16e06cf46196688026f949d9f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 11 Jun 2019 22:01:53 -0400 Subject: fixed issues with text box overlay and templates --- src/client/views/MainOverlayTextBox.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index afa72d56e..1b35fd40e 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -10,7 +10,6 @@ import { FormattedTextBox } from './nodes/FormattedTextBox'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { Doc } from '../../new_fields/Doc'; import { BoolCast } from '../../new_fields/Types'; -import { auto } from 'async'; interface MainOverlayTextBoxProps { } @@ -25,6 +24,7 @@ export class MainOverlayTextBox extends React.Component private _textTargetDiv: HTMLDivElement | undefined; private _textProxyDiv: React.RefObject; private _textBottom: boolean | undefined; + private _textAutoHeight: boolean | undefined; public TextDoc?: Doc; constructor(props: MainOverlayTextBoxProps) { @@ -37,7 +37,7 @@ export class MainOverlayTextBox extends React.Component this.TextDoc = box.props.Document; let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined); let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); }; - this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf) + this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content") } else { this.TextDoc = undefined; @@ -47,17 +47,18 @@ export class MainOverlayTextBox extends React.Component } @action - private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) { + private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform, autoHeight?: boolean) { if (this._textTargetDiv) { this._textTargetDiv.style.color = this._textColor; } + this._textAutoHeight = autoHeight; this.TextFieldKey = textFieldKey!; let txf = tx ? tx : () => Transform.Identity(); this._textXf = txf; this._textTargetDiv = div; this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; if (div) { - this._textBottom = textFieldKey === "caption" ? true : false; // (getComputedStyle(div) as any).bottom; + this._textBottom = div.parentElement && div.parentElement.style.bottom ? true : false; this._textColor = (getComputedStyle(div) as any).color; div.style.color = "transparent"; } @@ -107,12 +108,12 @@ export class MainOverlayTextBox extends React.Component if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { let textRect = this._textTargetDiv.getBoundingClientRect(); let s = this._textXf().Scale; - let bottom = this._textBottom ? textRect.bottom : textRect.top; - let hgt = 0; - return
    + let location = this._textBottom ? textRect.bottom : textRect.top; + let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight; + return
    -
    + style={{ width: `${textRect.width * s}px`, height: "0px" }}> +
    -- cgit v1.2.3-70-g09d2 From a638c12cde39a3ea5193a8038f72a55d706d9af8 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 12 Jun 2019 10:32:45 -0400 Subject: a bunch of cleanup and fixes to formattedtextbox & mainoverlaytextbox --- src/client/util/SelectionManager.ts | 8 +-- src/client/views/MainOverlayTextBox.tsx | 16 +++--- src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 7 +-- src/client/views/nodes/FormattedTextBox.tsx | 60 +++++----------------- 5 files changed, 28 insertions(+), 65 deletions(-) (limited to 'src') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index b26032b04..09bccb1a0 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -12,11 +12,11 @@ export namespace SelectionManager { @action SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it - if (!ctrlPressed) { - this.DeselectAll(); - } - if (manager.SelectedDocuments.indexOf(docView) === -1) { + if (!ctrlPressed) { + this.DeselectAll(); + } + manager.SelectedDocuments.push(docView); docView.props.whenActiveChanged(true); } diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 1b35fd40e..23e90ece5 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -1,15 +1,15 @@ import { action, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import "normalize.css"; import * as React from 'react'; +import { Doc } from '../../new_fields/Doc'; +import { BoolCast } from '../../new_fields/Types'; import { emptyFunction, returnTrue, returnZero, Utils } from '../../Utils'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; -import "normalize.css"; +import { CollectionDockingView } from './collections/CollectionDockingView'; import "./MainOverlayTextBox.scss"; import { FormattedTextBox } from './nodes/FormattedTextBox'; -import { CollectionDockingView } from './collections/CollectionDockingView'; -import { Doc } from '../../new_fields/Doc'; -import { BoolCast } from '../../new_fields/Types'; interface MainOverlayTextBoxProps { } @@ -25,7 +25,7 @@ export class MainOverlayTextBox extends React.Component private _textProxyDiv: React.RefObject; private _textBottom: boolean | undefined; private _textAutoHeight: boolean | undefined; - public TextDoc?: Doc; + @observable public TextDoc?: Doc; constructor(props: MainOverlayTextBoxProps) { super(props); @@ -105,6 +105,7 @@ export class MainOverlayTextBox extends React.Component } } render() { + this.TextDoc; if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { let textRect = this._textTargetDiv.getBoundingClientRect(); let s = this._textXf().Scale; @@ -114,8 +115,9 @@ export class MainOverlayTextBox extends React.Component
    -
    diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 762955a08..93a1a8cda 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -170,7 +170,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this.props.addDocument && this.props.addDocument(Docs.WebDocument(href, options)); } } else if (text) { - this.props.addDocument && this.props.addDocument(Docs.TextDocument({ ...options, documentText: "@@@" + text }), false); + this.props.addDocument && this.props.addDocument(Docs.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }), false); } return; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2428103d1..051940cc4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -125,10 +125,8 @@ export class DocumentView extends DocComponent(Docu constructor(props: DocumentViewProps) { super(props); - this.selectOnLoad = props.selectOnLoad; } - _reactionDisposer?: IReactionDisposer; @action componentDidMount() { @@ -442,14 +440,13 @@ export class DocumentView extends DocComponent(Docu onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; isSelected = () => SelectionManager.IsSelected(this); - @action select = (ctrlPressed: boolean) => { this.selectOnLoad = false; SelectionManager.SelectDoc(this, ctrlPressed); } + @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); } - @observable selectOnLoad: boolean = false; @computed get nativeWidth() { return this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.Document.nativeHeight || 0; } @computed get contents() { return ( - ); + ); } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 20e175031..6a51db4ac 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -8,11 +8,12 @@ import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc, Opt } from "../../../new_fields/Doc"; +import { Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; -import { DocUtils, Docs } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorKeymap"; @@ -21,37 +22,20 @@ import { ImageResizeView, schema } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { UndoManager } from "../../util/UndoManager"; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -import { Id } from '../../../new_fields/FieldSymbols'; -import { MainOverlayTextBox } from '../MainOverlayTextBox'; -import { Utils } from '../../../Utils'; -import { ContextMenuProps } from '../ContextMenuItem'; -import { ContextMenu } from '../ContextMenu'; library.add(faEdit); library.add(faSmile); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // -// HTML Markup: Key} />"); -// and the node's binding to the specified document KEYNAME as: -// document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.])); -// The Jsx parser at run time will bind: -// 'fieldKey' property to the Key stored in LayoutKeys -// and 'doc' property to the document that is being rendered -// -// When rendered() by React, this extracts the TextController from the Document stored at the -// specified Key and assigns it to an HTML input node. When changes are made to this node, -// this will edit the document and assign the new value to that field. -//] export interface FormattedTextBoxProps { isOverlay?: boolean; @@ -76,11 +60,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _proseRef: React.RefObject; private _editorView: Opt; private _toolTipTextMenu: TooltipTextMenu | undefined = undefined; - private _lastState: any = undefined; private _applyingChange: boolean = false; private _linkClicked = ""; private _reactionDisposer: Opt; - private _inputReactionDisposer: Opt; private _proxyReactionDisposer: Opt; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @observable _entered = false; @@ -119,10 +101,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const state = this._lastState = this._editorView.state.apply(tx); + const state = this._editorView.state.apply(tx); this._editorView.updateState(state); this._applyingChange = true; Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON()))); @@ -160,18 +141,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ] }; - if (this.props.isOverlay) { - this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay, - () => { - if (this._editorView) { - this._editorView.destroy(); - } - this.setupEditor(config, // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox - MainOverlayTextBox.Instance.TextDoc ? MainOverlayTextBox.Instance.TextDoc : this.props.Document, - MainOverlayTextBox.Instance.TextFieldKey ? MainOverlayTextBox.Instance.TextFieldKey : this.props.fieldKey); - } - ); - } else { + if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { if (this.props.isSelected()) { @@ -181,13 +151,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } - this._reactionDisposer = reaction( () => { const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined; - return field ? field.Data : undefined; + return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; }, - field => field && this._editorView && !this._applyingChange && + field => this._editorView && !this._applyingChange && this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); this.setupEditor(config, this.props.Document, this.props.fieldKey); @@ -215,8 +184,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (this.props.selectOnLoad) { - //this.props.select(false); - this._editorView!.focus(); + if (!this.props.isOverlay) this.props.select(false); + else this._editorView!.focus(); } } @@ -227,9 +196,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._reactionDisposer) { this._reactionDisposer(); } - if (this._inputReactionDisposer) { - this._inputReactionDisposer(); - } if (this._proxyReactionDisposer) { this._proxyReactionDisposer(); } @@ -394,7 +360,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial", pointerEvents: interactive ? "all" : "none", }} - // onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} @@ -403,7 +368,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onMouseDown={this.onMouseDown} - // tfs: do we need this event handler onWheel={this.onPointerWheel} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} -- cgit v1.2.3-70-g09d2