diff options
| author | tschicke-brown <tyler_schicke@brown.edu> | 2019-04-08 21:59:41 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-04-08 21:59:41 -0400 |
| commit | fbb5f2c19ffd1278d31b432a74ec3ae747b85894 (patch) | |
| tree | b6a4255bd3a0a3c30504639f3e0e7a48ebadd420 /src/client/views/collections | |
| parent | a2135bcc0a995378aad0e71ade832a4d526a37f0 (diff) | |
| parent | e963f324fe8436ff5fc8ee21cdf091b6265690f9 (diff) | |
Merge pull request #81 from browngraphicslab/propsRefactor
Props refactor
Diffstat (limited to 'src/client/views/collections')
13 files changed, 437 insertions, 381 deletions
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx new file mode 100644 index 000000000..7a5ab6e3c --- /dev/null +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -0,0 +1,166 @@ +import * as React from 'react'; +import { FieldViewProps } from '../nodes/FieldView'; +import { KeyStore } from '../../../fields/KeyStore'; +import { NumberField } from '../../../fields/NumberField'; +import { FieldWaiting, Field, FieldValue } from '../../../fields/Field'; +import { ContextMenu } from '../ContextMenu'; +import { SelectionManager } from '../../util/SelectionManager'; +import { Document } from '../../../fields/Document'; +import { ListField } from '../../../fields/ListField'; +import { action } from 'mobx'; +import { Transform } from '../../util/Transform'; +import { observer } from 'mobx-react'; + +export enum CollectionViewType { + Invalid, + Freeform, + Schema, + Docking, + Tree, +} + +export interface CollectionRenderProps { + addDocument: (document: Document, allowDuplicates?: boolean) => boolean; + removeDocument: (document: Document) => boolean; + moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; + active: () => boolean; + onActiveChanged: (isActive: boolean) => void; +} + +export interface CollectionViewProps extends FieldViewProps { + onContextMenu?: (e: React.MouseEvent) => void; + children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null; + className?: string; + contentRef?: React.Ref<HTMLDivElement>; +} + +export const COLLECTION_BORDER_WIDTH = 1; + +@observer +export class CollectionBaseView extends React.Component<CollectionViewProps> { + get collectionViewType(): CollectionViewType { + let Document = this.props.Document; + let viewField = Document.GetT(KeyStore.ViewType, NumberField); + if (viewField === FieldWaiting) { + return CollectionViewType.Invalid; + } else if (viewField) { + return viewField.Data; + } else { + return CollectionViewType.Freeform; + } + } + + active = (): boolean => { + var isSelected = this.props.isSelected(); + var topMost = this.props.isTopMost; + return isSelected || this._isChildActive || topMost; + } + + //TODO should this be observable? + private _isChildActive = false; + onActiveChanged = (isActive: boolean) => { + this._isChildActive = isActive; + this.props.onActiveChanged(isActive); + } + + createsCycle(documentToAdd: Document, containerDocument: Document): boolean { + let data = documentToAdd.GetList<Document>(KeyStore.Data, []); + for (const doc of data) { + if (this.createsCycle(doc, containerDocument)) + return true; + } + let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []); + for (const annot of annots) { + if (this.createsCycle(annot, containerDocument)) + return true; + } + for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) { + if (containerProto.Id === documentToAdd.Id) + return true; + } + return false; + } + + @action.bound + addDocument(doc: Document, allowDuplicates: boolean = false): boolean { + let props = this.props; + var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); + doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); + if (curPage >= 0) { + doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document); + } + if (props.Document.Get(props.fieldKey) instanceof Field) { + //TODO This won't create the field if it doesn't already exist + const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) + if (!this.createsCycle(doc, props.Document)) { + if (!value.some(v => v.Id === doc.Id) || allowDuplicates) + value.push(doc); + } + else + return false; + } else { + let proto = props.Document.GetPrototype(); + if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) { + props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); + } + else + return false; + } + return true; + } + + @action.bound + removeDocument(doc: Document): boolean { + const props = this.props; + //TODO This won't create the field if it doesn't already exist + const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) + let index = -1; + for (let i = 0; i < value.length; i++) { + if (value[i].Id === doc.Id) { + index = i; + break; + } + } + doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => { + if (annotationOn === props.Document) { + doc.Set(KeyStore.AnnotationOn, undefined, true); + } + }) + + if (index !== -1) { + value.splice(index, 1) + + // SelectionManager.DeselectAll() + ContextMenu.Instance.clearItems() + return true; + } + return false + } + + @action.bound + moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => boolean): boolean { + if (this.props.Document === targetCollection) { + return true; + } + if (this.removeDocument(doc)) { + return addDocument(doc); + } + return false; + } + + render() { + const props: CollectionRenderProps = { + addDocument: this.addDocument, + removeDocument: this.removeDocument, + moveDocument: this.moveDocument, + active: this.active, + onActiveChanged: this.onActiveChanged, + } + return ( + <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}> + {this.props.children(this.collectionViewType, props)} + </div> + ) + } + +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index d453bfef3..c5e1e7f4b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -8,12 +8,12 @@ import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import Measure from "react-measure"; import { FieldId, Opt, Field } from "../../../fields/Field"; -import { Utils } from "../../../Utils"; +import { Utils, returnTrue, emptyFunction } from "../../../Utils"; import { Server } from "../../Server"; import { undoBatch } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; import React = require("react"); import { SubCollectionViewProps } from "./CollectionViewBase"; import { ServerUtils } from "../../../server/ServerUtil"; @@ -101,12 +101,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp newRow.addChild(newContentItem, undefined, true); newRow.addChild(collayout, 0, true); - collayout.config["width"] = 50; - newContentItem.config["width"] = 50; + collayout.config.width = 50; + newContentItem.config.width = 50; } if (minimize) { - newContentItem.config["width"] = 10; - newContentItem.config["height"] = 10; + newContentItem.config.width = 10; + newContentItem.config.height = 10; } newContentItem.callDownwards('_$init'); this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); @@ -124,7 +124,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this._goldenLayout = new GoldenLayout(JSON.parse(config)); } else { - if (config == JSON.stringify(this._goldenLayout.toConfig())) + if (config === JSON.stringify(this._goldenLayout.toConfig())) return; try { this._goldenLayout.unbind('itemDropped', this.itemDropped); @@ -154,7 +154,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp reaction( () => this.props.Document.GetText(KeyStore.Data, ""), () => { - if (!this._goldenLayout || this._ignoreStateChange != JSON.stringify(this._goldenLayout.toConfig())) { + if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) { setTimeout(() => this.setupGoldenLayout(), 1); } this._ignoreStateChange = ""; @@ -193,7 +193,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp @action onPointerDown = (e: React.PointerEvent): void => { var className = (e.target as any).className; - if ((className == "lm_title" || className == "lm_tab lm_active") && (e.ctrlKey || e.altKey)) { + if ((className === "lm_title" || className === "lm_tab lm_active") && (e.ctrlKey || e.altKey)) { e.stopPropagation(); e.preventDefault(); let docid = (e.target as any).DashDocId; @@ -209,7 +209,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp }) })); } - if (className == "lm_drag_handle" || className == "lm_close" || className == "lm_maximise" || className == "lm_minimise" || className == "lm_close_tab") { + if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") { this._flush = true; } if (this.props.active()) { @@ -228,12 +228,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } tabCreated = (tab: any) => { - if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type != "stack") { - if (tab.titleElement[0].textContent.indexOf("-waiting") != -1) { + if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { + if (tab.titleElement[0].textContent.indexOf("-waiting") !== -1) { Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => { - if (f != undefined && f instanceof Document) { + if (f !== undefined && f instanceof Document) { f.GetTAsync(KeyStore.Title, TextField, (tfield) => { - if (tfield != undefined) { + if (tfield !== undefined) { tab.titleElement[0].textContent = f.Title; } }) @@ -297,9 +297,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document)); } - private _nativeWidth = () => { return this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); } - private _nativeHeight = () => { return this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); } - private _contentScaling = () => { return this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); } + private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth) + private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight) + private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth) ScreenToLocalTransform = () => { let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!); @@ -312,14 +312,16 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { var content = <div className="collectionDockingView-content" ref={this._mainCont}> <DocumentView key={this._document.Id} Document={this._document} - AddDocument={undefined} - RemoveDocument={undefined} + addDocument={undefined} + removeDocument={undefined} ContentScaling={this._contentScaling} PanelWidth={this._nativeWidth} PanelHeight={this._nativeHeight} ScreenToLocalTransform={this.ScreenToLocalTransform} isTopMost={true} - SelectOnLoad={false} + selectOnLoad={false} + parentActive={returnTrue} + onActiveChanged={emptyFunction} focus={(doc: Document) => { }} ContainingCollectionView={undefined} /> </div> diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 4d2daf149..1f2e71f6e 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,22 +1,20 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import { ContextMenu } from "../ContextMenu"; -import { CollectionView, CollectionViewType } from "./CollectionView"; import { CollectionViewProps } from "./CollectionViewBase"; import "./CollectionPDFView.scss" import React = require("react"); -import { FieldId } from "../../../fields/Field"; +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; +import { FieldView } from "../nodes/FieldView"; +import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView"; @observer export class CollectionPDFView extends React.Component<CollectionViewProps> { public static LayoutString(fieldKey: string = "DataKey") { - return `<${CollectionPDFView.name} Document={Document} - ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; + return FieldView.LayoutString(CollectionPDFView, fieldKey); } private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); } @@ -30,30 +28,31 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> { <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}> <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button> <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button> - </div>); + </div> + ); } - // "inherited" CollectionView API starts here... - @observable - public SelectedDocs: FieldId[] = [] - public active: () => boolean = () => CollectionView.Active(this); - - addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } - removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } - - specificContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + onContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "PDFOptions", event: () => { } }); } } - get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; } - get subView(): any { return CollectionView.SubView(this); } + private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { + let props = { ...renderProps, ...this.props }; + return ( + <> + <CollectionFreeFormView {...props} /> + {this.props.isSelected() ? this.uIButtons : (null)} + </> + ) + } render() { - return (<div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}> - {this.subView} - {this.props.isSelected() ? this.uIButtons : (null)} - </div>) + return ( + <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}> + {this.subView} + </CollectionBaseView> + ) } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index b10aaba98..4604945d1 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -22,9 +22,11 @@ import { EditableView } from "../EditableView"; import { DocumentView } from "../nodes/DocumentView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; -import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { CollectionView } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; import { TextField } from "../../../fields/TextField"; +import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; +import { emptyFunction, returnFalse } from "../../../Utils"; // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 @@ -73,19 +75,22 @@ export class CollectionSchemaView extends CollectionViewBase { renderCell = (rowProps: CellInfo) => { let props: FieldViewProps = { - doc: rowProps.value[0], + Document: rowProps.value[0], fieldKey: rowProps.value[1], isSelected: () => false, select: () => { }, isTopMost: false, - bindings: {}, selectOnLoad: false, + ScreenToLocalTransform: Transform.Identity, + focus: emptyFunction, + active: returnFalse, + onActiveChanged: emptyFunction, } let contents = ( <FieldView {...props} /> ) let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc)); + let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument); let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => { const res = run({ this: doc }); if (!res.success) return false; @@ -103,13 +108,13 @@ export class CollectionSchemaView extends CollectionViewBase { return false; } return ( - <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.doc.Id} ref={reference}> + <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}> <EditableView display={"inline"} contents={contents} height={56} GetValue={() => { - let field = props.doc.Get(props.fieldKey); + let field = props.Document.Get(props.fieldKey); if (field && field instanceof Field) { return field.ToScriptString(); } @@ -120,7 +125,7 @@ export class CollectionSchemaView extends CollectionViewBase { if (!script.compiled) { return false; } - return applyToDoc(props.doc, script.run); + return applyToDoc(props.Document, script.run); }} OnFillDown={(value: string) => { let script = CompileScript(value, { addReturn: true, params: { this: "Document" } }); @@ -155,8 +160,8 @@ export class CollectionSchemaView extends CollectionViewBase { } }), style: { - background: rowInfo.index == this._selectedIndex ? "lightGray" : "white", - //color: rowInfo.index == this._selectedIndex ? "white" : "black" + background: rowInfo.index === this._selectedIndex ? "lightGray" : "white", + //color: rowInfo.index === this._selectedIndex ? "white" : "black" } }; } @@ -184,8 +189,8 @@ export class CollectionSchemaView extends CollectionViewBase { @action toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => { this._startSplitPercent = this.splitPercentage; - if (this._startSplitPercent == this.splitPercentage) { - this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0); + if (this._startSplitPercent === this.splitPercentage) { + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); } } @@ -214,8 +219,8 @@ export class CollectionSchemaView extends CollectionViewBase { onDividerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onDividerMove); document.removeEventListener('pointerup', this.onDividerUp); - if (this._startSplitPercent == this.splitPercentage) { - this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0); + if (this._startSplitPercent === this.splitPercentage) { + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); } } onDividerDown = (e: React.PointerEvent) => { @@ -243,12 +248,8 @@ export class CollectionSchemaView extends CollectionViewBase { getContentScaling = (): number => this._contentScaling; getPanelWidth = (): number => this._panelWidth; getPanelHeight = (): number => this._panelHeight; - getTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - } - getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - } + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling) + getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling) focusDocument = (doc: Document) => { } @@ -299,28 +300,30 @@ export class CollectionSchemaView extends CollectionViewBase { let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined; // let doc = CompileScript(this.previewScript, { this: selected }, true)(); - let content = this._selectedIndex == -1 || !selected ? (null) : ( + let content = this._selectedIndex === -1 || !selected ? (null) : ( <Measure onResize={this.setScaling}> {({ measureRef }) => <div className="collectionSchemaView-content" ref={measureRef}> - {doc instanceof Document ? <DocumentView Document={doc} - AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument} - isTopMost={false} - SelectOnLoad={false} - ScreenToLocalTransform={this.getPreviewTransform} - ContentScaling={this.getContentScaling} - PanelWidth={this.getPanelWidth} - PanelHeight={this.getPanelHeight} - ContainingCollectionView={this.props.CollectionView} - focus={this.focusDocument} - /> : null} + {doc instanceof Document ? + <DocumentView Document={doc} + addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} + isTopMost={false} + selectOnLoad={false} + ScreenToLocalTransform={this.getPreviewTransform} + ContentScaling={this.getContentScaling} + PanelWidth={this.getPanelWidth} + PanelHeight={this.getPanelHeight} + ContainingCollectionView={undefined} + focus={this.focusDocument} + parentActive={this.props.active} + onActiveChanged={this.props.onActiveChanged} /> : null} <input value={this.previewScript} onChange={this.onPreviewScriptChange} style={{ position: 'absolute', bottom: '0px' }} /> </div> } </Measure> ) - let dividerDragger = this.splitPercentage == 0 ? (null) : + let dividerDragger = this.splitPercentage === 0 ? (null) : <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} /> //options button and menu @@ -330,12 +333,11 @@ export class CollectionSchemaView extends CollectionViewBase { <div id="schema-options-header"><h5><b>Options</b></h5></div> <div id="options-flyout-div"> <h6 className="schema-options-subHeader">Preview Window</h6> - <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage != 0} onChange={this.toggleExpander} /> Show Preview </div> + <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div> <h6 className="schema-options-subHeader" >Displayed Columns</h6> <ul id="schema-col-checklist" > - {Array.from(Object.keys(allKeys)).map(item => { - return (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />) - })} + {Array.from(Object.keys(allKeys)).map(item => + (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />))} </ul> <input value={this.newKeyName} onChange={this.newKeyChange} /> <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button> diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 0b12f11fd..dce35868e 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -7,17 +7,20 @@ import { Document } from "../../../fields/Document"; import { FieldWaiting } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { setupDrag } from "../../util/DragManager"; +import { setupDrag, DragManager } from "../../util/DragManager"; import { EditableView } from "../EditableView"; import "./CollectionTreeView.scss"; -import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { CollectionView } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; import React = require("react") +import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView'; +import { props } from 'bluebird'; export interface TreeViewProps { document: Document; deleteDoc: (doc: Document) => void; + moveDocument: DragManager.MoveFunction; copyOnDrag: boolean; } @@ -49,6 +52,16 @@ class TreeView extends React.Component<TreeViewProps> { } } + @action + move: DragManager.MoveFunction = (document, target, addDoc) => { + if (this.props.document === target) { + return true; + } + //TODO This should check if it was removed + this.remove(document) + return addDoc(document); + } + renderBullet(type: BulletType) { let onClicked = action(() => this._collapsed = !this._collapsed); let bullet: IconProp | undefined = undefined; @@ -64,7 +77,7 @@ class TreeView extends React.Component<TreeViewProps> { */ renderTitle() { let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document), this.props.copyOnDrag); + let onItemDown = setupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag); let editableView = (titleString: string) => (<EditableView display={"inline"} @@ -91,8 +104,8 @@ class TreeView extends React.Component<TreeViewProps> { if (!this._collapsed) { bulletType = BulletType.Collapsible; childElements = <ul> - {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} copyOnDrag={this.props.copyOnDrag} />)} - </ul> + {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)} + </ul > } else bulletType = BulletType.Collapsed; } @@ -122,7 +135,7 @@ export class CollectionTreeView extends CollectionViewBase { let copyOnDrag = this.props.Document.GetBoolean(KeyStore.CopyDraggedItems, false); let childrenElement = !children || children === FieldWaiting ? (null) : (children.Data.map(value => - <TreeView document={value} key={value.Id} deleteDoc={this.remove} copyOnDrag={copyOnDrag} />) + <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />) ) return ( diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 470a853e3..afadfeefb 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -3,11 +3,14 @@ import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import { ContextMenu } from "../ContextMenu"; -import { CollectionView, CollectionViewType } from "./CollectionView"; +import { CollectionView } from "./CollectionView"; +import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView"; import { CollectionViewProps } from "./CollectionViewBase"; import React = require("react"); import { FieldId } from "../../../fields/Field"; import "./CollectionVideoView.scss" +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; +import { FieldView } from "../nodes/FieldView"; @observer @@ -19,9 +22,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> { @observable _isPlaying: boolean = false; public static LayoutString(fieldKey: string = "DataKey") { - return `<${CollectionVideoView.name} Document={Document} - ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; + return FieldView.LayoutString(CollectionVideoView, fieldKey); } private get uIButtons() { let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]); @@ -42,7 +43,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> { @action mainCont = (ele: HTMLDivElement | null) => { if (ele) { - this._player = ele!.getElementsByTagName("video")[0]; + this._player = ele.getElementsByTagName("video")[0]; if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) { this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1); } @@ -60,7 +61,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> { @action updateTimecode = () => { if (this._player) { - if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero != -1) { + if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero !== -1) { this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero; (this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1; } else { @@ -101,30 +102,27 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> { } - // "inherited" CollectionView API starts here... - - @observable - public SelectedDocs: FieldId[] = [] - public active: () => boolean = () => CollectionView.Active(this); - - addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } - removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } - - specificContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + onContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } }); } } - get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; } - get subView(): any { return CollectionView.SubView(this); } - + private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { + let props = { ...renderProps, ...this.props }; + return ( + <> + <CollectionFreeFormView {...props} /> + {this.props.isSelected() ? this.uIButtons : (null)} + </> + ) + } render() { trace(); - return (<div className="collectionVideoView-cont" ref={this.mainCont} onContextMenu={this.specificContextMenu}> - {this.subView} - {this.props.isSelected() ? this.uIButtons : (null)} - </div>) + return ( + <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}> + {this.subView} + </CollectionBaseView>) } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 42ed3c86b..b31dcc888 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,160 +1,46 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; -import { ListField } from "../../../fields/ListField"; -import { SelectionManager } from "../../util/SelectionManager"; -import { ContextMenu } from "../ContextMenu"; -import React = require("react"); -import { KeyStore } from "../../../fields/KeyStore"; -import { NumberField } from "../../../fields/NumberField"; -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import { CollectionDockingView } from "./CollectionDockingView"; -import { CollectionSchemaView } from "./CollectionSchemaView"; -import { CollectionViewProps } from "./CollectionViewBase"; -import { CollectionTreeView } from "./CollectionTreeView"; -import { Field, FieldId, FieldWaiting } from "../../../fields/Field"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { undoBatch } from "../../util/UndoManager"; - -export enum CollectionViewType { - Invalid, - Freeform, - Schema, - Docking, - Tree -} - -export const COLLECTION_BORDER_WIDTH = 1; +import * as React from 'react' +import { FieldViewProps, FieldView } from '../nodes/FieldView'; +import { CollectionBaseView, CollectionViewType, CollectionRenderProps } from './CollectionBaseView'; +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { CollectionSchemaView } from './CollectionSchemaView'; +import { CollectionDockingView } from './CollectionDockingView'; +import { CollectionTreeView } from './CollectionTreeView'; +import { ContextMenu } from '../ContextMenu'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { KeyStore } from '../../../fields/KeyStore'; +import { observer } from 'mobx-react'; +import { undoBatch } from '../../util/UndoManager'; @observer -export class CollectionView extends React.Component<CollectionViewProps> { - - public static LayoutString(fieldKey: string = "DataKey") { - return `<${CollectionView.name} Document={Document} - ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; - } - - @observable - public SelectedDocs: FieldId[] = []; - public active: () => boolean = () => CollectionView.Active(this); - addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } - removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } - get subView() { return CollectionView.SubView(this); } - - public static Active(self: CollectionView): boolean { - var isSelected = self.props.isSelected(); - var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == self); - var topMost = self.props.isTopMost; - return isSelected || childSelected || topMost; - } - - static createsCycle(documentToAdd: Document, containerDocument: Document): boolean { - let data = documentToAdd.GetList<Document>(KeyStore.Data, []); - for (let i = 0; i < data.length; i++) { - if (CollectionView.createsCycle(data[i], containerDocument)) - return true; - } - let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []); - for (let i = 0; i < annots.length; i++) { - if (CollectionView.createsCycle(annots[i], containerDocument)) - return true; - } - for (let containerProto: any = containerDocument; containerProto && containerProto != FieldWaiting; containerProto = containerProto.GetPrototype()) { - if (containerProto.Id == documentToAdd.Id) - return true; - } - return false; - } - - @action - public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean { - var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); - doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); - if (curPage >= 0) { - doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document); - } - if (props.Document.Get(props.fieldKey) instanceof Field) { - //TODO This won't create the field if it doesn't already exist - const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) - if (!CollectionView.createsCycle(doc, props.Document)) { - if (!value.some(v => v.Id == doc.Id) || allowDuplicates) - value.push(doc); - } - else - return false; - } else { - let proto = props.Document.GetPrototype(); - if (!proto || proto == FieldWaiting || !CollectionView.createsCycle(proto, doc)) { - props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); - } - else - return false; - } - return true; - } - - @action - public static RemoveDocument(props: CollectionViewProps, doc: Document): boolean { - //TODO This won't create the field if it doesn't already exist - const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) - let index = -1; - for (let i = 0; i < value.length; i++) { - if (value[i].Id == doc.Id) { - index = i; - break; - } - } - doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => { - if (annotationOn == props.Document) { - doc.Set(KeyStore.AnnotationOn, undefined, true); - } - }) - - if (index !== -1) { - value.splice(index, 1) - - //SelectionManager.DeselectAll() - ContextMenu.Instance.clearItems() - return true; - } - return false - } - - get collectionViewType(): CollectionViewType { - let Document = this.props.Document; - let viewField = Document.GetT(KeyStore.ViewType, NumberField); - if (viewField === FieldWaiting) { - return CollectionViewType.Invalid; - } else if (viewField) { - return viewField.Data; - } else { - return CollectionViewType.Freeform; +export class CollectionView extends React.Component<FieldViewProps> { + public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr) } + + private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { + let props = { ...this.props, ...renderProps }; + switch (type) { + case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />) + case CollectionViewType.Docking: return (<CollectionDockingView {...props} />) + case CollectionViewType.Tree: return (<CollectionTreeView {...props} />) + case CollectionViewType.Freeform: + default: + return (<CollectionFreeFormView {...props} />) } + return (null); } - specificContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document.Id != CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + onContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // 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: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) }) ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) }) ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) }) } } - public static SubView(self: CollectionView) { - let subProps = { ...self.props, addDocument: self.addDocument, removeDocument: self.removeDocument, active: self.active, CollectionView: self } - switch (self.collectionViewType) { - case CollectionViewType.Freeform: return (<CollectionFreeFormView {...subProps} />) - case CollectionViewType.Schema: return (<CollectionSchemaView {...subProps} />) - case CollectionViewType.Docking: return (<CollectionDockingView {...subProps} />) - case CollectionViewType.Tree: return (<CollectionTreeView {...subProps} />) - } - return (null); - } - render() { - return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}> - {this.subView} - </div>) + return ( + <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}> + {this.SubView} + </CollectionBaseView> + ) } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 5a24f8cde..ad1294f76 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -7,38 +7,22 @@ import { FieldWaiting, Opt } from "../../../fields/Field"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DragManager } from "../../util/DragManager"; import { Documents, DocumentOptions } from "../../documents/Documents"; -import { Key } from "../../../fields/Key"; -import { Transform } from "../../util/Transform"; -import { CollectionView } from "./CollectionView"; import { RouteStore } from "../../../server/RouteStore"; import { TupleField } from "../../../fields/TupleField"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { NumberField } from "../../../fields/NumberField"; -import * as rp from "request-promise"; import { ServerUtils } from "../../../server/ServerUtil"; import { Server } from "../../Server"; -import { emptyStatement } from "babel-types"; -import { CollectionDockingView } from "./CollectionDockingView"; -import { runReactions } from "mobx/lib/internal"; +import { FieldViewProps } from "../nodes/FieldView"; +import * as rp from 'request-promise' -export interface CollectionViewProps { - fieldKey: Key; - Document: Document; - ScreenToLocalTransform: () => Transform; - isSelected: () => boolean; - isTopMost: boolean; - select: (ctrlPressed: boolean) => void; - bindings: any; - panelWidth: () => number; - panelHeight: () => number; - focus: (doc: Document) => void; +export interface CollectionViewProps extends FieldViewProps { + addDocument: (document: Document, allowDuplicates?: boolean) => boolean; + removeDocument: (document: Document) => boolean; + moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; } export interface SubCollectionViewProps extends CollectionViewProps { - active: () => boolean; - addDocument: (doc: Document, allowDuplicates: boolean) => boolean; - removeDocument: (doc: Document) => boolean; - CollectionView: CollectionView; } export type CursorEntry = TupleField<[string, string], [number, number]>; @@ -88,9 +72,14 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> de.data.draggedDocuments.map((draggedDocument: Document, i: number) => draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null))); } - let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true); - if (added && de.data.removeDocument && !de.data.aliasOnDrop && !de.data.copyOnDrop) { - de.data.removeDocument(this.props.CollectionView); + let added = false; + if (de.data.aliasOnDrop) { + added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false); + } else if (de.data.moveDocument) { + const move = de.data.moveDocument; + added = de.data.droppedDocuments.reduce((added: boolean, d) => added || move(d, this.props.Document, this.props.addDocument), false) + } else { + added = de.data.droppedDocuments.reduce((added: boolean, d) => added || this.props.addDocument(d), false) } e.stopPropagation(); return added; @@ -99,11 +88,11 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document; if (sourceDoc) runInAction(() => { let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document) - if (srcTarg && srcTarg != FieldWaiting) { + if (srcTarg && srcTarg !== FieldWaiting) { let linkDocs = srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]); linkDocs.map(linkDoc => { let targDoc = linkDoc.GetT(KeyStore.LinkedToDocs, Document); - if (targDoc && targDoc != FieldWaiting) { + if (targDoc && targDoc !== FieldWaiting) { let dropdoc = targDoc.MakeDelegate(); de.data.droppedDocuments.push(dropdoc); this.props.addDocument(dropdoc, false); @@ -156,8 +145,6 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> @undoBatch @action protected onDrop(e: React.DragEvent, options: DocumentOptions): void { - let that = this; - let html = e.dataTransfer.getData("text/html"); let text = e.dataTransfer.getData("text/plain"); @@ -167,7 +154,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> e.stopPropagation() e.preventDefault() - if (html && html.indexOf("<img") != 0 && !html.startsWith("<a")) { + if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) { console.log("not good"); let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 }); htmlDoc.SetText(KeyStore.DocumentText, text); @@ -177,10 +164,11 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> let batch = UndoManager.StartBatch("collection view drop"); let promises: Promise<void>[] = []; + // tslint:disable-next-line:prefer-for-of for (let i = 0; i < e.dataTransfer.items.length; i++) { const upload = window.location.origin + RouteStore.upload; let item = e.dataTransfer.items[i]; - if (item.kind === "string" && item.type.indexOf("uri") != -1) { + if (item.kind === "string" && item.type.indexOf("uri") !== -1) { let str: string; let prom = new Promise<string>(res => e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => { @@ -199,7 +187,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false) } let type = item.type - if (item.kind == "file") { + if (item.kind === "file") { let file = item.getAsFile(); let formData = new FormData() @@ -210,25 +198,26 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> let prom = fetch(upload, { method: 'POST', body: formData - }).then((res: Response) => { - return res.json() - }).then(json => { - json.map(action((file: any) => { + }).then(async (res: Response) => { + const json = await res.json(); + json.map((file: any) => { let path = window.location.origin + file - let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 }) - - let docs = that.props.Document.GetT(KeyStore.Data, ListField); - if (docs != FieldWaiting) { - if (!docs) { - docs = new ListField<Document>(); - that.props.Document.Set(KeyStore.Data, docs) - } - if (doc) { - docs.Data.push(doc); + runInAction(() => { + let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 }) + + let docs = this.props.Document.GetT(KeyStore.Data, ListField); + if (docs !== FieldWaiting) { + if (!docs) { + docs = new ListField<Document>(); + this.props.Document.Set(KeyStore.Data, docs) + } + if (doc) { + docs.Data.push(doc); + } } - } - })) - }) + }); + }); + }); promises.push(prom); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 1189dd4e8..abc6cecff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -31,17 +31,17 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP let x1w = srcDoc.GetNumber(KeyStore.Width, -1); let x2 = dstDoc.GetNumber(KeyStore.X, 0); let x2w = dstDoc.GetNumber(KeyStore.Width, -1); - if (x1w < 0 || x2w < 0 || i == j) + if (x1w < 0 || x2w < 0 || i === j) continue; let dstTarg = dstDoc; let srcTarg = srcDoc; let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => { let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : []; - return (bdocs.length && ((bdocs[0] == dstTarg && bdocs[1] == srcTarg)) ? true : false) + return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false) }); let brushAction = (field: ListField<Document>) => { let found = findBrush(field); - if (found != -1) { + if (found !== -1) { console.log("REMOVE BRUSH " + srcTarg.Title + " " + dstTarg.Title); field.Data.splice(found, 1); } @@ -53,9 +53,9 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); brushAction = brushAction = (field: ListField<Document>) => { - if (findBrush(field) == -1) { + if (findBrush(field) === -1) { console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title); - (findBrush(field) == -1) && field.Data.push(linkDoc); + (findBrush(field) === -1) && field.Data.push(linkDoc); } }; } @@ -69,10 +69,10 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP documentAnchors(view: DocumentView) { let equalViews = [view]; let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document); - if (containerDoc && containerDoc != FieldWaiting && containerDoc instanceof Document) { - equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype() as Document) + if (containerDoc && containerDoc instanceof Document) { + equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!) } - return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document); + return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document); } @computed @@ -84,14 +84,14 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document }))); possiblePairs.map(possiblePair => { if (!drawnPairs.reduce((found, drawnPair) => { - let match = (possiblePair.a == drawnPair.a && possiblePair.b == drawnPair.b); + let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b); if (match) { - if (!drawnPair.l.reduce((found, link) => found || link.Id == connection.l.Id, false)) + if (!drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) drawnPair.l.push(connection.l); } return match || found; }, false)) { - drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] as Document[] }); + drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] }); } }) return drawnPairs diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 19382e66f..f664f4e65 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,21 +1,8 @@ -import { action, computed, observable } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { FieldWaiting } from "../../../../fields/Field"; import { KeyStore } from "../../../../fields/KeyStore"; -import { TextField } from "../../../../fields/TextField"; -import { DragManager } from "../../../util/DragManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; -import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { DocumentViewProps } from "../../nodes/DocumentView"; -import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; -import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase"; -import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import { CollectionViewProps, CursorEntry } from "../CollectionViewBase"; import "./CollectionFreeFormView.scss"; -import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f99ba111c..731ba3332 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -11,7 +11,7 @@ import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps } from "../../nodes/DocumentView"; -import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; +import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView"; import { CollectionViewBase } from "../CollectionViewBase"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import "./CollectionFreeFormView.scss"; @@ -20,6 +20,8 @@ import React = require("react"); import v5 = require("uuid/v5"); import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import { PreviewCursor } from "./PreviewCursor"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { SelectionManager } from "../../../util/SelectionManager"; import { NumberField } from "../../../../fields/NumberField"; import { Main } from "../../Main"; @@ -41,15 +43,20 @@ export class CollectionFreeFormView extends CollectionViewBase { } public selectDocuments = (docs: Document[]) => { - this.props.CollectionView.SelectedDocs.length = 0; - docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id)); + SelectionManager.DeselectAll; + docs.map(doc => { + const dv = DocumentManager.Instance.getDocumentView(doc); + if (dv) { + SelectionManager.SelectDoc(dv, true); + } + }) } public getActiveDocuments = () => { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => { var page = doc.GetNumber(KeyStore.Page, -1); - if (page == curPage || page == -1) { + if (page === curPage || page === -1) { active.push(doc); } return active; @@ -61,6 +68,8 @@ export class CollectionFreeFormView extends CollectionViewBase { @observable private _lastX: number = 0; @observable private _lastY: number = 0; + private outerElement?: HTMLDivElement; + @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); } @@ -68,38 +77,40 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); } - @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections - @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections + @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) && this.outerElement ? this.outerElement.clientWidth / 2 : 0; } // shift so pan position is at center of window for non-overlay collections + @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) && this.outerElement ? this.outerElement.clientHeight / 2 : 0; }// shift so pan position is at center of window for non-overlay collections @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (super.drop(e, de)) { - let droppedDocs = de.data.droppedDocuments as Document[]; - let xoff = de.data.xOffset as number || 0; - let yoff = de.data.yOffset as number || 0; - if (droppedDocs && droppedDocs.length) { - let screenX = de.x - xoff; - let screenY = de.y - yoff; - const [x, y] = this.getTransform().transformPoint(screenX, screenY); - let dragDoc = de.data.droppedDocuments[0]; - let dragX = dragDoc.GetNumber(KeyStore.X, 0); - let dragY = dragDoc.GetNumber(KeyStore.Y, 0); - droppedDocs.map(async d => { - let docX = d.GetNumber(KeyStore.X, 0); - let docY = d.GetNumber(KeyStore.Y, 0); - d.SetNumber(KeyStore.X, x + (docX - dragX)); - d.SetNumber(KeyStore.Y, y + (docY - dragY)); - let docW = await d.GetTAsync(KeyStore.Width, NumberField); - let docH = await d.GetTAsync(KeyStore.Height, NumberField); - if (!docW) { - d.SetNumber(KeyStore.Width, 300); - } - if (!docH) { - d.SetNumber(KeyStore.Height, 300); - } - this.bringToFront(d); - }) + if (de.data instanceof DragManager.DocumentDragData) { + let droppedDocs = de.data.droppedDocuments; + let xoff = de.data.xOffset as number || 0; + let yoff = de.data.yOffset as number || 0; + if (droppedDocs.length) { + let screenX = de.x - xoff; + let screenY = de.y - yoff; + const [x, y] = this.getTransform().transformPoint(screenX, screenY); + let dragDoc = droppedDocs[0]; + let dragX = dragDoc.GetNumber(KeyStore.X, 0); + let dragY = dragDoc.GetNumber(KeyStore.Y, 0); + droppedDocs.map(async d => { + let docX = d.GetNumber(KeyStore.X, 0); + let docY = d.GetNumber(KeyStore.Y, 0); + d.SetNumber(KeyStore.X, x + (docX - dragX)); + d.SetNumber(KeyStore.Y, y + (docY - dragY)); + let docW = await d.GetTAsync(KeyStore.Width, NumberField); + let docH = await d.GetTAsync(KeyStore.Height, NumberField); + if (!docW) { + d.SetNumber(KeyStore.Width, 300); + } + if (!docH) { + d.SetNumber(KeyStore.Height, 300); + } + this.bringToFront(d); + }) + } } return true; } @@ -115,7 +126,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerDown = (e: React.PointerEvent): void => { - if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) && this.props.active()) { + if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling !== 1)) || e.button === 0) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -137,7 +148,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.props.active()) { - if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { + if ((!this.isAnnotationOverlay || this.zoomScaling !== 1) && !e.shiftKey) { let x = this.props.Document.GetNumber(KeyStore.PanX, 0); let y = this.props.Document.GetNumber(KeyStore.PanY, 0); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); @@ -166,8 +177,8 @@ export class CollectionFreeFormView extends CollectionViewBase { e.stopPropagation(); e.preventDefault(); } else { - // if (modes[e.deltaMode] == 'pixels') coefficient = 50; - // else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height?? + // if (modes[e.deltaMode] === 'pixels') coefficient = 50; + // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height?? let transform = this.getTransform(); let deltaScale = (1 - (e.deltaY / coefficient)); @@ -244,16 +255,19 @@ export class CollectionFreeFormView extends CollectionViewBase { getDocumentViewProps(document: Document): DocumentViewProps { return { Document: document, - AddDocument: this.props.addDocument, - RemoveDocument: this.props.removeDocument, + addDocument: this.props.addDocument, + removeDocument: this.props.removeDocument, + moveDocument: this.props.moveDocument, ScreenToLocalTransform: this.getTransform, isTopMost: false, - SelectOnLoad: document.Id == this._selectOnLoaded, + selectOnLoad: document.Id === this._selectOnLoaded, PanelWidth: document.Width, PanelHeight: document.Height, ContentScaling: this.noScaling, - ContainingCollectionView: this.props.CollectionView, - focus: this.focusDocument + ContainingCollectionView: undefined, + focus: this.focusDocument, + parentActive: this.props.active, + onActiveChanged: this.props.active, } } @@ -262,7 +276,7 @@ export class CollectionFreeFormView extends CollectionViewBase { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => { var page = doc.GetNumber(KeyStore.Page, -1); - if (page == curPage || page == -1) + if (page === curPage || page === -1) prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />); return prev; }, [] as JSX.Element[]) @@ -283,7 +297,7 @@ export class CollectionFreeFormView extends CollectionViewBase { getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) - getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); + getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; childViews = () => this.views; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index df150a045..704db1d4a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -48,7 +48,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @action onPointerDown = (e: React.PointerEvent): void => { - if (e.buttons == 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) { + if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) { this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; this._used = false; @@ -63,7 +63,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this._lastX = e.pageX; this._lastY = e.pageY; if (!e.cancelBubble) { - if (!this._used && e.buttons == 1 && !e.altKey && !e.metaKey && + if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey && (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) { this._visible = true; } @@ -99,15 +99,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @action marqueeCommand = (e: KeyboardEvent) => { - if (e.key == "Backspace" || e.key == "Delete") { + if (e.key === "Backspace" || e.key === "Delete") { this.marqueeSelect().map(d => this.props.removeDocument(d)); let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); - if (ink && ink != FieldWaiting) { + if (ink && ink !== FieldWaiting) { this.marqueeInkDelete(ink.Data); } this.cleanupInteractions(); } - if (e.key == "c") { + if (e.key === "c") { let bounds = this.Bounds; let selected = this.marqueeSelect().map(d => { this.props.removeDocument(d); @@ -118,7 +118,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return d; }); let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); - let inkData = ink && ink != FieldWaiting ? ink.Data : undefined; + let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined; //setTimeout(() => { let newCollection = Documents.FreeformDocument(selected, { x: bounds.left, @@ -147,7 +147,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { idata.set(key, { - pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), + pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })), color: value.color, width: value.width, tool: value.tool, diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx index 93c98f7b0..599461f85 100644 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx @@ -33,7 +33,7 @@ export class PreviewCursor extends React.Component<PreviewCursorProps> { @action onPointerDown = (e: React.PointerEvent) => { - if (e.button == 0 && this.props.container.props.active()) { + if (e.button === 0 && this.props.container.props.active()) { document.removeEventListener("keypress", this.onKeyPress, false); this._showOnUp = true; this.DownX = e.pageX; |
