diff options
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 46 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.scss | 20 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 362 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 9 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.scss | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 98 | ||||
| -rw-r--r-- | src/client/views/nodes/IconBox.scss | 12 | ||||
| -rw-r--r-- | src/client/views/nodes/IconBox.tsx | 45 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.scss | 12 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 91 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValueBox.tsx | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/WebBox.scss | 6 | ||||
| -rw-r--r-- | src/client/views/nodes/WebBox.tsx | 35 |
15 files changed, 451 insertions, 302 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index b00cefbf6..12e5bdf1f 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -9,23 +9,18 @@ import React = require("react"); import { OmitKeys } from "../../../Utils"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - zoomFade: number; } @observer export class CollectionFreeFormDocumentView extends React.Component<CollectionFreeFormDocumentViewProps> { private _mainCont = React.createRef<HTMLDivElement>(); - constructor(props: CollectionFreeFormDocumentViewProps) { - super(props); - } - @computed get transform(): string { - return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px) scale(${this.zoom}, ${this.zoom}) `; + return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `; } - @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); } + @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.ZoomBasis, 1); } @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); } @computed get width(): number { return this.props.Document.Width(); } @computed get height(): number { return this.props.Document.Height(); } @@ -50,36 +45,49 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr this.props.Document.SetData(KeyStore.ZIndex, h, NumberField); } - contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; - + get X() { + return this.props.Document.GetNumber(KeyStore.X, 0); + } + get Y() { + return this.props.Document.GetNumber(KeyStore.Y, 0); + } getTransform = (): Transform => this.props.ScreenToLocalTransform() - .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)) + .translate(-this.X, -this.Y) .scale(1 / this.contentScaling()).scale(1 / this.zoom) + contentScaling = () => (this.nativeWidth > 0 ? this.width / this.nativeWidth : 1); + panelWidth = () => this.props.PanelWidth(); + panelHeight = () => this.props.PanelHeight(); + @computed get docView() { - return <DocumentView {...this.docViewProps} + return <DocumentView {...OmitKeys(this.props, ['zoomFade'])} ContentScaling={this.contentScaling} ScreenToLocalTransform={this.getTransform} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} />; } - @computed - get docViewProps(): DocumentViewProps { - return (OmitKeys(this.props, ['zoomFade'])); - } - panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth(); - panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight(); render() { + let zoomFade = 1; + //var zoom = doc.GetNumber(KeyStore.Zoom, 1); + // let transform = this.getTransform().scale(this.contentScaling()).inverse(); + // var [sptX, sptY] = transform.transformPoint(0, 0); + // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); + // let w = bptX - sptX; + // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1; + // let fadeUp = .75 * 1800; + // let fadeDown = .075 * 1800; + // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; + return ( <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{ - opacity: this.props.zoomFade, + opacity: zoomFade, transformOrigin: "left top", transform: this.transform, - pointerEvents: "all", + pointerEvents: (zoomFade < 0.09 ? "none" : "all"), width: this.width, height: this.height, position: "absolute", diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 76f852601..07599c345 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -15,6 +15,7 @@ import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; +import { IconBox } from "./IconBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { VideoBox } from "./VideoBox"; @@ -62,7 +63,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { return <p>Error loading layout keys</p>; } return <JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} + components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()} jsx={this.layout} showWarnings={true} diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index a946ac1a8..7c72fb6e6 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -1,10 +1,11 @@ @import "../globalCssVariables"; -.documentView-node { +.documentView-node, .documentView-node-topmost { position: inherit; top: 0; left:0; - background: $light-color; //overflow: hidden; + // background: $light-color; //overflow: hidden; + transform-origin: left top; &.minimized { width: 30px; @@ -12,7 +13,6 @@ } .top { - background: #232323; height: 20px; cursor: pointer; } @@ -28,16 +28,6 @@ height: calc(100% - 20px); } } - -.minimized-box { - height: 10px; - width: 10px; - border-radius: 2px; - background: $dark-color -} - -.minimized-box:hover { - background: $main-accent; - transform: scale(1.15); - cursor: pointer; +.documentView-node-topmost { + background: white; }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e851bc9c5..07a3c1819 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,12 +1,10 @@ import { action, computed, runInAction, observable } from "mobx"; import { observer } from "mobx-react"; -import { BooleanField } from "../../../fields/BooleanField"; import { Document } from "../../../fields/Document"; -import { Field, FieldWaiting, Opt } from "../../../fields/Field"; +import { Field, Opt, FieldWaiting } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { TextField } from "../../../fields/TextField"; import { ServerUtils } from "../../../server/ServerUtil"; import { emptyFunction, Utils } from "../../../Utils"; import { Documents } from "../../documents/Documents"; @@ -24,9 +22,11 @@ import { DocumentContentsView } from "./DocumentContentsView"; import { Template, Templates } from "./../Templates"; import "./DocumentView.scss"; import React = require("react"); +import { TextField } from "../../../fields/TextField"; +import { string } from "prop-types"; +import { NumberField } from "../../../fields/NumberField"; import { TemplateField } from "../../../fields/TemplateField"; - export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; Document: Document; @@ -41,7 +41,7 @@ export interface DocumentViewProps { focus: (doc: Document) => void; selectOnLoad: boolean; parentActive: () => boolean; - onActiveChanged: (isActive: boolean) => void; + whenActiveChanged: (isActive: boolean) => void; } export interface JsxArgs extends DocumentViewProps { Keys: { [name: string]: Key }; @@ -67,14 +67,12 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { let Keys: { [name: string]: any } = {}; let Fields: { [name: string]: any } = {}; for (const key of keys) { - let fn = emptyFunction; - Object.defineProperty(fn, "name", { value: key + "Key" }); - Keys[key] = fn; + Object.defineProperty(emptyFunction, "name", { value: key + "Key" }); + Keys[key] = emptyFunction; } for (const field of fields) { - let fn = emptyFunction; - Object.defineProperty(fn, "name", { value: field }); - Fields[field] = fn; + Object.defineProperty(emptyFunction, "name", { value: field }); + Fields[field] = emptyFunction; } let args: JsxArgs = { Document: function Document() { }, @@ -87,13 +85,14 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { @observer export class DocumentView extends React.Component<DocumentViewProps> { - private _mainCont = React.createRef<HTMLDivElement>(); - public get ContentRef() { - return this._mainCont; - } + static _incompleteAnimations: Map<string, boolean> = new Map<string, boolean>(); private _downX: number = 0; private _downY: number = 0; @computed get base(): string { return this.props.Document.GetText(KeyStore.BaseLayout, "<p>Error loading base layout data</p>"); } + private _mainCont = React.createRef<HTMLDivElement>(); + private _dropDisposer?: DragManager.DragDropDisposer; + + public get ContentDiv() { return this._mainCont.current; } @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @computed get topMost(): boolean { return this.props.isTopMost; } @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); } @@ -105,9 +104,13 @@ export class DocumentView extends React.Component<DocumentViewProps> { } set templates(templates: Array<Template>) { this.props.Document.SetData(KeyStore.Templates, templates, TemplateField); } screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); + onPointerDown = (e: React.PointerEvent): void => { this._downX = e.clientX; this._downY = e.clientY; + if (e.button === 2 && !this.isSelected()) { + return; + } if (e.shiftKey && e.buttons === 2) { if (this.props.isTopMost) { this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey); @@ -116,7 +119,10 @@ export class DocumentView extends React.Component<DocumentViewProps> { } e.stopPropagation(); } else { - if (this.active) { + let maxdoc = this.props.Document.GetT(KeyStore.MaximizedDoc, Document); + if (this.active || + maxdoc instanceof Document // bcz: need a better way of allowing a document to handle pointer events when its not active (ie. be a top-level widget) + ) { e.stopPropagation(); document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); @@ -126,11 +132,9 @@ export class DocumentView extends React.Component<DocumentViewProps> { } } - private dropDisposer?: DragManager.DragDropDisposer; - componentDidMount() { if (this._mainCont.current) { - this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { + this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } @@ -138,33 +142,31 @@ export class DocumentView extends React.Component<DocumentViewProps> { } componentDidUpdate() { - if (this.dropDisposer) { - this.dropDisposer(); + if (this._dropDisposer) { + this._dropDisposer(); } if (this._mainCont.current) { - this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { + this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } } componentWillUnmount() { - if (this.dropDisposer) { - this.dropDisposer(); + if (this._dropDisposer) { + this._dropDisposer(); } runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1)); } startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { if (this._mainCont.current) { - const [left, top] = this.props - .ScreenToLocalTransform() - .inverse() - .transformPoint(0, 0); + const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0); let dragData = new DragManager.DocumentDragData([this.props.Document]); + const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top); dragData.aliasOnDrop = dropAliasOfDraggedDoc; - dragData.xOffset = x - left; - dragData.yOffset = y - top; + dragData.xOffset = xoff; + dragData.yOffset = yoff; dragData.moveDocument = this.props.moveDocument; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { handlers: { @@ -179,13 +181,10 @@ export class DocumentView extends React.Component<DocumentViewProps> { if (e.cancelBubble) { return; } - if ( - Math.abs(this._downX - e.clientX) > 3 || - Math.abs(this._downY - e.clientY) > 3 - ) { + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - if (!this.topMost || e.buttons === 2 || e.altKey) { + if (!e.altKey && (!this.topMost || e.buttons === 2)) { this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey); } } @@ -196,57 +195,127 @@ export class DocumentView extends React.Component<DocumentViewProps> { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); e.stopPropagation(); - if (!SelectionManager.IsSelected(this) && - e.button !== 2 && - Math.abs(e.clientX - this._downX) < 4 && - Math.abs(e.clientY - this._downY) < 4 - ) { - SelectionManager.SelectDoc(this, e.ctrlKey); + if (!SelectionManager.IsSelected(this) && e.button !== 2 && + Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { + this.props.Document.GetTAsync(KeyStore.MaximizedDoc, Document).then(maxdoc => { + if (maxdoc instanceof Document) { // bcz: need a better way to associate behaviors with click events on widget-documents + this.props.addDocument && this.props.addDocument(maxdoc, false); + this.toggleIcon(); + } else + SelectionManager.SelectDoc(this, e.ctrlKey); + }); } } - stopPropogation = (e: React.SyntheticEvent) => { + stopPropagation = (e: React.SyntheticEvent) => { e.stopPropagation(); } deleteClicked = (): void => { - if (this.props.removeDocument) { - this.props.removeDocument(this.props.Document); - } + this.props.removeDocument && this.props.removeDocument(this.props.Document); } fieldsClicked = (e: React.MouseEvent): void => { - if (this.props.addDocument) { - this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false); - } + let kvp = Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }); + CollectionDockingView.Instance.AddRightSplit(kvp); } fullScreenClicked = (e: React.MouseEvent): void => { - CollectionDockingView.Instance.OpenFullScreen(this.props.Document); + CollectionDockingView.Instance.OpenFullScreen((this.props.Document.GetPrototype() as Document).MakeDelegate()); ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Close Full Screen", - event: this.closeFullScreenClicked - }); + ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } closeFullScreenClicked = (e: React.MouseEvent): void => { CollectionDockingView.Instance.CloseFullScreen(); ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Full Screen", - event: this.fullScreenClicked - }); + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } + @action createIcon = (layoutString: string): Document => { + let iconDoc = Documents.IconDocument(layoutString); + iconDoc.SetText(KeyStore.Title, "ICON" + this.props.Document.Title) + iconDoc.SetBoolean(KeyStore.IsMinimized, false); + iconDoc.SetNumber(KeyStore.NativeWidth, 0); + iconDoc.SetNumber(KeyStore.NativeHeight, 0); + iconDoc.SetNumber(KeyStore.X, this.props.Document.GetNumber(KeyStore.X, 0)); + iconDoc.SetNumber(KeyStore.Y, this.props.Document.GetNumber(KeyStore.Y, 0) - 24); + iconDoc.Set(KeyStore.Prototype, this.props.Document); + iconDoc.Set(KeyStore.MaximizedDoc, this.props.Document); + this.props.Document.Set(KeyStore.MinimizedDoc, iconDoc); + this.props.addDocument && this.props.addDocument(iconDoc, false); + return iconDoc; + } + + animateBetweenIcon(icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) { + setTimeout(() => { + let now = Date.now(); + let progress = Math.min(1, (now - stime) / 200); + let pval = maximizing ? + [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] : + [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress]; + target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress); + target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress); + target.SetNumber(KeyStore.X, pval[0]); + target.SetNumber(KeyStore.Y, pval[1]); + if (now < stime + 200) { + this.animateBetweenIcon(icon, targ, width, height, stime, target, maximizing); + } + else { + if (!maximizing) { + target.SetBoolean(KeyStore.IsMinimized, true); + target.SetNumber(KeyStore.X, targ[0]); + target.SetNumber(KeyStore.Y, targ[1]); + target.SetNumber(KeyStore.Width, width); + target.SetNumber(KeyStore.Height, height); + } + DocumentView._incompleteAnimations.set(target.Id, false); + } + }, + 2); + } + @action - public minimize = (): void => { - this.props.Document.SetData( - KeyStore.Minimized, - true as boolean, - BooleanField - ); + public toggleIcon = async (): Promise<void> => { SelectionManager.DeselectAll(); + let isMinimized: boolean | undefined; + let minDoc = await this.props.Document.GetTAsync(KeyStore.MinimizedDoc, Document); + if (!minDoc) return; + let minimizedDocSet = await minDoc.GetTAsync(KeyStore.LinkTags, ListField); + if (!minimizedDocSet) return; + minimizedDocSet.Data.map(async minimizedDoc => { + if (minimizedDoc instanceof Document) { + this.props.addDocument && this.props.addDocument(minimizedDoc, false); + let maximizedDoc = await minimizedDoc.GetTAsync(KeyStore.MaximizedDoc, Document); + if (maximizedDoc instanceof Document && !DocumentView._incompleteAnimations.get(maximizedDoc.Id)) { + DocumentView._incompleteAnimations.set(maximizedDoc.Id, true); + isMinimized = isMinimized === undefined ? maximizedDoc.GetBoolean(KeyStore.IsMinimized, false) : isMinimized; + maximizedDoc.SetBoolean(KeyStore.IsMinimized, false); + let minx = await minimizedDoc.GetTAsync(KeyStore.X, NumberField); + let miny = await minimizedDoc.GetTAsync(KeyStore.Y, NumberField); + let maxx = await maximizedDoc.GetTAsync(KeyStore.X, NumberField); + let maxy = await maximizedDoc.GetTAsync(KeyStore.Y, NumberField); + let maxw = await maximizedDoc.GetTAsync(KeyStore.Width, NumberField); + let maxh = await maximizedDoc.GetTAsync(KeyStore.Height, NumberField); + if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined && + maxw !== undefined && maxh !== undefined) + this.animateBetweenIcon( + [minx.Data, miny.Data], [maxx.Data, maxy.Data], maxw.Data, maxh.Data, + Date.now(), maximizedDoc, isMinimized); + } + + } + }) + } + + @action + public getIconDoc = async (): Promise<Document | undefined> => { + return await this.props.Document.GetTAsync(KeyStore.MinimizedDoc, Document).then(async mindoc => + mindoc ? mindoc : + await this.props.Document.GetTAsync(KeyStore.BackgroundLayout, TextField).then(async field => + (field instanceof TextField) ? this.createIcon(field.Data) : + await this.props.Document.GetTAsync(KeyStore.Layout, TextField).then(field => + (field instanceof TextField) ? this.createIcon(field.Data) : undefined))); } @undoBatch @@ -261,9 +330,9 @@ export class DocumentView extends React.Component<DocumentViewProps> { sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc => runInAction(() => { let batch = UndoManager.StartBatch("document view drop"); - linkDoc.Set(KeyStore.Title, new TextField("New Link")); - linkDoc.Set(KeyStore.LinkDescription, new TextField("")); - linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); + linkDoc.SetText(KeyStore.Title, "New Link"); + linkDoc.SetText(KeyStore.LinkDescription, ""); + linkDoc.SetText(KeyStore.LinkTags, "Default"); let dstTarg = protoDest ? protoDest : destDoc; let srcTarg = protoSrc ? protoSrc : sourceDoc; @@ -294,11 +363,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { } onDrop = (e: React.DragEvent) => { - if (e.isDefaultPrevented()) { - return; - } let text = e.dataTransfer.getData("text/plain"); - if (text && text.startsWith("<div")) { + if (!e.isDefaultPrevented() && text && text.startsWith("<div")) { let oldLayout = this.props.Document.GetText(KeyStore.Layout, ""); let layout = text.replace("{layout}", oldLayout); this.props.Document.SetText(KeyStore.Layout, layout); @@ -347,145 +413,51 @@ export class DocumentView extends React.Component<DocumentViewProps> { @action onContextMenu = (e: React.MouseEvent): void => { e.stopPropagation(); - let moved = - Math.abs(this._downX - e.clientX) > 3 || - Math.abs(this._downY - e.clientY) > 3; - if (moved || e.isDefaultPrevented()) { + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 || + e.isDefaultPrevented()) { e.preventDefault(); return; } e.preventDefault(); - if (!this.isMinimized()) { - ContextMenu.Instance.addItem({ - description: "Minimize", - event: this.minimize - }); - } - ContextMenu.Instance.addItem({ - description: "Full Screen", - event: this.fullScreenClicked - }); - ContextMenu.Instance.addItem({ - description: "Fields", - event: this.fieldsClicked - }); - ContextMenu.Instance.addItem({ - description: "Center", - event: () => this.props.focus(this.props.Document) - }); - ContextMenu.Instance.addItem({ - description: "Open Right", - event: () => - CollectionDockingView.Instance.AddRightSplit(this.props.Document) - }); - ContextMenu.Instance.addItem({ - description: "Copy URL", - event: () => { - Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)); - } - }); - ContextMenu.Instance.addItem({ - description: "Copy ID", - event: () => { - Utils.CopyText(this.props.Document.Id); - } - }); + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); + ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked }); + ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }); + ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) }); + ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)) }); + ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document.Id) }); //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) + ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - if (!this.topMost) { - // DocumentViews should stop propagation of this event - e.stopPropagation(); - } - - ContextMenu.Instance.addItem({ - description: "Delete", - event: this.deleteClicked - }); - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); - SelectionManager.SelectDoc(this, e.ctrlKey); - } - - isMinimized = () => { - let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField); - if (field && field !== FieldWaiting) { - return field.Data; - } - } - - @action - expand = () => { - this.props.Document.SetData( - KeyStore.Minimized, - false as boolean, - BooleanField - ); + if (!SelectionManager.IsSelected(this)) + SelectionManager.SelectDoc(this, false); } isSelected = () => SelectionManager.IsSelected(this); + select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); - select = (ctrlPressed: boolean) => { - SelectionManager.SelectDoc(this, ctrlPressed); - } + @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } + @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } + @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); } - @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } - @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } - @computed - get contents() { - return (<DocumentContentsView - {...this.props} - isSelected={this.isSelected} - select={this.select} - layoutKey={KeyStore.Layout} - />); - } render() { - if (!this.props.Document) { - return null; - } - var scaling = this.props.ContentScaling(); - var nativeWidth = this.nativeWidth; - var nativeHeight = this.nativeHeight; - - if (this.isMinimized()) { - return ( - <div - className="minimized-box" - ref={this._mainCont} - style={{ - transformOrigin: "left top", - transform: `scale(${scaling} , ${scaling})` - }} - onClick={this.expand} - onDrop={this.onDrop} - onPointerDown={this.onPointerDown} - /> - ); - } else { - var backgroundcolor = this.props.Document.GetText( - KeyStore.BackgroundColor, - "" - ); - return ( - <div - className="documentView-node" - ref={this._mainCont} - style={{ - background: backgroundcolor, - width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%", - height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%", - transformOrigin: "left top", - transform: `scale(${scaling} , ${scaling})` - }} - onDrop={this.onDrop} - onContextMenu={this.onContextMenu} - onPointerDown={this.onPointerDown} - > - {this.contents} - </div> - ); - } + var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%"; + var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%"; + + return ( + <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`} + ref={this._mainCont} + style={{ + background: this.props.Document.GetText(KeyStore.BackgroundColor, ""), + width: nativeWidth, height: nativeHeight, + transform: `scale(${scaling}, ${scaling})` + }} + onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} + > + {this.contents} + </div> + ); } } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ebd25f937..93e385821 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -23,6 +23,8 @@ import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../. import { CollectionView } from "../collections/CollectionView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; +import { IconField } from "../../../fields/IconFIeld"; +import { IconBox } from "./IconBox"; // @@ -43,7 +45,7 @@ export interface FieldViewProps { moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; ScreenToLocalTransform: () => Transform; active: () => boolean; - onActiveChanged: (isActive: boolean) => void; + whenActiveChanged: (isActive: boolean) => void; focus: (doc: Document) => void; } @@ -72,6 +74,9 @@ export class FieldView extends React.Component<FieldViewProps> { else if (field instanceof ImageField) { return <ImageBox {...this.props} />; } + else if (field instanceof IconField) { + return <IconBox {...this.props} />; + } else if (field instanceof VideoField) { return <VideoBox {...this.props} />; } @@ -95,7 +100,7 @@ export class FieldView extends React.Component<FieldViewProps> { layoutKey={KeyStore.Layout} ContainingCollectionView={this.props.ContainingCollectionView} parentActive={this.props.active} - onActiveChanged={this.props.onActiveChanged} /> + whenActiveChanged={this.props.whenActiveChanged} /> ); } else if (field instanceof ListField) { diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 3978c3d38..5eb2bf7ce 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -10,7 +10,7 @@ outline: none !important; } -.formattedTextBox-cont { +.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden { background: $light-color-secondary; padding: 0.9em; border-width: 0px; @@ -24,6 +24,10 @@ height: 100%; pointer-events: all; } +.formattedTextBox-cont-hidden { + overflow: hidden; + pointer-events: none; +} .menuicon { display: inline-block; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9f71609d5..a61f00fd0 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,22 +1,25 @@ import { action, IReactionDisposer, reaction, trace, computed } from "mobx"; import { baseKeymap } from "prosemirror-commands"; -import { history, redo, undo } from "prosemirror-history"; +import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { FieldWaiting, Opt } from "../../../fields/Field"; +import { KeyStore } from "../../../fields/KeyStore"; import { RichTextField } from "../../../fields/RichTextField"; +import { TextField } from "../../../fields/TextField"; +import { Document } from "../../../fields/Document"; +import buildKeymap from "../../util/ProsemirrorKeymap"; import { inpRules } from "../../util/RichTextRules"; import { schema } from "../../util/RichTextSchema"; +import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { ContextMenu } from "../../views/ContextMenu"; -import { Main } from "../Main"; +import { MainOverlayTextBox } from "../MainOverlayTextBox"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -import { TextField } from "../../../fields/TextField"; -import { KeyStore } from "../../../fields/KeyStore"; -import { MainOverlayTextBox } from "../MainOverlayTextBox"; +import { SelectionManager } from "../../util/SelectionManager"; import { observer } from "mobx-react"; const { buildMenuItems } = require("prosemirror-example-setup"); const { menuBar } = require("prosemirror-menu"); @@ -49,6 +52,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } private _ref: React.RefObject<HTMLDivElement>; private _editorView: Opt<EditorView>; + private _gotDown: boolean = false; private _reactionDisposer: Opt<IReactionDisposer>; private _inputReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; @@ -84,12 +88,18 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ history(), - keymap({ "Mod-z": undo, "Mod-y": redo }), + keymap(buildKeymap(schema)), keymap(baseKeymap), - this.tooltipMenuPlugin() + this.tooltipTextMenuPlugin(), + // this.tooltipLinkingMenuPlugin(), + new Plugin({ + props: { + attributes: { class: "ProseMirror-example-setup-style" } + } + }) ] : [ history(), - keymap({ "Mod-z": undo, "Mod-y": redo }), + keymap(buildKeymap(schema)), keymap(baseKeymap), ] }; @@ -100,42 +110,36 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte if (this._editorView) { this._editorView.destroy(); } - - this.setupEditor(config); + 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 } ); } else { +<<<<<<< HEAD this._proxyReactionDisposer = reaction(() => { }/*this.props.isSelected()*/, () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform())); +======= + this._proxyReactionDisposer = reaction(() => this.props.isSelected(), + () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform)); +>>>>>>> b63bcb791013766d5d16e4f38964499268f904c4 } + this._reactionDisposer = reaction( () => { const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined; return field && field !== FieldWaiting ? field.Data : undefined; }, - field => { - if (field && this._editorView && !this._applyingChange) { - this._editorView.updateState( - EditorState.fromJSON(config, JSON.parse(field)) - ); - } - } + field => field && this._editorView && !this._applyingChange && + this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); - this.setupEditor(config); + this.setupEditor(config, this.props.Document); } - private setupEditor(config: any) { - let state: EditorState; - let field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined; - if (field && field !== FieldWaiting && field.Data) { - state = EditorState.fromJSON(config, JSON.parse(field.Data)); - } else { - state = EditorState.create(config); - } + private setupEditor(config: any, doc?: Document) { + let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined; if (this._ref.current) { this._editorView = new EditorView(this._ref.current, { - state, + state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction }); } @@ -161,10 +165,6 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } } - shouldComponentUpdate() { - return false; - } - @action onChange(e: React.ChangeEvent<HTMLInputElement>) { const { fieldKey, Document } = this.props; @@ -173,13 +173,17 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } onPointerDown = (e: React.PointerEvent): void => { if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { + console.log("first"); e.stopPropagation(); } if (e.button === 2) { + this._gotDown = true; + console.log("second"); e.preventDefault(); } } onPointerUp = (e: React.PointerEvent): void => { + console.log("pointer up"); if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } @@ -187,7 +191,9 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte onFocused = (e: React.FocusEvent): void => { if (!this.props.isOverlay) { - MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()); + if (MainOverlayTextBox.Instance.TextDoc != this.props.Document) { + MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform); + } } else { if (this._ref.current) { this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll; @@ -199,6 +205,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte textCapability = (e: React.MouseEvent): void => { }; specificContextMenu = (e: React.MouseEvent): void => { + if (!this._gotDown) { + e.preventDefault(); + return; + } ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability @@ -224,7 +234,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } } - tooltipMenuPlugin() { + tooltipTextMenuPlugin() { let myprops = this.props; return new Plugin({ view(_editorView) { @@ -232,17 +242,36 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } }); } + + tooltipLinkingMenuPlugin() { + let myprops = this.props; + return new Plugin({ + view(_editorView) { + return new TooltipLinkingMenu(_editorView, myprops); + } + }); + } + onKeyPress(e: React.KeyboardEvent) { + if (e.key == "Escape") { + SelectionManager.DeselectAll(); + } e.stopPropagation(); + if (e.keyCode === 9) e.preventDefault(); // stop propagation doesn't seem to stop propagation of native keyboard events. // so we set a flag on the native event that marks that the event's been handled. // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; } render() { + let style = this.props.isOverlay ? "scroll" : "hidden"; return ( +<<<<<<< HEAD <div style={{ overflowY: /*this.props.isSelected() ||*/ this.props.isOverlay ? "scroll" : "hidden" }} className={`formattedTextBox-cont`} +======= + <div className={`formattedTextBox-cont-${style}`} +>>>>>>> b63bcb791013766d5d16e4f38964499268f904c4 onKeyDown={this.onKeyPress} onKeyPress={this.onKeyPress} onFocus={this.onFocused} @@ -251,8 +280,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte onContextMenu={this.specificContextMenu} // tfs: do we need this event handler onWheel={this.onPointerWheel} - ref={this._ref} - /> + ref={this._ref} /> ); } } diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss new file mode 100644 index 000000000..ce0ee2e09 --- /dev/null +++ b/src/client/views/nodes/IconBox.scss @@ -0,0 +1,12 @@ + +@import "../globalCssVariables"; +.iconBox-container { + position: absolute; + left:0; + top:0; + svg { + width: 100% !important; + height: 100%; + background: white; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx new file mode 100644 index 000000000..9c90c0a0e --- /dev/null +++ b/src/client/views/nodes/IconBox.tsx @@ -0,0 +1,45 @@ +import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from '../../../fields/Document'; +import { IconField } from "../../../fields/IconFIeld"; +import { KeyStore } from "../../../fields/KeyStore"; +import { SelectionManager } from "../../util/SelectionManager"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./IconBox.scss"; + + +library.add(faCaretUp); +library.add(faObjectGroup); +library.add(faStickyNote); +library.add(faFilePdf); +library.add(faFilm); + +@observer +export class IconBox extends React.Component<FieldViewProps> { + public static LayoutString() { return FieldView.LayoutString(IconBox); } + + @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); } + @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "<p>Error loading layout data</p>" as string); } + @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); } + + public static DocumentIcon(layout: string) { + let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf : + layout.indexOf("ImageBox") !== -1 ? faImage : + layout.indexOf("Formatted") !== -1 ? faStickyNote : + layout.indexOf("Video") !== -1 ? faFilm : + layout.indexOf("Collection") !== -1 ? faObjectGroup : + faCaretUp; + return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" /> + } + + render() { + return ( + <div className="iconBox-container"> + {this.minimizedIcon} + </div>); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 487038841..f4b3837ff 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -8,6 +8,16 @@ max-height: 100%; } +.imageBox-dot { + position:absolute; + bottom: 10; + left: 0; + border-radius: 10px; + width:20px; + height:20px; + background:gray; +} + .imageBox-cont img { height: 100%; width:100%; @@ -18,4 +28,4 @@ border: none; width: 100%; height: 100%; -} +}
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 6b0a3a799..ce855384c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,51 +1,87 @@ -import { action, observable, trace } from 'mobx'; +import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import Lightbox from 'react-image-lightbox'; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app +import { Document } from '../../../fields/Document'; import { FieldWaiting } from '../../../fields/Field'; import { ImageField } from '../../../fields/ImageField'; import { KeyStore } from '../../../fields/KeyStore'; +import { ListField } from '../../../fields/ListField'; +import { Utils } from '../../../Utils'; +import { DragManager } from '../../util/DragManager'; +import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { Utils } from '../../../Utils'; @observer export class ImageBox extends React.Component<FieldViewProps> { public static LayoutString() { return FieldView.LayoutString(ImageBox); } - private _ref: React.RefObject<HTMLDivElement>; private _imgRef: React.RefObject<HTMLImageElement>; private _downX: number = 0; private _downY: number = 0; private _lastTap: number = 0; @observable private _photoIndex: number = 0; @observable private _isOpen: boolean = false; + private dropDisposer?: DragManager.DragDropDisposer; constructor(props: FieldViewProps) { super(props); - this._ref = React.createRef(); this._imgRef = React.createRef(); - this.state = { - photoIndex: 0, - isOpen: false, - }; } @action onLoad = (target: any) => { var h = this._imgRef.current!.naturalHeight; var w = this._imgRef.current!.naturalWidth; - this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w); + if (this._photoIndex === 0) { + this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w); + this.props.Document.SetNumber(KeyStore.Height, this.props.Document.Width() * h / w); + } } - componentDidMount() { + + protected createDropTarget = (ele: HTMLDivElement) => { + if (this.dropDisposer) { + this.dropDisposer(); + } + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + onDrop = (e: React.DragEvent) => { + e.stopPropagation(); + e.preventDefault(); + console.log("IMPLEMENT ME PLEASE"); } - componentWillUnmount() { + + @undoBatch + drop = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.DocumentDragData) { + de.data.droppedDocuments.map(action((drop: Document) => { + let layout = drop.GetText(KeyStore.BackgroundLayout, ""); + if (layout.indexOf(ImageBox.name) !== -1) { + let imgData = this.props.Document.Get(KeyStore.Data); + if (imgData instanceof ImageField && imgData) { + this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData])); + } + let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]); + if (imgList) { + let field = drop.Get(KeyStore.Data); + if (field === FieldWaiting) { } + else if (field instanceof ImageField) imgList.push(field); + else if (field instanceof ListField) imgList.push(field.Data); + } + e.stopPropagation(); + } + })); + // de.data.removeDocument() bcz: need to implement + } } onPointerDown = (e: React.PointerEvent): void => { @@ -70,8 +106,7 @@ export class ImageBox extends React.Component<FieldViewProps> { e.stopPropagation(); } - lightbox = (path: string) => { - const images = [path]; + lightbox = (images: string[]) => { if (this._isOpen) { return (<Lightbox mainSrc={images[this._photoIndex]} @@ -102,15 +137,35 @@ export class ImageBox extends React.Component<FieldViewProps> { } } + @action + onDotDown(index: number) { + this._photoIndex = index; + this.props.Document.SetNumber(KeyStore.CurPage, index); + } + + dots(paths: string[]) { + let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1); + let dist = Math.min(nativeWidth / paths.length, 40); + let left = (nativeWidth - paths.length * dist) / 2; + return paths.map((p, i) => + <div className="imageBox-placer" key={i} > + <div className="imageBox-dot" style={{ background: (i == this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} /> + </div> + ); + } + render() { let field = this.props.Document.Get(this.props.fieldKey); - let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : - field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif"; + let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"]; + if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"]; + else if (field instanceof ImageField) paths = [field.Data.href]; + else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href); let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1); return ( - <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}> - <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} /> - {this.lightbox(path)} + <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}> + <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} /> + {paths.length > 1 ? this.dots(paths) : (null)} + {this.lightbox(paths)} </div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 29e4af160..ddbec014b 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -26,12 +26,6 @@ export class KeyValueBox extends React.Component<FieldViewProps> { super(props); } - - - shouldComponentUpdate() { - return false; - } - @action onEnterKey = (e: React.KeyboardEvent): void => { if (e.key === 'Enter') { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 5d69f23b2..d480eb5af 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -48,7 +48,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { isTopMost: false, selectOnLoad: false, active: returnFalse, - onActiveChanged: emptyFunction, + whenActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, focus: emptyDocFunction, }; diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index c73bc0c47..2ad1129a4 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -9,6 +9,12 @@ overflow: scroll; } +#webBox-htmlSpan { + position: absolute; + top:0; + left:0; +} + .webBox-button { padding : 0vw; border: none; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 90ce72c41..1edb4d826 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -18,21 +18,40 @@ export class WebBox extends React.Component<FieldViewProps> { @computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); } + _ignore = 0; + onPreWheel = (e: React.WheelEvent) => { + this._ignore = e.timeStamp; + } + onPrePointer = (e: React.PointerEvent) => { + this._ignore = e.timeStamp; + } + onPostPointer = (e: React.PointerEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + onPostWheel = (e: React.WheelEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } render() { let field = this.props.Document.Get(this.props.fieldKey); let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu"; - let content = this.html ? - <span dangerouslySetInnerHTML={{ __html: this.html }}></span> : - <div style={{ width: "100%", height: "100%", position: "absolute" }}> - <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe> - {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />} + let content = + <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> + {this.html ? <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: this.html }} /> : + <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }} />} </div>; return ( - <div className="webBox-cont" > - {content} - </div>); + <> + <div className="webBox-cont" > + {content} + </div> + {this.props.isSelected() ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />} + </>); } }
\ No newline at end of file |
