diff options
Diffstat (limited to 'src/client/views/DocumentDecorations.tsx')
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 288 |
1 files changed, 207 insertions, 81 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2dc496bc1..18449ed32 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,24 +1,26 @@ -import { action, computed, observable, trace, runInAction } from "mobx"; +import { action, computed, observable, runInAction, untracked, reaction } from "mobx"; import { observer } from "mobx-react"; import { Key } from "../../fields/Key"; -//import ContentEditable from 'react-contenteditable' import { KeyStore } from "../../fields/KeyStore"; import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; -import { Document } from "../../fields/Document"; import { TextField } from "../../fields/TextField"; -import { DragManager, DragLinksAsDocuments } from "../util/DragManager"; +import { Document } from "../../fields/Document"; +import { emptyFunction, Utils } from "../../Utils"; +import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; -import { CollectionView } from "./collections/CollectionView"; +import { undoBatch } from "../util/UndoManager"; import './DocumentDecorations.scss'; +import { MainOverlayTextBox } from "./MainOverlayTextBox"; import { DocumentView } from "./nodes/DocumentView"; import { LinkMenu } from "./nodes/LinkMenu"; +import { TemplateMenu } from "./TemplateMenu"; import React = require("react"); -import { FieldWaiting } from "../../fields/Field"; -import { emptyFunction } from "../../Utils"; -import { Main } from "./Main"; -import { undo } from "prosemirror-history"; -import { undoBatch } from "../util/UndoManager"; +import { Template, Templates } from "./Templates"; +import { CompileScript } from "../util/Scripting"; +import { IconBox } from "./nodes/IconBox"; +import { FieldValue, Field } from "../../fields/Field"; +import { Documents } from "../documents/Documents"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -26,37 +28,36 @@ export const Flyout = higflyout.default; @observer export class DocumentDecorations extends React.Component<{}, { value: string }> { static Instance: DocumentDecorations; - private _resizer = ""; private _isPointerDown = false; + private _resizing = ""; private keyinput: React.RefObject<HTMLInputElement>; - private _documents: DocumentView[] = SelectionManager.SelectedDocuments(); private _resizeBorderWidth = 16; - private _linkBoxHeight = 30; + private _linkBoxHeight = 20; private _titleHeight = 20; private _linkButton = React.createRef<HTMLDivElement>(); private _linkerButton = React.createRef<HTMLDivElement>(); - //@observable private _title: string = this._documents[0].props.Document.Title; - @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : ""; + private _downX = 0; + private _downY = 0; + @observable private _minimizedX = 0; + @observable private _minimizedY = 0; + @observable private _title: string = ""; + @observable private _edtingTitle = false; @observable private _fieldKey: Key = KeyStore.Title; @observable private _hidden = false; @observable private _opacity = 1; - @observable private _dragging = false; - + @observable private _iconifying = false; + @observable public Interacting = false; constructor(props: Readonly<{}>) { super(props); DocumentDecorations.Instance = this; - this.handleChange = this.handleChange.bind(this); this.keyinput = React.createRef(); + reaction(() => SelectionManager.SelectedDocuments().slice(), (docs) => docs.length === 0 && (this._edtingTitle = false)); } - @action - handleChange = (event: any) => { - this._title = event.target.value; - } - - @action - enterPressed = (e: any) => { + @action titleChanged = (event: any) => { this._title = event.target.value; } + @action titleBlur = () => { this._edtingTitle = false; } + @action titleEntered = (e: any) => { var key = e.keyCode || e.which; // enter pressed if (key === 13) { @@ -64,25 +65,52 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (text[0] === '#') { let command = text.slice(1, text.length); this._fieldKey = new Key(command); - // if (command === "Title" || command === "title") { - // this._fieldKey = KeyStore.Title; - // } - // else if (command === "Width" || command === "width") { - // this._fieldKey = KeyStore.Width; - // } - this._title = "changed"; - // TODO: Change field with switch statement + this._title = this.selectionTitle; } else { - this._title = "changed"; + if (SelectionManager.SelectedDocuments().length > 0) { + let field = SelectionManager.SelectedDocuments()[0].props.Document.Get(this._fieldKey); + if (field instanceof NumberField) { + SelectionManager.SelectedDocuments().forEach(d => + d.props.Document.SetNumber(this._fieldKey, +this._title)); + } else if (field instanceof TextField || true) { + SelectionManager.SelectedDocuments().forEach(d => + d.props.Document.SetText(this._fieldKey, this._title)); + } + } } e.target.blur(); } } + @action onTitleDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + e.stopPropagation(); + this.onBackgroundDown(e); + document.removeEventListener("pointermove", this.onTitleMove); + document.removeEventListener("pointerup", this.onTitleUp); + document.addEventListener("pointermove", this.onTitleMove); + document.addEventListener("pointerup", this.onTitleUp); + } + @action onTitleMove = (e: PointerEvent): void => { + if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { + this.Interacting = true; + } + if (this.Interacting) this.onBackgroundMove(e); + e.stopPropagation(); + } + @action onTitleUp = (e: PointerEvent): void => { + if (Math.abs(e.clientX - this._downX) < 4 || Math.abs(e.clientY - this._downY) < 4) { + this._title = this.selectionTitle; + this._edtingTitle = true; + } + document.removeEventListener("pointermove", this.onTitleMove); + document.removeEventListener("pointerup", this.onTitleUp); + this.onBackgroundUp(e); + } @computed get Bounds(): { x: number, y: number, b: number, r: number } { - this._documents = SelectionManager.SelectedDocuments(); return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { if (documentView.props.isTopMost) { return bounds; @@ -97,46 +125,39 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); } - - @computed - public get Hidden() { return this._hidden; } - public set Hidden(value: boolean) { this._hidden = value; } - - _lastDrag: number[] = [0, 0]; onBackgroundDown = (e: React.PointerEvent): void => { document.removeEventListener("pointermove", this.onBackgroundMove); - document.addEventListener("pointermove", this.onBackgroundMove); document.removeEventListener("pointerup", this.onBackgroundUp); + document.addEventListener("pointermove", this.onBackgroundMove); document.addEventListener("pointerup", this.onBackgroundUp); - this._lastDrag = [e.clientX, e.clientY]; e.stopPropagation(); - if (e.currentTarget.localName !== "input") { - e.preventDefault(); - } + e.preventDefault(); } @action onBackgroundMove = (e: PointerEvent): void => { let dragDocView = SelectionManager.SelectedDocuments()[0]; - const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); + const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); + const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top); let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); + dragData.xOffset = xoff; + dragData.yOffset = yoff; dragData.aliasOnDrop = false; - dragData.xOffset = e.x - left; - dragData.yOffset = e.y - top; - let move = SelectionManager.SelectedDocuments()[0].props.moveDocument; - dragData.moveDocument = move; - this._dragging = true; + dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument; + this.Interacting = true; + this._hidden = true; document.removeEventListener("pointermove", this.onBackgroundMove); document.removeEventListener("pointerup", this.onBackgroundUp); - DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentRef.current!), dragData, e.x, e.y, { - handlers: { - dragComplete: action(() => this._dragging = false), - }, + document.removeEventListener("pointermove", this.onTitleMove); + document.removeEventListener("pointerup", this.onTitleUp); + DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, { + handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) }, hideSource: true }); e.stopPropagation(); } + @action onBackgroundUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onBackgroundMove); document.removeEventListener("pointerup", this.onBackgroundUp); @@ -169,34 +190,114 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onCloseUp); } } + @action onMinimizeDown = (e: React.PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { + this._downX = e.pageX; + this._downY = e.pageY; + let selDoc = SelectionManager.SelectedDocuments()[0]; + let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0); + this._minimizedX = selDocPos[0] + 12; + this._minimizedY = selDocPos[1] + 12; document.removeEventListener("pointermove", this.onMinimizeMove); document.addEventListener("pointermove", this.onMinimizeMove); document.removeEventListener("pointerup", this.onMinimizeUp); document.addEventListener("pointerup", this.onMinimizeUp); } } + + @action onMinimizeMove = (e: PointerEvent): void => { e.stopPropagation(); - if (e.button === 0) { + if (Math.abs(e.pageX - this._downX) > Utils.DRAG_THRESHOLD || + Math.abs(e.pageY - this._downY) > Utils.DRAG_THRESHOLD) { + let selDoc = SelectionManager.SelectedDocuments()[0]; + let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0); + let snapped = Math.abs(e.pageX - selDocPos[0]) < 20 && Math.abs(e.pageY - selDocPos[1]) < 20; + this._minimizedX = snapped ? selDocPos[0] + 4 : e.clientX; + this._minimizedY = snapped ? selDocPos[1] - 18 : e.clientY; + let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd); + Promise.all(selectedDocs.map(async selDoc => await this.getIconDoc(selDoc))).then(minDocSet => + this.moveIconDocs(SelectionManager.SelectedDocuments()) + ); + this._iconifying = snapped; } } + + + @action createIcon = (docView: DocumentView, layoutString: string): Document => { + let doc = docView.props.Document; + let iconDoc = Documents.IconDocument(layoutString); + iconDoc.SetText(KeyStore.Title, "ICON" + doc.Title) + iconDoc.SetBoolean(KeyStore.IsMinimized, false); + iconDoc.SetNumber(KeyStore.NativeWidth, 0); + iconDoc.SetNumber(KeyStore.NativeHeight, 0); + iconDoc.SetNumber(KeyStore.X, doc.GetNumber(KeyStore.X, 0)); + iconDoc.SetNumber(KeyStore.Y, doc.GetNumber(KeyStore.Y, 0) - 24); + iconDoc.Set(KeyStore.Prototype, doc); + iconDoc.Set(KeyStore.MaximizedDoc, doc); + doc.Set(KeyStore.MinimizedDoc, iconDoc); + docView.props.addDocument && docView.props.addDocument(iconDoc, false); + return iconDoc; + } + @action + public getIconDoc = async (docView: DocumentView): Promise<Document | undefined> => { + let doc = docView.props.Document; + let iconDoc = await doc.GetTAsync(KeyStore.MinimizedDoc, Document).then(async mindoc => + mindoc ? mindoc : + await doc.GetTAsync(KeyStore.BackgroundLayout, TextField).then(async field => + (field instanceof TextField) ? this.createIcon(docView, field.Data) : + await doc.GetTAsync(KeyStore.Layout, TextField).then(field => + (field instanceof TextField) ? this.createIcon(docView, field.Data) : undefined))); + if (SelectionManager.SelectedDocuments()[0].props.addDocument !== undefined) + SelectionManager.SelectedDocuments()[0].props.addDocument!(iconDoc!); + return iconDoc; + } + @action onMinimizeUp = (e: PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { - SelectionManager.SelectedDocuments().map(dv => dv.minimize()); document.removeEventListener("pointermove", this.onMinimizeMove); document.removeEventListener("pointerup", this.onMinimizeUp); + let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd); + Promise.all(selectedDocs.map(async selDoc => await this.getIconDoc(selDoc))).then(minDocSet => { + let minDocs = minDocSet.filter(minDoc => minDoc instanceof Document).map(minDoc => minDoc as Document); + minDocs.map(minDoc => { + minDoc.SetNumber(KeyStore.X, minDocs[0].GetNumber(KeyStore.X, 0)); + minDoc.SetNumber(KeyStore.Y, minDocs[0].GetNumber(KeyStore.Y, 0)); + minDoc.SetData(KeyStore.LinkTags, minDocs, ListField); + if (this._iconifying && selectedDocs[0].props.removeDocument) { + selectedDocs[0].props.removeDocument(minDoc); + (minDoc.Get(KeyStore.MaximizedDoc, false) as Document)!.Set(KeyStore.MinimizedDoc, undefined); + } + }); + runInAction(() => this._minimizedX = this._minimizedY = 0); + if (!this._iconifying) selectedDocs[0].props.toggleMinimized(); + this._iconifying = false; + }); } } + moveIconDocs(selViews: DocumentView[], minDocSet?: FieldValue<Field>[]) { + selViews.map(selDoc => { + let minDoc = selDoc.props.Document.Get(KeyStore.MinimizedDoc); + if (minDoc instanceof Document) { + let zoom = selDoc.props.Document.GetNumber(KeyStore.ZoomBasis, 1); + let where = (selDoc.props.ScreenToLocalTransform()).scale(selDoc.props.ContentScaling()).scale(1 / zoom). + transformPoint(this._minimizedX - 12, this._minimizedY - 12); + minDoc.SetNumber(KeyStore.X, where[0] + selDoc.props.Document.GetNumber(KeyStore.X, 0)); + minDoc.SetNumber(KeyStore.Y, where[1] + selDoc.props.Document.GetNumber(KeyStore.Y, 0)); + } + }); + } + @action onPointerDown = (e: React.PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { this._isPointerDown = true; - this._resizer = e.currentTarget.id; + this._resizing = e.currentTarget.id; + this.Interacting = true; document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -221,7 +322,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (this._linkerButton.current !== null) { document.removeEventListener("pointermove", this.onLinkerButtonMoved); document.removeEventListener("pointerup", this.onLinkerButtonUp); - let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0].props.Document); + let selDoc = SelectionManager.SelectedDocuments()[0]; + let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.GetPrototype() : undefined; + let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { handlers: { dragComplete: action(emptyFunction), @@ -265,7 +368,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let dX = 0, dY = 0, dW = 0, dH = 0; - switch (this._resizer) { + switch (this._resizing) { case "": break; case "documentDecorations-topLeftResizer": @@ -304,9 +407,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> break; } - Main.Instance.SetTextDoc(); + MainOverlayTextBox.Instance.SetTextDoc(); SelectionManager.SelectedDocuments().forEach(element => { - const rect = element.screenRect(); + const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect(); + if (rect.width !== 0) { let doc = element.props.Document; let width = doc.GetNumber(KeyStore.Width, 0); @@ -334,8 +438,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> }); } + @action onPointerUp = (e: PointerEvent): void => { e.stopPropagation(); + this._resizing = ""; + this.Interacting = false; + SelectionManager.ReselectAll(); if (e.button === 0) { e.preventDefault(); this._isPointerDown = false; @@ -344,17 +452,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } - getValue = (): string => { - if (this._title === "changed" && this._documents.length > 0) { - let field = this._documents[0].props.Document.Get(this._fieldKey); + @computed + get selectionTitle(): string { + if (SelectionManager.SelectedDocuments().length === 1) { + let field = SelectionManager.SelectedDocuments()[0].props.Document.Get(this._fieldKey); if (field instanceof TextField) { return (field).GetValue(); } else if (field instanceof NumberField) { return (field).GetValue().toString(); } + } else if (SelectionManager.SelectedDocuments().length > 1) { + return "-multiple-"; } - return this._title; + return "-unset-"; } changeFlyoutContent = (): void => { @@ -363,20 +474,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> // buttonOnPointerUp = (e: React.PointerEvent): void => { // e.stopPropagation(); // } + render() { var bounds = this.Bounds; - if (bounds.x === Number.MAX_VALUE) { - return (null); - } - // console.log(this._documents.length) - // let test = this._documents[0].props.Document.Title; - if (this.Hidden) { - return (null); - } - if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { - console.log("DocumentDecorations: Bounds Error"); + let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; + if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } + let minimizeIcon = ( + <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}> + {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."} + </div>); let linkButton = null; if (SelectionManager.SelectedDocuments().length > 0) { @@ -391,25 +499,42 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div> </Flyout>); } + + let templates: Map<Template, boolean> = new Map(); + let doc = SelectionManager.SelectedDocuments()[0]; + Array.from(Object.values(Templates.TemplateList)).map(template => { + let docTemps = doc.templates; + let checked = false; + docTemps.forEach(temp => { + if (template.Name === temp.Name) { + checked = true; + } + }); + templates.set(template, checked); + }); + return (<div className="documentDecorations"> <div className="documentDecorations-background" style={{ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px", left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, - pointerEvents: this._dragging ? "none" : "all", + pointerEvents: this.Interacting ? "none" : "all", zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} > </div> - <div id="documentDecorations-container" style={{ + <div className="documentDecorations-container" style={{ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px", left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, opacity: this._opacity }}> - <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div> - <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} /> + {minimizeIcon} + + {this._edtingTitle ? + <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> : + <div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>} <div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div> <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> @@ -423,6 +548,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> <div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div> <div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div> + <TemplateMenu doc={doc} templates={templates} /> </div > </div> ); |