diff options
Diffstat (limited to 'src/client/views')
26 files changed, 719 insertions, 392 deletions
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 234f82eb9..ea40c8e99 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -3,16 +3,16 @@ display: flex; z-index: 1000; box-shadow: #AAAAAA .2vw .2vw .4vw; + flex-direction: column; } .contextMenu-item { - width: 10vw; - height: 4vh; - background: #DDDDDD; + width: auto; + height: auto; + background: #F0F8FF; display: flex; - justify-content: center; + justify-content: left; align-items: center; - flex-direction: column; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; @@ -20,11 +20,18 @@ -ms-user-select: none; user-select: none; transition: all .1s; + border-width: .11px; + border-style: none; + border-color: rgb(187, 186, 186); + border-bottom-style: solid; + padding: 10px; + white-space: nowrap; + font-size: 1.5vw; } .contextMenu-item:hover { transition: all .1s; - background: #AAAAAA + background: #B0E0E6; } .contextMenu-description { diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 4f26a75d2..fcb934860 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,6 +1,6 @@ import React = require("react"); import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem"; -import { observable } from "mobx"; +import { observable, action } from "mobx"; import { observer } from "mobx-react"; import "./ContextMenu.scss" @@ -12,6 +12,8 @@ export class ContextMenu extends React.Component { @observable private _pageX: number = 0; @observable private _pageY: number = 0; @observable private _display: string = "none"; + @observable private _searchString: string = ""; + private ref: React.RefObject<HTMLDivElement>; @@ -23,11 +25,13 @@ export class ContextMenu extends React.Component { ContextMenu.Instance = this; } + @action clearItems() { this._items = [] this._display = "none" } + @action addItem(item: ContextMenuProps) { if (this._items.indexOf(item) === -1) { this._items.push(item); @@ -38,10 +42,13 @@ export class ContextMenu extends React.Component { return this._items; } + @action displayMenu(x: number, y: number) { this._pageX = x this._pageY = y + this._searchString = ""; + this._display = "flex" } @@ -59,10 +66,18 @@ export class ContextMenu extends React.Component { render() { return ( <div className="contextMenu-cont" style={{ left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}> - {this._items.map(prop => { + <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange}></input> + {this._items.filter(prop => { + return prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1; + }).map(prop => { return <ContextMenuItem {...prop} key={prop.description} /> })} </div> ) } + + @action + onChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._searchString = e.target.value; + } }
\ No newline at end of file diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 8f00f8b3d..4801c1555 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -1,11 +1,19 @@ import React = require("react"); -import { ContextMenu } from "./ContextMenu"; export interface ContextMenuProps { description: string; event: (e: React.MouseEvent<HTMLDivElement>) => void; } +export interface SubmenuProps { + description: string; + subitems: ContextMenuProps[]; +} + +export interface ContextMenuItemProps { + type: ContextMenuProps | SubmenuProps +} + export class ContextMenuItem extends React.Component<ContextMenuProps> { render() { return ( diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 975a125f7..9fd73a33b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,4 +1,4 @@ -import { observable, computed, action } from "mobx"; +import { observable, computed } from "mobx"; import React = require("react"); import { SelectionManager } from "../util/SelectionManager"; import { observer } from "mobx-react"; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 8d9a47eaa..84b1b91c3 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -3,12 +3,30 @@ import { observer } from 'mobx-react'; import { observable, action } from 'mobx'; export interface EditableProps { + /** + * Called to get the initial value for editing + * */ GetValue(): string; + + /** + * Called to apply changes + * @param value - The string entered by the user to set the value to + * @returns `true` if setting the value was successful, `false` otherwise + * */ SetValue(value: string): boolean; + + /** + * The contents to render when not editing + */ contents: any; height: number } +/** + * Customizable view that can be given an arbitrary view to render normally, + * but can also be edited with customizable functions to get a string version + * of the content, and set the value based on the entered string. + */ @observer export class EditableView extends React.Component<EditableProps> { @observable @@ -17,8 +35,9 @@ export class EditableView extends React.Component<EditableProps> { @action onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key == "Enter" && !e.ctrlKey) { - this.props.SetValue(e.currentTarget.value); - this.editing = false; + if (this.props.SetValue(e.currentTarget.value)) { + this.editing = false; + } } else if (e.key == "Escape") { this.editing = false; } @@ -27,12 +46,11 @@ export class EditableView extends React.Component<EditableProps> { render() { if (this.editing) { return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)} - style={{ width: "100%" }}></input> + style={{ display: "inline" }}></input> } else { return ( - <div className="editableView-container-editing" style={{ display: "flex", height: "100%", maxHeight: `${this.props.height}` }} - onClick={action(() => this.editing = true)} - > + <div className="editableView-container-editing" style={{ display: "inline", height: "100%", maxHeight: `${this.props.height}` }} + onClick={action(() => this.editing = true)}> {this.props.contents} </div> ) diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index e73f62904..4334ed299 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -28,4 +28,24 @@ h1 { p { margin: 0px; padding: 0px; -}
\ No newline at end of file +} +::-webkit-scrollbar { + -webkit-appearance: none; + height:5px; + width:5px; +} +::-webkit-scrollbar-thumb { + border-radius: 2px; + background-color: rgba(0,0,0,.5); +} + +.main-buttonDiv { + position: absolute; + width: 150px; + left: 0px; +} +.main-undoButtons { + position: absolute; + width: 150px; + right: 0px; +} diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 17dda899d..d845fa7a3 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,167 +1,101 @@ -import { action, configure, reaction, computed } from 'mobx'; +import { action, configure } from 'mobx'; import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { DocumentDecorations } from './DocumentDecorations'; -import { Documents } from '../documents/Documents'; import { Document } from '../../fields/Document'; import { KeyStore } from '../../fields/KeyStore'; import "./Main.scss"; -import { ContextMenu } from './ContextMenu'; -import { DocumentView } from './nodes/DocumentView'; -import { Server } from '../Server'; +import { MessageStore } from '../../server/Message'; import { Utils } from '../../Utils'; -import { ServerUtils } from '../../server/ServerUtil'; -import { MessageStore, DocumentTransfer } from '../../server/Message'; +import { Documents } from '../documents/Documents'; +import { Server } from '../Server'; +import { setupDrag } from '../util/DragManager'; import { Transform } from '../util/Transform'; -import { CollectionDockingView } from './collections/CollectionDockingView'; -import { FieldWaiting } from '../../fields/Field'; import { UndoManager } from '../util/UndoManager'; -import { DragManager } from '../util/DragManager'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +import { ContextMenu } from './ContextMenu'; +import { DocumentDecorations } from './DocumentDecorations'; +import { DocumentView } from './nodes/DocumentView'; +import "./Main.scss"; -configure({ - enforceActions: "observed" -}); -window.addEventListener("drop", function (e) { - e.preventDefault(); -}, false) -window.addEventListener("dragover", function (e) { - e.preventDefault(); -}, false) +configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action +window.addEventListener("drop", (e) => e.preventDefault(), false) +window.addEventListener("dragover", (e) => e.preventDefault(), false) document.addEventListener("pointerdown", action(function (e: PointerEvent) { - console.log(ContextMenu); if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) { ContextMenu.Instance.clearItems() } }), true) -//runInAction(() => -// let doc1 = Documents.TextDocument({ title: "hello" }); -// let doc2 = doc1.MakeDelegate(); -// doc2.Set(KS.X, new NumberField(150)); -// doc2.Set(KS.Y, new NumberField(20)); -// let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { -// x: 450, y: 100, title: "cat 1" -// }); -// doc3.Set(KeyStore.Data, new ImageField); -// const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { -// x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v -// })); -// schemaDocs[0].SetData(KS.Author, "Tyler", TextField); -// schemaDocs[4].SetData(KS.Author, "Bob", TextField); -// schemaDocs.push(doc2); -// const doc7 = Documents.SchemaDocument(schemaDocs) - const mainDocId = "mainDoc"; -Documents.initProtos(() => { - Utils.EmitCallback(Server.Socket, MessageStore.GetField, mainDocId, (res: any) => { - console.log("HELLO WORLD") - console.log("RESPONSE: " + res) - let mainContainer: Document; - let mainfreeform: Document; - if (res) { - mainContainer = ServerUtils.FromJson(res) as Document; - mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document); - } - else { - mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId); - Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainContainer.ToJson())) +let mainContainer: Document; +let mainfreeform: Document; +Documents.initProtos(mainDocId, (res?: Document) => { + if (res instanceof Document) { + mainContainer = res; + mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document); + } + else { + mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId); - setTimeout(() => { - mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" }); - Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainfreeform.ToJson())); + // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) + setTimeout(() => { + mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" }); - var docs = [mainfreeform].map(doc => CollectionDockingView.makeDocumentConfig(doc)); - mainContainer.SetText(KeyStore.Data, JSON.stringify({ content: [{ type: 'row', content: docs }] })); - mainContainer.Set(KeyStore.ActiveFrame, mainfreeform); - }, 0); - } + var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(mainfreeform)] }] }; + mainContainer.SetText(KeyStore.Data, JSON.stringify(dockingLayout)); + mainContainer.Set(KeyStore.ActiveFrame, mainfreeform); + }, 0); + } - let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {})) - let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) - let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a feeform collection" })); - let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" })); - let addImageNode = action(() => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - width: 200, height: 200, title: "an image of a cat" - })); + let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; + let weburl = "https://cs.brown.edu/courses/cs166/"; + let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {})) + let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) + let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" })); + let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" })); + let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" })); + let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); - let addClick = (creator: any) => action(() => { - var img = creator(); - img.SetNumber(KeyStore.X, 0); - img.SetNumber(KeyStore.Y, 0); - mainfreeform.GetList<Document>(KeyStore.Data, []).push(img); - }); + let addClick = (creator: () => Document) => action(() => + mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator()) + ); - let imgRef = React.createRef<HTMLDivElement>(); - let textRef = React.createRef<HTMLDivElement>(); - let schemaRef = React.createRef<HTMLDivElement>(); - let colRef = React.createRef<HTMLDivElement>(); - let curMoveListener: any = null - let onRowMove = (creator: any, dragRef: any) => action((e: PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); + let imgRef = React.createRef<HTMLDivElement>(); + let webRef = React.createRef<HTMLDivElement>(); + let textRef = React.createRef<HTMLDivElement>(); + let schemaRef = React.createRef<HTMLDivElement>(); + let colRef = React.createRef<HTMLDivElement>(); - document.removeEventListener("pointermove", curMoveListener); - document.removeEventListener('pointerup', onRowUp); - DragManager.StartDrag(dragRef.current!, { document: creator() }); - }); - let onRowUp = action((e: PointerEvent): void => { - document.removeEventListener("pointermove", curMoveListener); - document.removeEventListener('pointerup', onRowUp); - }); - let onRowDown = (creator: any, dragRef: any) => (e: React.PointerEvent) => { - if (e.shiftKey) { - CollectionDockingView.Instance.StartOtherDrag(dragRef.current!, creator()); - e.stopPropagation(); - } else { - document.addEventListener("pointermove", curMoveListener = onRowMove(creator, dragRef)); - document.addEventListener('pointerup', onRowUp); - } - } - ReactDOM.render(( - <div style={{ position: "absolute", width: "100%", height: "100%" }}> - <DocumentView Document={mainContainer} - AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity} - ContentScaling={() => 1} - PanelWidth={() => 0} - PanelHeight={() => 0} - isTopMost={true} - ContainingCollectionView={undefined} /> - <DocumentDecorations /> - <ContextMenu /> - <div style={{ position: 'absolute', bottom: '0px', left: '0px', width: '150px' }} ref={imgRef} > - <button onPointerDown={onRowDown(addImageNode, imgRef)} onClick={addClick(addImageNode)}>Add Image</button></div> - <div style={{ position: 'absolute', bottom: '25px', left: '0px', width: '150px' }} ref={textRef}> - <button onPointerDown={onRowDown(addTextNode, textRef)} onClick={addClick(addTextNode)}>Add Text</button></div> - <div style={{ position: 'absolute', bottom: '50px', left: '0px', width: '150px' }} ref={colRef}> - <button onPointerDown={onRowDown(addColNode, colRef)} onClick={addClick(addColNode)}>Add Collection</button></div> - <div style={{ position: 'absolute', bottom: '75px', left: '0px', width: '150px' }} ref={schemaRef}> - <button onPointerDown={onRowDown(addSchemaNode, schemaRef)} onClick={addClick(addSchemaNode)}>Add Schema</button></div> - <button style={{ position: 'absolute', bottom: '100px', left: '0px', width: '150px' }} onClick={clearDatabase}>Clear Database</button> - <button style={{ position: 'absolute', bottom: '25', right: '0px', width: '150px' }} onClick={() => UndoManager.Undo()}>Undo</button> - <button style={{ position: 'absolute', bottom: '0', right: '0px', width: '150px' }} onClick={() => UndoManager.Redo()}>Redo</button> - </div>), - document.getElementById('root')); - }) -}); -// let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { -// x: 650, y: 500, width: 600, height: 600, title: "cat 2" -// }); -// let docset2 = new Array<Document>(doc4);//, doc1, doc3); -// let doc6 = Documents.CollectionDocument(docset2, { -// x: 350, y: 100, width: 600, height: 600, title: "docking collection" -// }); -// let mainNodes = mainContainer.GetOrCreate(KeyStore.Data, ListField); -// mainNodes.Data.push(doc6); -// mainNodes.Data.push(doc2); -// mainNodes.Data.push(doc4); -// mainNodes.Data.push(doc3); -// mainNodes.Data.push(doc5); -// mainNodes.Data.push(doc1); -//mainNodes.Data.push(doc2); -//mainNodes.Data.push(doc6); -// mainContainer.Set(KeyStore.Data, mainNodes); -//} -//); + ReactDOM.render(( + <div style={{ position: "absolute", width: "100%", height: "100%" }}> + <DocumentView Document={mainContainer} + AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity} + ContentScaling={() => 1} + PanelWidth={() => 0} + PanelHeight={() => 0} + isTopMost={true} + SelectOnLoad={false} + focus={() => { }} + ContainingCollectionView={undefined} /> + <DocumentDecorations /> + <ContextMenu /> + <div className="main-buttonDiv" style={{ bottom: '0px' }} ref={imgRef} > + <button onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>Add Image</button></div> + <div className="main-buttonDiv" style={{ bottom: '25px' }} ref={webRef} > + <button onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>Add Web</button></div> + <div className="main-buttonDiv" style={{ bottom: '50px' }} ref={textRef}> + <button onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>Add Text</button></div> + <div className="main-buttonDiv" style={{ bottom: '75px' }} ref={colRef}> + <button onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>Add Collection</button></div> + <div className="main-buttonDiv" style={{ bottom: '100px' }} ref={schemaRef}> + <button onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>Add Schema</button></div> + <div className="main-buttonDiv" style={{ bottom: '125px' }} > + <button onClick={clearDatabase}>Clear Database</button></div> + <button className="main-undoButtons" style={{ bottom: '25px' }} onClick={() => UndoManager.Undo()}>Undo</button> + <button className="main-undoButtons" style={{ bottom: '0px' }} onClick={() => UndoManager.Redo()}>Redo</button> + </div>), + document.getElementById('root')); +}) diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 5fb632469..c51fba908 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,16 +1,15 @@ import * as GoldenLayout from "golden-layout"; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, computed, observable, reaction } from "mobx"; +import { action, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; -import Measure from "react-measure"; import { Document } from "../../../fields/Document"; -import { FieldId, Opt, Field } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; +import Measure from "react-measure"; +import { FieldId, Opt, Field } from "../../../fields/Field"; import { Utils } from "../../../Utils"; import { Server } from "../../Server"; -import { DragManager } from "../../util/DragManager"; import { undoBatch } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; @@ -34,10 +33,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } private _goldenLayout: any = null; - private _dragDiv: any = null; - private _dragParent: HTMLElement | null = null; - private _dragElement: HTMLElement | undefined; - private _dragFakeElement: HTMLElement | undefined; private _containerRef = React.createRef<HTMLDivElement>(); private _fullScreen: any = null; @@ -47,28 +42,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp (window as any).React = React; (window as any).ReactDOM = ReactDOM; } - - public StartOtherDrag(dragElement: HTMLElement, dragDoc: Document) { - this._dragElement = dragElement; - this._dragParent = dragElement.parentElement; - // bcz: we want to copy this document into the header, not move it there. - // However, GoldenLayout is setup to move things, so we have to do some kludgy stuff: - - // - create a temporary invisible div and register that as a DragSource with GoldenLayout - this._dragDiv = document.createElement("div"); - this._dragDiv.style.opacity = 0; - DragManager.Root().appendChild(this._dragDiv); - this._goldenLayout.createDragSource(this._dragDiv, CollectionDockingView.makeDocumentConfig(dragDoc)); - - // - add our document to that div so that GoldenLayout will get the move events its listening for - this._dragDiv.appendChild(this._dragElement); - - // - add a duplicate of our document to the original document's container - // (GoldenLayout will be removing our original one) - this._dragFakeElement = dragElement.cloneNode(true) as HTMLElement; - this._dragParent!.appendChild(this._dragFakeElement); - - // all of this must be undone when the document has been dropped (see tabCreated) + public StartOtherDrag(dragDoc: Document, e: any) { + this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: e.button }) } @action @@ -98,7 +73,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // @action - public AddRightSplit(document: Document) { + public AddRightSplit(document: Document, minimize: boolean = false) { this._goldenLayout.emit('stateChanged'); let newItemStackConfig = { type: 'stack', @@ -121,10 +96,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp collayout.config["width"] = 50; newContentItem.config["width"] = 50; } + if (minimize) { + newContentItem.config["width"] = 10; + newContentItem.config["height"] = 10; + } newContentItem.callDownwards('_$init'); this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); this._goldenLayout.emit('stateChanged'); this.stateChanged(); + return newContentItem; } setupGoldenLayout() { @@ -218,13 +198,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this.stateChanged(); } tabCreated = (tab: any) => { - if (this._dragDiv) { - this._dragDiv.removeChild(this._dragElement); - this._dragParent!.removeChild(this._dragFakeElement!); - this._dragParent!.appendChild(this._dragElement!); - DragManager.Root().removeChild(this._dragDiv); - this._dragDiv = null; - } tab.closeElement.off('click') //unbind the current click handler .click(function () { tab.contentItem.remove(); @@ -245,7 +218,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp render() { return ( <div className="collectiondockingview-container" id="menuContainer" - onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} onContextMenu={(e) => e.preventDefault()} ref={this._containerRef} + onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} style={{ width: "100%", height: "100%", @@ -263,7 +236,7 @@ interface DockedFrameProps { @observer export class DockedFrameRenderer extends React.Component<DockedFrameProps> { - @observable private _mainCont = React.createRef<HTMLDivElement>(); + private _mainCont = React.createRef<HTMLDivElement>(); @observable private _panelWidth = 0; @observable private _panelHeight = 0; @observable private _document: Opt<Document>; @@ -295,6 +268,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { PanelHeight={this._nativeHeight} ScreenToLocalTransform={this.ScreenToLocalTransform} isTopMost={true} + SelectOnLoad={false} + focus={(doc: Document) => { }} ContainingCollectionView={undefined} /> </div> diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index f7b2e6449..f432e8cc3 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -1,15 +1,5 @@ .collectionfreeformview-container { - ::-webkit-scrollbar { - -webkit-appearance: none; - height:5px; - width:5px; - } - ::-webkit-scrollbar-thumb { - border-radius: 2px; - background-color: rgba(0,0,0,.5); - } - .collectionfreeformview > .jsx-parser{ position:absolute; height: 100%; diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 63ecec0df..f71f2791c 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -13,9 +13,10 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView } from "../collections/CollectionView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocumentView } from "../nodes/DocumentView"; -import { WebView } from "../nodes/WebView"; import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; +import { WebBox } from "../nodes/WebBox"; +import { KeyValueBox } from "../nodes/KeyValueBox" import "./CollectionFreeFormView.scss"; import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; @@ -28,6 +29,7 @@ export class CollectionFreeFormView extends CollectionViewBase { private _canvasRef = React.createRef<HTMLDivElement>(); private _lastX: number = 0; private _lastY: number = 0; + private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) @observable private _downX: number = 0; @@ -96,13 +98,12 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.props.active()) { - e.preventDefault(); e.stopPropagation(); let x = this.props.Document.GetNumber(KeyStore.PanX, 0); let y = this.props.Document.GetNumber(KeyStore.PanY, 0); - let [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); + let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this._previewCursorVisible = false; - this.SetPan(x + dx, y + dy); + this.SetPan(x - dx, y - dy); } this._lastX = e.pageX; this._lastY = e.pageY; @@ -133,11 +134,12 @@ export class CollectionFreeFormView extends CollectionViewBase { deltaScale = 1 / this.zoomScaling; let [x, y] = transform.transformPoint(e.clientX, e.clientY); - let localTransform = this.getLocalTransform(); + let localTransform = this.getLocalTransform() localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y) + console.log(localTransform) this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale); - this.SetPan(localTransform.TranslateX, localTransform.TranslateY); + this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale); } } @@ -161,14 +163,15 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onKeyDown = (e: React.KeyboardEvent<Element>) => { //if not these keys, make a textbox if preview cursor is active! - if (!e.ctrlKey && !e.altKey && !e.shiftKey) { + if (!e.ctrlKey && !e.altKey) { if (this._previewCursorVisible) { //make textbox and add it to this collection let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); (this._downX, this._downY); let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" }); + // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself + this._selectOnLoaded = newBox.Id; //set text to be the typed key and get focus on text box this.props.CollectionView.addDocument(newBox); - newBox.SetNumber(KeyStore.SelectOnLoaded, 1); //remove cursor from screen this._previewCursorVisible = false; } @@ -193,7 +196,6 @@ export class CollectionFreeFormView extends CollectionViewBase { }); } - @computed get backgroundLayout(): string | undefined { let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField); if (field && field !== "<Waiting>") { @@ -206,10 +208,18 @@ export class CollectionFreeFormView extends CollectionViewBase { return field.Data; } } + + focusDocument = (doc: Document) => { + let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2; + let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2; + this.SetPan(x, y); + this.props.focus(this.props.Document); + } + + @computed get views() { - const { fieldKey, Document } = this.props; - const lvalue = Document.GetT<ListField<Document>>(fieldKey, ListField); + const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField); if (lvalue && lvalue != FieldWaiting) { return lvalue.Data.map(doc => { return (<CollectionFreeFormDocumentView key={doc.Id} Document={doc} @@ -217,10 +227,13 @@ export class CollectionFreeFormView extends CollectionViewBase { RemoveDocument={this.props.removeDocument} ScreenToLocalTransform={this.getTransform} isTopMost={false} + SelectOnLoad={doc.Id === this._selectOnLoaded} ContentScaling={this.noScaling} PanelWidth={doc.Width} PanelHeight={doc.Height} - ContainingCollectionView={this.props.CollectionView} />); + ContainingCollectionView={this.props.CollectionView} + focus={this.focusDocument} + />); }) } return null; @@ -230,7 +243,7 @@ export class CollectionFreeFormView extends CollectionViewBase { get backgroundView() { return !this.backgroundLayout ? (null) : (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }} bindings={this.props.bindings} jsx={this.backgroundLayout} showWarnings={true} @@ -241,7 +254,7 @@ export class CollectionFreeFormView extends CollectionViewBase { get overlayView() { return !this.overlayLayout ? (null) : (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }} bindings={this.props.bindings} jsx={this.overlayLayout} showWarnings={true} @@ -249,13 +262,13 @@ export class CollectionFreeFormView extends CollectionViewBase { />); } - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH - this.centeringShiftX, -COLLECTION_BORDER_WIDTH - this.centeringShiftY).transform(this.getLocalTransform()) - getLocalTransform = (): Transform => Transform.Identity.translate(-this.panX, -this.panY).scale(1 / this.scale); + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) + getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; //when focus is lost, this will remove the preview cursor @action - onBlur = (e: React.FocusEvent<HTMLInputElement>): void => { + onBlur = (e: React.FocusEvent<HTMLDivElement>): void => { this._previewCursorVisible = false; } @@ -269,15 +282,16 @@ export class CollectionFreeFormView extends CollectionViewBase { cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", position: "absolute", transformOrigin: "left top", transform: `translate(${x}px, ${y}px)` }}>I</div> } - const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX; - const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY; + let [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; + + const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); + const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); return ( <div className="collectionfreeformview-container" onPointerDown={this.onPointerDown} onKeyPress={this.onKeyDown} onWheel={this.onPointerWheel} - onContextMenu={(e) => e.preventDefault()} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onBlur={this.onBlur} @@ -285,7 +299,7 @@ export class CollectionFreeFormView extends CollectionViewBase { tabIndex={0} ref={this.createDropTarget}> <div className="collectionfreeformview" - style={{ transformOrigin: "left top", transform: ` translate(${panx}px, ${pany}px) scale(${this.zoomScaling}, ${this.zoomScaling})` }} + style={{ transformOrigin: "left top", transform: `translate(${dx}px, ${dy}px) scale(${this.zoomScaling}, ${this.zoomScaling}) translate(${panx}px, ${pany}px)` }} ref={this._canvasRef}> {this.backgroundView} {cursor} diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 995d60f74..d40e6d314 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -6,15 +6,6 @@ position: absolute; width: 100%; height: 100%; - ::-webkit-scrollbar { - -webkit-appearance: none; - height:5px; - width:5px; - } - ::-webkit-scrollbar-thumb { - border-radius: 2px; - background-color: rgba(0,0,0,.5); - } .collectionSchemaView-previewRegion { position: relative; background: black; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 106a5c4f5..49f95c014 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -1,14 +1,15 @@ import React = require("react") -import { action, observable, trace } from "mobx"; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; import "react-table/react-table.css"; import { Document } from "../../../fields/Document"; -import { Field, FieldWaiting } from "../../../fields/Field"; +import { Field } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { CompileScript, ToField } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; +import { ContextMenu } from "../ContextMenu"; import { EditableView } from "../EditableView"; import { DocumentView } from "../nodes/DocumentView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; @@ -39,15 +40,16 @@ export class CollectionSchemaView extends CollectionViewBase { isSelected: () => false, select: () => { }, isTopMost: false, - bindings: {} + bindings: {}, + selectOnLoad: false, } let contents = ( <FieldView {...props} /> ) let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, props.doc); + let onItemDown = setupDrag(reference, () => props.doc); return ( - <div onPointerDown={onItemDown} ref={reference}> + <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}> <EditableView contents={contents} height={36} GetValue={() => { let field = props.doc.Get(props.fieldKey); @@ -57,7 +59,7 @@ export class CollectionSchemaView extends CollectionViewBase { return field || ""; }} SetValue={(value: string) => { - let script = CompileScript(value); + let script = CompileScript(value, undefined, true); if (!script.compiled) { return false; } @@ -174,6 +176,8 @@ export class CollectionSchemaView extends CollectionViewBase { return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); } + focusDocument = (doc: Document) => { } + render() { const columns = this.props.Document.GetList(KeyStore.ColumnsKey, [KeyStore.Title, KeyStore.Data, KeyStore.Author]) const children = this.props.Document.GetList<Document>(this.props.fieldKey, []); @@ -185,11 +189,14 @@ export class CollectionSchemaView extends CollectionViewBase { <DocumentView Document={selected} AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument} isTopMost={false} + SelectOnLoad={false} ScreenToLocalTransform={this.getTransform} ContentScaling={this.getContentScaling} PanelWidth={this.getPanelWidth} PanelHeight={this.getPanelHeight} - ContainingCollectionView={this.props.CollectionView} /> + ContainingCollectionView={this.props.CollectionView} + focus={this.focusDocument} + /> </div> } </Measure> diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index c488e2894..f8d580a7b 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,3 +1,8 @@ +#body { + padding: 20px; + background: #bbbbbb; +} + ul { list-style: none; } @@ -10,25 +15,23 @@ li { padding-left: 0; } -/* ALL THESE SPACINGS ARE SUPER HACKY RIGHT NOW HANNAH PLS HELP */ - -li:before { - content: '\2014'; - margin-right: 0.7em; +.bullet { + width: 1.5em; + display: inline-block; } -.collapsed:before { - content: '\25b6'; - margin-right: 0.65em; +.collectionTreeView-dropTarget { + border-style: solid; + box-sizing: border-box; + height: 100%; } -.uncollapsed:before { - content: '\25bc'; - margin-right: 0.5em; +.docContainer { + display: inline-table; } -.collectionTreeView-dropTarget { - border-style: solid; - box-sizing: border-box; - height:100%; +.delete-button { + color: #999999; + float: right; + margin-left: 1em; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 64f4c0970..8b06d9ac4 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -7,12 +7,20 @@ import React = require("react") import { TextField } from "../../../fields/TextField"; import { observable, action } from "mobx"; import "./CollectionTreeView.scss"; +import { EditableView } from "../EditableView"; import { setupDrag } from "../../util/DragManager"; import { FieldWaiting } from "../../../fields/Field"; import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; export interface TreeViewProps { document: Document; + deleteDoc: (doc: Document) => void; +} + +export enum BulletType { + Collapsed, + Collapsible, + List } @observer @@ -24,41 +32,101 @@ class TreeView extends React.Component<TreeViewProps> { @observable collapsed: boolean = false; + delete = () => { + this.props.deleteDoc(this.props.document); + } + + + @action + remove = (document: Document) => { + var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); + if (children && children !== FieldWaiting) { + children.Data.splice(children.Data.indexOf(document), 1); + } + } + + renderBullet(type: BulletType) { + let onClicked = action(() => this.collapsed = !this.collapsed); + + switch (type) { + case BulletType.Collapsed: + return <div className="bullet" onClick={onClicked}>▶</div> + case BulletType.Collapsible: + return <div className="bullet" onClick={onClicked}>▼</div> + case BulletType.List: + return <div className="bullet">—</div> + } + } + /** - * Renders a single child document. If this child is a collection, it will call renderTreeView again. Otherwise, it will just append a list element. - * @param childDocument The document to render. + * Renders the EditableView title element for placement into the tree. */ - renderChild(childDocument: Document) { + renderTitle() { + let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField); + + // if the title hasn't loaded, immediately return the div + if (!title || title === "<Waiting>") { + return <div key={this.props.document.Id}></div>; + } + + return <div className="docContainer"> <EditableView contents={title.Data} + height={36} GetValue={() => { + let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField); + if (title && title !== "<Waiting>") + return title.Data; + return ""; + }} SetValue={(value: string) => { + this.props.document.SetData(KeyStore.Title, value, TextField); + return true; + }} /> + <div className="delete-button" onClick={this.delete}>x</div> + </div > + } + + render() { + var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); + let reference = React.createRef<HTMLDivElement>(); + let onItemDown = setupDrag(reference, () => this.props.document); + let titleElement = this.renderTitle(); - var children = childDocument.GetT<ListField<Document>>(KeyStore.Data, ListField); - let title = childDocument.GetT<TextField>(KeyStore.Title, TextField); - let onItemDown = setupDrag(reference, childDocument); + // check if this document is a collection + if (children && children !== FieldWaiting) { + let subView; - if (title && title != FieldWaiting) { - let subView = !children || this.collapsed || children === FieldWaiting ? (null) : - <ul> - <TreeView document={childDocument} /> - </ul>; - return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}> - <li className={!children ? "leaf" : this.collapsed ? "collapsed" : "uncollapsed"} - onClick={action(() => this.collapsed = !this.collapsed)} > - {title.Data} - {subView} + // if uncollapsed, then add the children elements + if (!this.collapsed) { + // render all children elements + let childrenElement = (children.Data.map(value => + <TreeView document={value} deleteDoc={this.remove} />) + ) + subView = + <li key={this.props.document.Id} > + {this.renderBullet(BulletType.Collapsible)} + {titleElement} + <ul key={this.props.document.Id}> + {childrenElement} + </ul> + </li> + } else { + subView = <li key={this.props.document.Id}> + {this.renderBullet(BulletType.Collapsed)} + {titleElement} </li> + } + + return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}> + {subView} </div> } - return (null); - } - render() { - var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); - return !children || children === FieldWaiting ? (null) : - (children.Data.map(value => - <div key={value.Id}> - {this.renderChild(value)} - </div>) - ) + // otherwise this is a normal leaf node + else { + return <li key={this.props.document.Id}> + {this.renderBullet(BulletType.List)} + {titleElement} + </li>; + } } } @@ -66,21 +134,42 @@ class TreeView extends React.Component<TreeViewProps> { @observer export class CollectionTreeView extends CollectionViewBase { + @action + remove = (document: Document) => { + var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); + if (children && children !== FieldWaiting) { + children.Data.splice(children.Data.indexOf(document), 1); + } + } + render() { let titleStr = ""; let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField); if (title && title !== FieldWaiting) { titleStr = title.Data; } + + var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); + let childrenElement = !children || children === FieldWaiting ? (null) : + (children.Data.map(value => + <TreeView document={value} key={value.Id} deleteDoc={this.remove} />) + ) + return ( - <div className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} > - <h3>{titleStr}</h3> + <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}> + <h3> + <EditableView contents={titleStr} + height={72} GetValue={() => { + return this.props.Document.Title; + }} SetValue={(value: string) => { + this.props.Document.SetData(KeyStore.Title, value, TextField); + return true; + }} /> + </h3> <ul className="no-indent"> - <TreeView - document={this.props.Document} - /> + {childrenElement} </ul> - </div> + </div > ); } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 35ac48177..31824763d 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from "mobx"; +import { action } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { ListField } from "../../../fields/ListField"; @@ -30,7 +30,7 @@ export class CollectionView extends React.Component<CollectionViewProps> { public static LayoutString(fieldKey: string = "DataKey") { return `<CollectionView Document={Document} ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} BackgroundView={BackgroundView} />`; + isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; } public active = () => { var isSelected = this.props.isSelected(); @@ -49,6 +49,7 @@ export class CollectionView extends React.Component<CollectionViewProps> { } } + @action removeDocument = (doc: Document): boolean => { //TODO This won't create the field if it doesn't already exist @@ -60,6 +61,7 @@ export class CollectionView extends React.Component<CollectionViewProps> { break; } } + if (index !== -1) { value.splice(index, 1) @@ -87,29 +89,45 @@ export class CollectionView extends React.Component<CollectionViewProps> { Document.SetData(KeyStore.ViewType, type, NumberField); } + specificContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped) { // 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: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) }) + ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) }) + ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) }) + ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) + } + } render() { let viewType = this.collectionViewType; - + let subView: JSX.Element; switch (viewType) { case CollectionViewType.Freeform: - return (<CollectionFreeFormView {...this.props} + subView = (<CollectionFreeFormView {...this.props} addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active} - CollectionView={this} />); + CollectionView={this} />) + break; case CollectionViewType.Schema: - return (<CollectionSchemaView {...this.props} + subView = (<CollectionSchemaView {...this.props} addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active} CollectionView={this} />) + break; case CollectionViewType.Docking: - return (<CollectionDockingView {...this.props} + subView = (<CollectionDockingView {...this.props} addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active} CollectionView={this} />) + break; case CollectionViewType.Tree: - return (<CollectionTreeView {...this.props} + subView = (<CollectionTreeView {...this.props} addDocument={this.addDocument} removeDocument={this.removeDocument} active={this.active} CollectionView={this} />) + break; default: - return <div></div> + subView = <div></div> + break; } + return (<div onContextMenu={this.specificContextMenu}> + {subView} + </div>) } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 217536e2b..0a3b965f2 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -1,16 +1,16 @@ -import { action, computed } from "mobx"; +import { action } from "mobx"; import { Document } from "../../../fields/Document"; import { ListField } from "../../../fields/ListField"; import React = require("react"); import { KeyStore } from "../../../fields/KeyStore"; -import { Opt, FieldWaiting } from "../../../fields/Field"; +import { FieldWaiting } from "../../../fields/Field"; import { undoBatch } from "../../util/UndoManager"; import { DragManager } from "../../util/DragManager"; import { DocumentView } from "../nodes/DocumentView"; import { Documents, DocumentOptions } from "../../documents/Documents"; import { Key } from "../../../fields/Key"; import { Transform } from "../../util/Transform"; - +import { CollectionView } from "./CollectionView"; export interface CollectionViewProps { fieldKey: Key; @@ -22,12 +22,13 @@ export interface CollectionViewProps { bindings: any; panelWidth: () => number; panelHeight: () => number; + focus: (doc: Document) => void; } export interface SubCollectionViewProps extends CollectionViewProps { active: () => boolean; addDocument: (doc: Document) => void; removeDocument: (doc: Document) => boolean; - CollectionView: any; + CollectionView: CollectionView; } export class CollectionViewBase extends React.Component<SubCollectionViewProps> { @@ -67,7 +68,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> let html = e.dataTransfer.getData("text/html"); let text = e.dataTransfer.getData("text/plain"); if (html && html.indexOf("<img") != 0) { - let htmlDoc = Documents.HtmlDocument(html, { ...options }); + let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 }); htmlDoc.SetText(KeyStore.DocumentText, text); this.props.addDocument(htmlDoc); return; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 50dc9ddc1..e01e1d4cd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -12,26 +12,29 @@ import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; -import { WebView } from "./WebView"; import { ContextMenu } from "../ContextMenu"; import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; +import { Documents } from "../../documents/Documents" +import { KeyValueBox } from "./KeyValueBox" +import { WebBox } from "../nodes/WebBox"; import "./DocumentView.scss"; import React = require("react"); -const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? +const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? + export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView>; - Document: Document; AddDocument?: (doc: Document) => void; RemoveDocument?: (doc: Document) => boolean; ScreenToLocalTransform: () => Transform; isTopMost: boolean; - //tfs: This shouldn't be necessary I don't think ContentScaling: () => number; PanelWidth: () => number; PanelHeight: () => number; + focus: (doc: Document) => void; + SelectOnLoad: boolean; } export interface JsxArgs extends DocumentViewProps { Keys: { [name: string]: Key } @@ -77,29 +80,23 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { @observer export class DocumentView extends React.Component<DocumentViewProps> { - private _mainCont = React.createRef<HTMLDivElement>(); private _documentBindings: any = null; - private _contextMenuCanOpen = false; private _downX: number = 0; private _downY: number = 0; - @computed get active(): boolean { return SelectionManager.IsSelected(this) || !this.props.ContainingCollectionView || this.props.ContainingCollectionView.active(); } @computed get topMost(): boolean { return !this.props.ContainingCollectionView || this.props.ContainingCollectionView.collectionViewType == CollectionViewType.Docking; } @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); } @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); } @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); } - 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.shiftKey && e.buttons === 1) { - CollectionDockingView.Instance.StartOtherDrag(this._mainCont.current!, this.props.Document); + CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e); e.stopPropagation(); } else { - this._contextMenuCanOpen = true; if (this.active && !e.isDefaultPrevented()) { e.stopPropagation(); if (e.buttons === 2) { @@ -112,16 +109,14 @@ export class DocumentView extends React.Component<DocumentViewProps> { } } } - onPointerMove = (e: PointerEvent): void => { if (e.cancelBubble) { - this._contextMenuCanOpen = false; return; } if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { - this._contextMenuCanOpen = false; + document.removeEventListener("pointermove", this.onPointerMove) + document.removeEventListener("pointerup", this.onPointerUp) if (this._mainCont.current != null && !this.topMost) { - this._contextMenuCanOpen = false; const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); let dragData: { [id: string]: any } = {}; dragData["documentView"] = this; @@ -129,7 +124,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { dragData["yOffset"] = e.y - top; DragManager.StartDrag(this._mainCont.current, dragData, { handlers: { - dragComplete: action((e: DragManager.DragCompleteEvent) => { }), + dragComplete: action(() => { }), }, hideSource: true }) @@ -138,7 +133,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { e.stopPropagation(); e.preventDefault(); } - onPointerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onPointerMove) document.removeEventListener("pointerup", this.onPointerUp) @@ -148,17 +142,24 @@ export class DocumentView extends React.Component<DocumentViewProps> { } } - deleteClicked = (e: React.MouseEvent): void => { + deleteClicked = (): void => { if (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)); + } + } fullScreenClicked = (e: React.MouseEvent): void => { CollectionDockingView.Instance.OpenFullScreen(this.props.Document); ContextMenu.Instance.clearItems(); 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(); @@ -168,18 +169,18 @@ export class DocumentView extends React.Component<DocumentViewProps> { @action onContextMenu = (e: React.MouseEvent): void => { - e.preventDefault() e.stopPropagation(); - if (!SelectionManager.IsSelected(this) || !this._contextMenuCanOpen) { + let moved = Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3; + if (moved || e.isDefaultPrevented()) { + e.preventDefault() return; } + e.preventDefault() - ContextMenu.Instance.clearItems() 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: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) }) - ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) }) - ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) }) //ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) }) ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) if (!this.topMost) { @@ -191,11 +192,9 @@ export class DocumentView extends React.Component<DocumentViewProps> { ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) SelectionManager.SelectDoc(this, e.ctrlKey); } - @computed get mainContent() { - var val = this.props.Document.Id; return <JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox }} bindings={this._documentBindings} jsx={this.layout} showWarnings={true} @@ -203,27 +202,34 @@ export class DocumentView extends React.Component<DocumentViewProps> { /> } + isSelected = () => { + return SelectionManager.IsSelected(this); + } + + select = (ctrlPressed: boolean) => { + SelectionManager.SelectDoc(this, ctrlPressed) + } + render() { - if (!this.props.Document) - return <div></div> + if (!this.props.Document) return <div></div> let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField); if (!lkeys || lkeys === "<Waiting>") { return <p>Error loading layout keys</p>; } this._documentBindings = { ...this.props, - isSelected: () => SelectionManager.IsSelected(this), - select: (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed) + isSelected: this.isSelected, + select: this.select, + focus: this.props.focus }; for (const key of this.layoutKeys) { - this._documentBindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data + this._documentBindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data } for (const key of this.layoutFields) { let field = this.props.Document.Get(key); this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field; } this._documentBindings.bindings = this._documentBindings; - var scaling = this.props.ContentScaling(); var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); @@ -233,13 +239,12 @@ export class DocumentView extends React.Component<DocumentViewProps> { width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%", height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%", transformOrigin: "left top", - transform: `scale(${scaling},${scaling})` + transform: `scale(${scaling} , ${scaling})` }} onContextMenu={this.onContextMenu} - onPointerDown={this.onPointerDown} - > + onPointerDown={this.onPointerDown} > {this.mainContent} </div> ) } -} +}
\ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index b71309bf5..9e63006d1 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -1,17 +1,17 @@ import React = require("react") import { observer } from "mobx-react"; import { computed } from "mobx"; -import { Field, Opt, FieldWaiting, FieldValue } from "../../../fields/Field"; +import { Field, FieldWaiting, FieldValue } from "../../../fields/Field"; import { Document } from "../../../fields/Document"; import { TextField } from "../../../fields/TextField"; import { NumberField } from "../../../fields/NumberField"; import { RichTextField } from "../../../fields/RichTextField"; import { ImageField } from "../../../fields/ImageField"; +import { WebField } from "../../../fields/WebField"; import { Key } from "../../../fields/Key"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; -import { HtmlField } from "../../../fields/HtmlField"; -import { WebView } from "./WebView"; +import { WebBox } from "./WebBox"; // // these properties get assigned through the render() method of the DocumentView when it creates this node. @@ -24,12 +24,15 @@ export interface FieldViewProps { isSelected: () => boolean; select: () => void; isTopMost: boolean; + selectOnLoad: boolean; bindings: any; } @observer export class FieldView extends React.Component<FieldViewProps> { - public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") { return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} isTopMost={isTopMost} />`; } + public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") { + return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost} />`; + } @computed get field(): FieldValue<Field> { @@ -50,12 +53,16 @@ export class FieldView extends React.Component<FieldViewProps> { else if (field instanceof ImageField) { return <ImageBox {...this.props} /> } + else if (field instanceof WebField) { + return <WebBox {...this.props} /> + } + // bcz: this belongs here, but it doesn't render well so taking it out for now + // else if (field instanceof HtmlField) { + // return <WebBox {...this.props} /> + // } else if (field instanceof NumberField) { return <p>{field.Data}</p> } - else if (field instanceof HtmlField) { - return <WebView {...this.props} /> - } else if (field != FieldWaiting) { return <p>{JSON.stringify(field.GetValue())}</p> } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index a0f662f18..a6cee9957 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -5,15 +5,15 @@ import { keymap } from "prosemirror-keymap"; import { schema } from "../../util/RichTextSchema"; import { EditorState, Transaction, } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Opt, FieldWaiting, FieldValue } from "../../../fields/Field"; +import { Opt, FieldWaiting } from "../../../fields/Field"; import "./FormattedTextBox.scss"; -import { KeyStore } from "../../../fields/KeyStore"; import React = require("react") import { RichTextField } from "../../../fields/RichTextField"; import { FieldViewProps, FieldView } from "./FieldView"; import { Plugin } from 'prosemirror-state' import { Decoration, DecorationSet } from 'prosemirror-view' import { TooltipTextMenu } from "../../util/TooltipTextMenu" +import { ContextMenu } from "../../views/ContextMenu"; @@ -45,7 +45,6 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { super(props); this._ref = React.createRef(); - this.onChange = this.onChange.bind(this); } @@ -53,16 +52,12 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - const { doc, fieldKey } = this.props; - doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + this.props.doc.SetData(this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField); } } componentDidMount() { let state: EditorState; - const { doc, fieldKey } = this.props; - - // let mySchema = new Schema({ nodes, marks }) const config = { schema, plugins: [ @@ -71,11 +66,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { keymap(baseKeymap), this.tooltipMenuPlugin() ] - }; - let field = doc.GetT(fieldKey, RichTextField); - if (field && field != FieldWaiting) { // bcz: don't think this works + let field = this.props.doc.GetT(this.props.fieldKey, RichTextField); + if (field && field != FieldWaiting) { state = EditorState.fromJSON(config, JSON.parse(field.Data)); } else { state = EditorState.create(config); @@ -95,9 +89,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))); } }) - - //if tagged to be selected when created, then select & focus - if (this.props.doc.GetNumber(KeyStore.SelectOnLoaded, 0)) { + if (this.props.selectOnLoad) { this.props.select(); this._editorView!.focus(); } @@ -118,15 +110,35 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { @action onChange(e: React.ChangeEvent<HTMLInputElement>) { - const { fieldKey, doc } = this.props; - doc.SetData(fieldKey, e.target.value, RichTextField); + this.props.doc.SetData(this.props.fieldKey, e.target.value, RichTextField); } onPointerDown = (e: React.PointerEvent): void => { - let me = this; if (e.buttons === 1 && this.props.isSelected()) { e.stopPropagation(); } } + + //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE + textCapability = (e: React.MouseEvent): void => { + } + + specificContextMenu = (e: React.MouseEvent): void => { + ContextMenu.Instance.addItem({ description: "Text Capability", event: this.textCapability }); + // ContextMenu.Instance.addItem({ + // description: "Submenu", + // items: [ + // { + // description: "item 1", event: + // }, + // { + // description: "item 2", event: + // } + // ] + // }) + // e.stopPropagation() + + } + onPointerWheel = (e: React.WheelEvent): void => { e.stopPropagation(); } @@ -142,6 +154,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { render() { return (<div className="formattedTextBox-cont" onPointerDown={this.onPointerDown} + onContextMenu={this.specificContextMenu} onWheel={this.onPointerWheel} ref={this._ref} />) } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4fe73fb8d..8c44395f4 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -7,9 +7,9 @@ import { ImageField } from '../../../fields/ImageField'; import { FieldViewProps, FieldView } from './FieldView'; import { FieldWaiting } from '../../../fields/Field'; import { observer } from "mobx-react" +import { ContextMenu } from "../../views/ContextMenu"; import { observable, action } from 'mobx'; import { KeyStore } from '../../../fields/KeyStore'; -import { element } from 'prop-types'; @observer export class ImageBox extends React.Component<FieldViewProps> { @@ -89,13 +89,21 @@ export class ImageBox extends React.Component<FieldViewProps> { } } + //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE + imageCapability = (e: React.MouseEvent): void => { + } + + specificContextMenu = (e: React.MouseEvent): void => { + ContextMenu.Instance.addItem({ description: "Image Capability", event: this.imageCapability }); + } + render() { let field = this.props.doc.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 nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1); return ( - <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} > + <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>) diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss new file mode 100644 index 000000000..1295266e5 --- /dev/null +++ b/src/client/views/nodes/KeyValueBox.scss @@ -0,0 +1,31 @@ +.keyValueBox-cont { + overflow-y:scroll; + height: 100%; + border: black; + border-width: 1px; + border-style: solid; + box-sizing: border-box; + display: inline-block; + .imageBox-cont img { + max-height:45px; + height: auto; + } +} +.keyValueBox-table { + position: relative; +} +.keyValueBox-header { + background:gray; +} +.keyValueBox-evenRow { + background: white; + .formattedTextBox-cont { + background: white; + } +} +.keyValueBox-oddRow { + background: lightGray; + .formattedTextBox-cont { + background: lightgray; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx new file mode 100644 index 000000000..e8ebd50be --- /dev/null +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -0,0 +1,85 @@ + +import { IReactionDisposer } from 'mobx'; +import { observer } from "mobx-react"; +import { EditorView } from 'prosemirror-view'; +import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app +import { Document } from '../../../fields/Document'; +import { Opt, FieldWaiting } from '../../../fields/Field'; +import { KeyStore } from '../../../fields/KeyStore'; +import { FieldView, FieldViewProps } from './FieldView'; +import { KeyValuePair } from "./KeyValuePair"; +import "./KeyValueBox.scss"; +import React = require("react") + +@observer +export class KeyValueBox extends React.Component<FieldViewProps> { + + public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr) } + private _ref: React.RefObject<HTMLDivElement>; + private _editorView: Opt<EditorView>; + private _reactionDisposer: Opt<IReactionDisposer>; + + + constructor(props: FieldViewProps) { + super(props); + + this._ref = React.createRef(); + } + + + + shouldComponentUpdate() { + return false; + } + + + onPointerDown = (e: React.PointerEvent): void => { + if (e.buttons === 1 && this.props.isSelected()) { + e.stopPropagation(); + } + } + onPointerWheel = (e: React.WheelEvent): void => { + e.stopPropagation(); + } + + createTable = () => { + let doc = this.props.doc.GetT(KeyStore.Data, Document); + if (!doc || doc == FieldWaiting) { + return <tr><td>Loading...</td></tr> + } + let realDoc = doc; + + let ids: { [key: string]: string } = {}; + let protos = doc.GetAllPrototypes(); + for (const proto of protos) { + proto._proxies.forEach((val, key) => { + if (!(key in ids)) { + ids[key] = key; + } + }) + } + + let rows: JSX.Element[] = []; + let i = 0; + for (let key in ids) { + rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />) + } + return rows; + } + + + render() { + + return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}> + <table className="keyValueBox-table"> + <tbody> + <tr className="keyValueBox-header"> + <th>Key</th> + <th>Fields</th> + </tr> + {this.createTable()} + </tbody> + </table> + </div>) + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx new file mode 100644 index 000000000..a97e98313 --- /dev/null +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -0,0 +1,58 @@ +import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app +import "./KeyValueBox.scss"; +import React = require("react") +import { FieldViewProps, FieldView } from './FieldView'; +import { Opt, Field } from '../../../fields/Field'; +import { observer } from "mobx-react" +import { observable, action } from 'mobx'; +import { Document } from '../../../fields/Document'; +import { Key } from '../../../fields/Key'; +import { Server } from "../../Server" + +// Represents one row in a key value plane + +export interface KeyValuePairProps { + rowStyle: string; + fieldId: string; + doc: Document; +} +@observer +export class KeyValuePair extends React.Component<KeyValuePairProps> { + + @observable + private key: Opt<Key> + + constructor(props: KeyValuePairProps) { + super(props); + Server.GetField(this.props.fieldId, + action((field: Opt<Field>) => { + if (field) { + this.key = field as Key; + } + })); + + } + + + render() { + if (!this.key) { + return <tr><td>error</td><td></td></tr> + + } + let props: FieldViewProps = { + doc: this.props.doc, + fieldKey: this.key, + isSelected: () => false, + select: () => { }, + isTopMost: false, + bindings: {}, + selectOnLoad: false, + } + return ( + <tr className={this.props.rowStyle}> + <td>{this.key.Name}</td> + <td><FieldView {...props} /></td> + </tr> + ) + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss new file mode 100644 index 000000000..e72b3c4da --- /dev/null +++ b/src/client/views/nodes/WebBox.scss @@ -0,0 +1,14 @@ + +.webBox-cont { + padding: 0vw; + position: absolute; + width: 100%; + height: 100%; +} + +.webBox-button { + padding : 0vw; + border: none; + width : 100%; + height: 100%; +}
\ No newline at end of file diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx new file mode 100644 index 000000000..2ca8d49ce --- /dev/null +++ b/src/client/views/nodes/WebBox.tsx @@ -0,0 +1,38 @@ +import "./WebBox.scss"; +import React = require("react") +import { WebField } from '../../../fields/WebField'; +import { FieldViewProps, FieldView } from './FieldView'; +import { FieldWaiting } from '../../../fields/Field'; +import { observer } from "mobx-react" +import { computed } from 'mobx'; +import { KeyStore } from '../../../fields/KeyStore'; + +@observer +export class WebBox extends React.Component<FieldViewProps> { + + public static LayoutString() { return FieldView.LayoutString(WebBox); } + + constructor(props: FieldViewProps) { + super(props); + } + + @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); } + + render() { + let field = this.props.doc.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" }} />} + </div>; + + return ( + <div className="webBox-cont" > + {content} + </div>) + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/WebView.tsx b/src/client/views/nodes/WebView.tsx deleted file mode 100644 index 717aa8bf5..000000000 --- a/src/client/views/nodes/WebView.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { FieldViewProps, FieldView } from "./FieldView"; -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import { KeyStore } from "../../../fields/KeyStore"; -import React = require('react') -import { TextField } from "../../../fields/TextField"; -import { HtmlField } from "../../../fields/HtmlField"; -import { RichTextField } from "../../../fields/RichTextField"; - -@observer -export class WebView extends React.Component<FieldViewProps> { - public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(WebView, fieldStr) } - - @computed - get html(): string { - return this.props.doc.GetData(KeyStore.Data, HtmlField, "" as string); - } - - render() { - return <span dangerouslySetInnerHTML={{ __html: this.html }}></span> - } -}
\ No newline at end of file |
