diff options
Diffstat (limited to 'src/client/views/collections')
18 files changed, 463 insertions, 503 deletions
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 93eaab453..818a41009 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -12,7 +12,6 @@ import { ContextMenu } from '../ContextMenu'; import { FieldViewProps } from '../nodes/FieldView'; import './CollectionBaseView.scss'; import { DateField } from '../../../new_fields/DateField'; -import { DocumentType } from '../../documents/DocumentTypes'; import { ImageField } from '../../../new_fields/URLField'; export enum CollectionViewType { @@ -81,12 +80,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { } } - @computed get dataDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) ? this.props.DataDoc ? this.props.DataDoc : this.props.Document : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; } active = (): boolean => { var isSelected = this.props.isSelected(); - return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary); + return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; } //TODO should this be observable? @@ -96,7 +95,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { this.props.whenActiveChanged(isActive); } - @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } @action.bound addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { @@ -105,7 +104,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } - allowDuplicates = true; let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; const value = Cast(targetDataDoc[targetField], listSpec(Doc)); @@ -128,7 +126,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); + let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); + index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)); @@ -142,17 +141,16 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { return false; } + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. @action.bound moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - let self = this; - let targetDataDoc = this.props.Document; - if (Doc.AreProtosEqual(targetDataDoc, targetCollection)) { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } - if (this.removeDocument(doc)) { - return addDocument(doc); - } - return false; + return this.removeDocument(doc) ? addDocument(doc) : false; } showIsTagged = () => { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index f184a3944..ef2681410 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,36 +1,35 @@ +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faFile } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, Lambda, observable, reaction, trace, computed, runInAction } from "mobx"; +import { action, Lambda, observable, reaction, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; +import { DateField } from '../../../new_fields/DateField'; import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; import { Id } from '../../../new_fields/FieldSymbols'; +import { List } from '../../../new_fields/List'; import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; +import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; -import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { undoBatch } from "../../util/UndoManager"; +import { MainView } from '../MainView'; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; -import { ParentDocSelector } from './ParentDocumentSelector'; import React = require("react"); -import { MainView } from '../MainView'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faFile, faUnlockAlt } from '@fortawesome/free-solid-svg-icons'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { Docs } from '../../documents/Documents'; -import { DateField } from '../../../new_fields/DateField'; -import { List } from '../../../new_fields/List'; -import { DocumentType } from '../../documents/DocumentTypes'; +import { ButtonSelector } from './ParentDocumentSelector'; library.add(faFile); @observer @@ -44,7 +43,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp width: width, props: { documentId: document[Id], - dataDocumentId: dataDoc ? dataDoc[Id] : "" + dataDocumentId: dataDoc && dataDoc[Id] !== document[Id] ? dataDoc[Id] : "" //collectionDockingView: CollectionDockingView.Instance } }; @@ -63,24 +62,22 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp constructor(props: SubCollectionViewProps) { super(props); - if (props.addDocTab === emptyFunction) { - runInAction(() => CollectionDockingView.Instance = this); - } + !CollectionDockingView.Instance && (CollectionDockingView.Instance = this); //Why is this here? (window as any).React = React; (window as any).ReactDOM = ReactDOM; } hack: boolean = false; undohack: any = null; - public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs: (Doc | undefined)[] = []) { + public StartOtherDrag(e: any, dragDocs: Doc[]) { let config: any; if (dragDocs.length === 1) { - config = CollectionDockingView.makeDocumentConfig(dragDocs[0], dragDataDocs[0]); + config = CollectionDockingView.makeDocumentConfig(dragDocs[0], undefined); } else { config = { type: 'row', content: dragDocs.map((doc, i) => { - CollectionDockingView.makeDocumentConfig(doc, dragDataDocs[i]); + CollectionDockingView.makeDocumentConfig(doc, undefined); }) }; } @@ -90,18 +87,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp dragSource.destroy(); }); dragSource._dragListener.onMouseDown(e); - // dragSource.destroy(); - // this.hack = true; - // this.undohack = UndoManager.StartBatch("goldenDrag"); - // dragDocs.map((dragDoc, i) => - // this.AddRightSplit(dragDoc, dragDataDocs[i], true).contentItems[0].tab._dragListener. - // onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); } + @undoBatch @action public OpenFullScreen(docView: DocumentView) { let document = Doc.MakeAlias(docView.props.Document); - let dataDoc = docView.dataDoc; + let dataDoc = docView.props.DataDoc; let newItemStackConfig = { type: 'stack', content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)] @@ -113,6 +105,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this._maximizedSrc = docView; this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); this.stateChanged(); + SelectionManager.DeselectAll(); } public CloseFullScreen = () => { @@ -131,21 +124,25 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp @undoBatch @action - public CloseRightSplit = (document: Doc): boolean => { + public static CloseRightSplit(document: Doc): boolean { + if (!CollectionDockingView.Instance) return false; + let instance = CollectionDockingView.Instance; let retVal = false; - if (this._goldenLayout.root.contentItems[0].isRow) { - retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { + if (instance._goldenLayout.root.contentItems[0].isRow) { + retVal = Array.from(instance._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && + DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId) && Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { child.contentItems[0].remove(); - this.layoutChanged(document); + instance.layoutChanged(document); return true; } else { Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { - if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { + if (DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId) && + Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { child.contentItems[j].remove(); child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); - let docs = Cast(this.props.Document.data, listSpec(Doc)); + let docs = Cast(instance.props.Document.data, listSpec(Doc)); docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); return true; } @@ -156,7 +153,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp }); } if (retVal) { - this.stateChanged(); + instance.stateChanged(); } return retVal; } @@ -181,9 +178,12 @@ 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 // + @undoBatch @action - public AddRightSplit = (document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) => { - let docs = Cast(this.props.Document.data, listSpec(Doc)); + public static AddRightSplit(document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) { + if (!CollectionDockingView.Instance) return false; + let instance = CollectionDockingView.Instance; + let docs = Cast(instance.props.Document.data, listSpec(Doc)); if (docs) { docs.push(document); } @@ -192,15 +192,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)] }; - var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); + var newContentItem = instance._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); - if (this._goldenLayout.root.contentItems.length === 0) { - this._goldenLayout.root.addChild(newContentItem); - } else if (this._goldenLayout.root.contentItems[0].isRow) { - this._goldenLayout.root.contentItems[0].addChild(newContentItem); + if (instance._goldenLayout.root.contentItems.length === 0) { + instance._goldenLayout.root.addChild(newContentItem); + } else if (instance._goldenLayout.root.contentItems[0].isRow) { + instance._goldenLayout.root.contentItems[0].addChild(newContentItem); } else { - var collayout = this._goldenLayout.root.contentItems[0]; - var newRow = collayout.layoutManager.createContentItem({ type: "row" }, this._goldenLayout); + var collayout = instance._goldenLayout.root.contentItems[0]; + var newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout); collayout.parent.replaceChild(collayout, newRow); newRow.addChild(newContentItem, undefined, true); @@ -215,10 +215,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // newContentItem.config.height = 10; } newContentItem.callDownwards('_$init'); - this.layoutChanged(); - - return newContentItem; + instance.layoutChanged(); + return true; } + + @undoBatch @action public AddTab = (stack: any, document: Doc, dataDocument: Doc | undefined) => { Doc.GetProto(document).lastOpened = new DateField; @@ -245,6 +246,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp stack.addChild(docContentConfig, undefined); } this.layoutChanged(); + return true; } setupGoldenLayout() { @@ -404,6 +406,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp dragSpan.style.bottom = "6px"; dragSpan.style.paddingLeft = "4px"; dragSpan.style.paddingRight = "2px"; + let gearSpan = document.createElement("span"); + gearSpan.style.position = "relative"; + gearSpan.style.paddingLeft = "0px"; + gearSpan.style.paddingRight = "12px"; let upDiv = document.createElement("span"); const stack = tab.contentItem.parent; // shifts the focus to this tab when another tab is dragged over it @@ -419,14 +425,19 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp e => { e.preventDefault(); e.stopPropagation(); - DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc], [dataDoc]), e.clientX, e.clientY, { + DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { handlers: { dragComplete: emptyFunction }, hideSource: false }); }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan); - ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv); - tab.reactComponents = [dragSpan, upDiv]; + ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan); + // ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, data, where) => { + // where === "onRight" ? CollectionDockingView.AddRightSplit(doc, dataDoc) : CollectionDockingView.Instance.AddTab(stack, doc, dataDoc); + // return true; + // }} />, upDiv); + tab.reactComponents = [dragSpan, gearSpan, upDiv]; tab.element.append(dragSpan); + tab.element.append(gearSpan); tab.element.append(upDiv); tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => { tab.titleElement[0].textContent = doc.title, { fireImmediately: true }; @@ -538,11 +549,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { @observable private _isActive: boolean = false; get _stack(): any { - let parent = (this.props as any).glContainer.parent.parent; - if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) { - return parent.parent.contentItems[1]; - } - return parent; + return (this.props as any).glContainer.parent.parent; } constructor(props: any) { super(props); @@ -616,17 +623,18 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { } return Transform.Identity(); } - get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; } + get previewPanelCenteringOffset() { return this.nativeWidth() && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() / this.ScreenToLocalTransform().Scale) / 2 : 0; } - addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { + addDocTab = (doc: Doc, dataDoc: Opt<Doc>, location: string) => { if (doc.dockingConfig) { MainView.Instance.openWorkspace(doc); + return true; } else if (location === "onRight") { - CollectionDockingView.Instance.AddRightSplit(doc, dataDoc); + return CollectionDockingView.AddRightSplit(doc, dataDoc); } else if (location === "close") { - CollectionDockingView.Instance.CloseRightSplit(doc); + return CollectionDockingView.CloseRightSplit(doc); } else { - CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); + return CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc); } } @computed get docView() { @@ -640,6 +648,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { bringToFront={emptyFunction} addDocument={undefined} removeDocument={undefined} + ruleProvider={undefined} ContentScaling={this.contentScaling} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} @@ -652,6 +661,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { addDocTab={this.addDocTab} pinToPres={this.PinDoc} ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} zoomToScale={emptyFunction} getScale={returnOne} />; } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index c59107b53..4dac27e60 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -39,7 +39,7 @@ export interface CellProps { Document: Doc; fieldKey: string; renderDepth: number; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; isFocused: boolean; @@ -149,7 +149,9 @@ export class CollectionSchemaCell extends React.Component<CellProps> { DataDoc: this.props.rowProps.original, fieldKey: this.props.rowProps.column.id as string, fieldExt: "", + ruleProvider: undefined, ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.CollectionView.props.Document, isSelected: returnFalse, select: emptyFunction, renderDepth: this.props.renderDepth + 1, @@ -171,7 +173,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> { let onItemDown = (e: React.PointerEvent) => { if (fieldIsDoc) { SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); + this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, + this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); } }; let onPointerEnter = (e: React.PointerEvent): void => { @@ -235,13 +238,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> { return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run); }} OnFillDown={async (value: string) => { - let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - if (!script.compiled) { - return; + const script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); + if (script.compiled) { + DocListCast(this.props.Document[this.props.fieldKey]). + forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, script.run)); } - const run = script.run; - const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]); - val && val.forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, run)); }} /> </div > diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index ec40043cc..39abc41ec 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -201,12 +201,8 @@ export class MovableRow extends React.Component<MovableRowProps> { @action move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => { let targetView = DocumentManager.Instance.getDocumentView(target); - if (targetView) { - let targetContainingColl = targetView.props.ContainingCollectionView; //.props.ContainingCollectionView.props.Document; - if (targetContainingColl) { - let targetContCollDoc = targetContainingColl.props.Document; - return doc !== target && doc !== targetContCollDoc && this.props.removeDoc(doc) && addDoc(doc); - } + if (targetView && targetView.props.ContainingCollectionDoc) { + return doc !== target && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); } return doc !== target && this.props.removeDoc(doc) && addDoc(doc); } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 9d83aa6c1..7bd2a1971 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -14,7 +14,7 @@ import { listSpec } from "../../../new_fields/Schema"; import { Docs, DocumentOptions } from "../../documents/Documents"; import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; import { Gateway } from "../../northstar/manager/Gateway"; -import { SetupDrag, DragManager } from "../../util/DragManager"; +import { DragManager } from "../../util/DragManager"; import { CompileScript, ts, Transformer } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; @@ -32,6 +32,7 @@ import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, Collection import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; import { ComputedField, ScriptField } from "../../../new_fields/ScriptField"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { DocumentType } from "../../documents/DocumentTypes"; library.add(faCog, faPlus, faSortUp, faSortDown); @@ -161,6 +162,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined} childDocs={this.childDocs} renderDepth={this.props.renderDepth} + ruleProvider={this.props.Document.isRuleProvider && layoutDoc && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider} width={this.previewWidth} height={this.previewHeight} getTransform={this.getPreviewTransform} @@ -194,6 +196,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { childDocs={this.childDocs} CollectionView={this.props.CollectionView} ContainingCollectionView={this.props.ContainingCollectionView} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} fieldKey={this.props.fieldKey} renderDepth={this.props.renderDepth} moveDocument={this.props.moveDocument} @@ -245,6 +248,7 @@ export interface SchemaTableProps { childDocs?: Doc[]; CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; + ContainingCollectionDoc: Opt<Doc>; fieldKey: string; renderDepth: number; deleteDocument: (document: Doc) => boolean; @@ -252,7 +256,7 @@ export interface SchemaTableProps { ScreenToLocalTransform: () => Transform; active: () => boolean; onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; isSelected: () => boolean; isFocused: (document: Doc) => boolean; @@ -901,6 +905,7 @@ interface CollectionSchemaPreviewProps { fitToBox?: boolean; width: () => number; height: () => number; + ruleProvider: Doc | undefined; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView; onClick?: ScriptField; @@ -910,7 +915,7 @@ interface CollectionSchemaPreviewProps { removeDocument: (document: Doc) => boolean; active: () => boolean; whenActiveChanged: (isActive: boolean) => void; - addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; setPreviewScript: (script: string) => void; previewScript?: string; @@ -943,13 +948,12 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { - let docDrag = de.data; - let computed = CompileScript("return this.image_data[0]", { params: { this: "Doc" } }); this.props.childDocs && this.props.childDocs.map(otherdoc => { - let doc = docDrag.draggedDocuments[0]; let target = Doc.GetProto(otherdoc); - target.layout = target.detailedLayout = Doc.MakeDelegate(doc); - computed.compiled && (target.miniLayout = new ComputedField(computed)); + let layoutNative = Doc.MakeTitled("layoutNative"); + layoutNative.layout = ComputedField.MakeFunction("this.image_data[0]"); + target.layoutNative = layoutNative; + target.layoutCUstom = target.layout = Doc.MakeDelegate(de.data.draggedDocuments[0]); }); e.stopPropagation(); } @@ -995,12 +999,14 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre Document={this.props.Document} fitToBox={this.props.fitToBox} onClick={this.props.onClick} + ruleProvider={this.props.ruleProvider} showOverlays={this.props.showOverlays} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument} whenActiveChanged={this.props.whenActiveChanged} ContainingCollectionView={this.props.CollectionView} + ContainingCollectionDoc={this.props.CollectionView && this.props.CollectionView.props.Document} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} parentActive={this.props.active} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 91e10b0ac..ccf131797 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -29,7 +29,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { _masonryGridRef: HTMLDivElement | null = null; _draggerRef = React.createRef<HTMLDivElement>(); _heightDisposer?: IReactionDisposer; - _childLayoutDisposer?: IReactionDisposer; _sectionFilterDisposer?: IReactionDisposer; _docXfs: any[] = []; _columnStart: number = 0; @@ -87,10 +86,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } componentDidMount() { - this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], - async (args) => args[1] instanceof Doc && - this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc), undefined))); - // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). this._heightDisposer = reaction(() => { if (this.isStackingView && BoolCast(this.props.Document.autoHeight)) { @@ -115,7 +110,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { ); } componentWillUnmount() { - this._childLayoutDisposer && this._childLayoutDisposer(); this._heightDisposer && this._heightDisposer(); this._sectionFilterDisposer && this._sectionFilterDisposer(); } @@ -134,7 +128,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } - @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : ScriptCast(this.Document.onChildClick); } + @computed get onClickHandler() { return ScriptCast(this.Document.onChildClick); } getDisplayDoc(layoutDoc: Doc, dataDoc: Doc | undefined, dxf: () => Transform, width: () => number) { let height = () => this.getDocHeight(layoutDoc); @@ -144,6 +138,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { DataDocument={dataDoc} showOverlays={this.overlays} renderDepth={this.props.renderDepth} + ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider} fitToBox={this.props.fitToBox} onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler} width={width} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index bc4fe7dd7..b3b7b40dd 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -155,6 +155,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let key = StrCast(this.props.parent.props.Document.sectionFilter); let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, documentText: "@@@" + value, title: value, autoHeight: true }); newDoc[key] = this.getValue(this.props.heading); + let maxHeading = this.props.docList.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; + newDoc.heading = heading; return this.props.parent.props.addDocument(newDoc); } @@ -175,13 +178,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC let key = StrCast(this.props.parent.props.Document.sectionFilter); let value = this.getValue(this._heading); value = typeof value === "string" ? `"${value}"` : value; - let script = `return doc.${key} === ${value}`; - let compiled = CompileScript(script, { params: { doc: Doc.name } }); - if (compiled.compiled) { - let scriptField = new ScriptField(compiled); - alias.viewSpecScript = scriptField; - let dragData = new DragManager.DocumentDragData([alias], [alias.proto]); - DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY); + alias.viewSpecScript = ScriptField.MakeFunction(`doc.${key} === ${value}`, { doc: Doc.name }); + if (alias.viewSpecScript) { + DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY); } e.stopPropagation(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 59fc11359..0680f6644 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,4 +1,4 @@ -import { action, computed } from "mobx"; +import { action, computed, IReactionDisposer, reaction } from "mobx"; import * as rp from 'request-promise'; import CursorField from "../../../new_fields/CursorField"; import { Doc, DocListCast, HeightSym } from "../../../new_fields/Doc"; @@ -36,11 +36,14 @@ export interface CollectionViewProps extends FieldViewProps { export interface SubCollectionViewProps extends CollectionViewProps { CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; + ruleProvider: Doc | undefined; } export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; + private _childLayoutDisposer?: IReactionDisposer; + protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer && this.dropDisposer(); if (ele) { @@ -51,33 +54,35 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { this.createDropTarget(ele); } - @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + componentDidMount() { + this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], + async (args) => args[1] instanceof Doc && + this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)))); + } + componentWillUnmount() { + this._childLayoutDisposer && this._childLayoutDisposer(); + } - get childDocs() { - let self = this; - //TODO tfs: This might not be what we want? - //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) - let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]); - let viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - if (viewSpecScript) { - let script = viewSpecScript.script; - docs = docs.filter(d => { - let res = script.run({ doc: d }); - if (res.success) { - return res.result; - } - else { - console.log(res.error); - } - }); - } - return docs; + // The data field for rendeing this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. + // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through + // to its children which may be templates. + // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise. + @computed get dataField() { + return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey]; + } + + + get childLayoutPairs() { + return this.childDocs.map(cd => Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, cd)).filter(pair => pair.layout).map(pair => ({ layout: pair.layout!, data: pair.data! })); } get childDocList() { - //TODO tfs: This might not be what we want? - //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) - return Cast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc)); + return Cast(this.dataField, listSpec(Doc)); + } + get childDocs() { + let docs = DocListCast(this.dataField); + const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); + return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; } @action @@ -118,7 +123,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) { if (de.mods === "AltKey" && de.data.draggedDocuments.length) { this.childDocs.map(doc => - Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined) + Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc) ); e.stopPropagation(); return true; @@ -127,9 +132,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { if (de.data.dropAction || de.data.userDropAction) { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (de.data.moveDocument) { - let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; - added = movedDocs.reduce((added: boolean, d) => - de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false); + let movedDocs = de.data.draggedDocuments;// de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; + // note that it's possible the drag function might create a drop document that's not the same as the + // original dragged document. So we explicitly call addDocument() with a droppedDocument and + added = movedDocs.reduce((added: boolean, d, i) => + de.data.moveDocument(d, this.props.Document, (doc: Doc) => this.props.addDocument(de.data.droppedDocuments[i])) || added, false); } else { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } @@ -271,6 +278,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { promises.push(prom); } } + if (text) { + this.props.addDocument(Docs.Create.TextDocument({ ...options, documentText: "@@@" + text, width: 400, height: 315 })); + return; + } if (promises.length) { Promise.all(promises).finally(() => { completed && completed(); batch.end(); }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index f5bb76966..e5313f68c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -37,9 +37,10 @@ export interface TreeViewProps { containingCollection: Doc; renderDepth: number; deleteDoc: (doc: Doc) => boolean; + ruleProvider: Doc | undefined; moveDocument: DragManager.MoveFunction; dropAction: "alias" | "copy" | undefined; - addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; panelWidth: () => number; panelHeight: () => number; @@ -177,7 +178,7 @@ class TreeView extends React.Component<TreeViewProps> { SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true)} OnFillDown={undoBatch((value: string) => { Doc.GetProto(this.dataDoc)[key] = value; - let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined; + let doc = this.props.document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.layoutCustom)) : undefined; if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; return this.props.addDocument(doc); @@ -200,6 +201,7 @@ class TreeView extends React.Component<TreeViewProps> { ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" }); } ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" }); ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); e.stopPropagation(); e.preventDefault(); @@ -324,6 +326,7 @@ class TreeView extends React.Component<TreeViewProps> { DataDocument={this.resolvedDataDoc} renderDepth={this.props.renderDepth} showOverlays={this.noOverlays} + ruleProvider={this.props.document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.document : this.props.ruleProvider} fitToBox={this.boundsOfCollectionDocument !== undefined} width={this.docWidth} height={this.docHeight} @@ -344,7 +347,7 @@ class TreeView extends React.Component<TreeViewProps> { @computed get renderBullet() { - return <div className="bullet" title="view inline" onClick={action(() => this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> + return <div className="bullet" title="view inline" onClick={action((e: React.MouseEvent) => { this.treeViewOpen = !this.treeViewOpen; e.stopPropagation(); })} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}> {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />} </div>; } @@ -402,7 +405,7 @@ class TreeView extends React.Component<TreeViewProps> { </div>; } public static GetChildElements( - docList: Doc[], + docs: Doc[], treeViewId: string, containingCollection: Doc, dataDoc: Doc | undefined, @@ -411,7 +414,7 @@ class TreeView extends React.Component<TreeViewProps> { remove: ((doc: Doc) => boolean), move: DragManager.MoveFunction, dropAction: dropActionType, - addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void, + addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => boolean, pinToPres: (document: Doc) => void, screenToLocalXf: () => Transform, outerXf: () => { translateX: number, translateY: number }, @@ -422,19 +425,9 @@ class TreeView extends React.Component<TreeViewProps> { preventTreeViewOpen: boolean, renderedIds: string[] ) { - let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0); - let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); + const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); if (viewSpecScript) { - let script = viewSpecScript.script; - docs = docs.filter(d => { - let res = script.run({ doc: d }); - if (res.success) { - return res.result; - } - else { - console.log(res.error); - } - }); + docs = docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } let ascending = Cast(containingCollection.sortAscending, "boolean", null); @@ -491,6 +484,7 @@ class TreeView extends React.Component<TreeViewProps> { dataDoc={pair.data} containingCollection={containingCollection} treeViewId={treeViewId} + ruleProvider={containingCollection.isRuleProvider && pair.layout.type !== DocumentType.TEXT ? containingCollection : containingCollection.ruleProvider as Doc} key={child[Id]} indentDocument={indent} renderDepth={renderDepth} @@ -544,7 +538,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // excludeFromLibrary means this is the user document + if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" }); ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.props.Document), icon: "minus" }); e.stopPropagation(); @@ -559,8 +553,8 @@ export class CollectionTreeView extends CollectionSubView(Document) { outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {}); openNotifsCol = () => { - if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) { - CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined); + if (CollectionTreeView.NotifsCol) { + this.props.addDocTab(CollectionTreeView.NotifsCol, undefined, "onRight"); } } @@ -610,7 +604,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)} OnFillDown={undoBatch((value: string) => { Doc.GetProto(this.props.Document).title = value; - let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined; + let doc = this.props.Document.layoutCustom instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.layoutCustom)) : undefined; if (!doc) doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) }); TreeView.loadId = doc[Id]; Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, false); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 94b49fb98..1f2dc9b5c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -105,6 +105,12 @@ export class CollectionView extends React.Component<FieldViewProps> { subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" }); subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" }); subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" }); + subItems.push({ + description: "Stacking (AutoHeight)", event: () => { + this.props.Document.viewType = CollectionViewType.Stacking; + this.props.Document.autoHeight = true; + }, icon: "ellipsis-v" + }); subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index c897af17e..7510b86a0 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -215,14 +215,11 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro } } let fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ? - `return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : - `return (${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : - "return true"; + `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : + `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : + "true"; - let compiled = CompileScript(fullScript, { params: { doc: Doc.name }, typecheck: false }); - if (compiled.compiled) { - this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); - } + this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name }); } @action @@ -283,11 +280,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro @action.bound clearFilter = () => { - let compiled = CompileScript("return true", { params: { doc: Doc.name }, typecheck: false }); - if (compiled.compiled) { - this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); - } - + this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction("true", { doc: Doc.name }); this._keyRestrictions = []; this.addKeyRestrictions([]); } diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss index 2dd3e49f2..c186d15f8 100644 --- a/src/client/views/collections/ParentDocumentSelector.scss +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -19,4 +19,13 @@ border-right: 0px; border-left: 0px; } +} +.parentDocumentSelector-button { + pointer-events: all; +} +.buttonSelector { + position: absolute; + display: inline-block; + padding-left: 5px; + padding-right: 5px; }
\ No newline at end of file diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index d8475a467..7f2913214 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -8,8 +8,15 @@ import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; import { NumCast } from "../../../new_fields/Types"; import { CollectionViewType } from "./CollectionBaseView"; +import { DocumentButtonBar } from "../DocumentButtonBar"; +import { DocumentManager } from "../../util/DocumentManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEdit } from "@fortawesome/free-solid-svg-icons"; +import { library } from "@fortawesome/fontawesome-svg-core"; -type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void }; +library.add(faEdit); + +type SelectorProps = { Document: Doc, Stack?: any, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void }; @observer export class SelectorContextMenu extends React.Component<SelectorProps> { @observable private _docs: { col: Doc, target: Doc }[] = []; @@ -83,7 +90,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> { ); } return ( - <span style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }} + <span className="parentDocumentSelector-button" style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}> <p>^</p> @@ -92,3 +99,38 @@ export class ParentDocSelector extends React.Component<SelectorProps> { ); } } + +@observer +export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any }> { + @observable hover = false; + + @action + onMouseLeave = () => { + this.hover = false; + } + + @action + onMouseEnter = () => { + this.hover = true; + } + + render() { + let flyout; + if (this.hover) { + let view = DocumentManager.Instance.getDocumentView(this.props.Document); + flyout = !view ? (null) : ( + <div className="PDS-flyout" title=" " onMouseLeave={this.onMouseLeave}> + <DocumentButtonBar views={[view]} stack={this.props.Stack} /> + </div> + ); + } + return ( + <span className="buttonSelector" + onMouseEnter={this.onMouseEnter} + onMouseLeave={this.onMouseLeave}> + {this.hover ? (null) : <FontAwesomeIcon icon={faEdit} size={"sm"} />} + {flyout} + </span> + ); + } +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 2a64a7afb..cfd18ad35 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,8 +1,9 @@ .collectionfreeformlinkview-linkLine { stroke: black; transform: translate(10000px,10000px); - // opacity: 0.5; + opacity: 0.8; pointer-events: all; + stroke-width: 3px; } .collectionfreeformlinkview-linkCircle { stroke: rgb(0,0,0); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 12771d11e..df089eb00 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -50,7 +50,6 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo return ( <> <line key="linkLine" className="collectionfreeformlinkview-linkLine" - style={{ strokeWidth: `${3}` }} x1={`${x1}`} y1={`${y1}`} x2={`${x2}`} y2={`${y2}`} /> {/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle" diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index a25627dd1..a81f5315a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -79,15 +79,15 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP if (containerDoc) { equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!); } - if (view.props.ContainingCollectionView) { - let collid = view.props.ContainingCollectionView.props.Document[Id]; + if (view.props.ContainingCollectionDoc) { + let collid = view.props.ContainingCollectionDoc[Id]; DocListCast(this.props.Document[this.props.fieldKey]). filter(child => child[Id] === collid).map(view => DocumentManager.Instance.getDocumentViews(view).map(view => equalViews.push(view))); } - return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document); + return equalViews.filter(sv => sv.props.ContainingCollectionDoc === this.props.Document); } @computed diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f7bda0a26..eb738d783 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,10 +8,10 @@ import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../../new_fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue, DateCast } from "../../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { Docs } from "../../../documents/Documents"; +import { Docs, DocumentOptions } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; @@ -24,9 +24,9 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; +import { DocumentViewProps, documentSchema } from "../../nodes/DocumentView"; import { pageSchema } from "../../nodes/ImageBox"; import { OverlayElementOptions, OverlayView } from "../../OverlayView"; import PDFMenu from "../../pdf/PDFMenu"; @@ -51,6 +51,9 @@ export const panZoomSchema = createSchema({ scale: "number", arrangeScript: ScriptField, arrangeInit: ScriptField, + useClusters: "boolean", + isRuleProvider: "boolean", + fitToBox: "boolean" }); export interface ViewDefBounds { @@ -161,6 +164,7 @@ export namespace PivotView { y={pos.y} width={pos.width} height={pos.height} + transition={"transform 1s"} jitterRotation={NumCast(target.props.Document.jitterRotation)} {...target.getChildDocumentViewProps(doc)} />, @@ -181,8 +185,8 @@ export namespace PivotView { } -type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; -const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema); +type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; +const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema); @observer export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @@ -190,35 +194,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _lastY: number = 0; private get _pwidth() { return this.props.PanelWidth(); } private get _pheight() { return this.props.PanelHeight(); } - private inkKey = "ink"; - private _childLayoutDisposer?: IReactionDisposer; - private _childDisposer?: IReactionDisposer; - - componentDidMount() { - this._childDisposer = reaction(() => this.childDocs, - async (childDocs) => { - let childLayout = Cast(this.props.Document.childLayout, Doc) as Doc; - childLayout && childDocs.map(async doc => { - if (!Doc.AreProtosEqual(childLayout, (await doc).layout as Doc)) { - Doc.ApplyTemplateTo(childLayout, doc, undefined); - } - }); - }); - this._childLayoutDisposer = reaction(() => Cast(this.props.Document.childLayout, Doc), - async (childLayout) => { - this.childDocs.map(async doc => { - if (!Doc.AreProtosEqual(childLayout as Doc, (await doc).layout as Doc)) { - Doc.ApplyTemplateTo(childLayout as Doc, doc, undefined); - } - }); - }); - } - componentWillUnmount() { - this._childDisposer && this._childDisposer(); - this._childLayoutDisposer && this._childLayoutDisposer(); - } - - get parentScaling() { + private get parentScaling() { return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1; } @@ -249,7 +225,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return res; } - @computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; } + @computed get fitToBox() { return this.props.fitToBox || this.Document.fitToBox; } @computed get nativeWidth() { return this.fitToBox ? 0 : this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.fitToBox ? 0 : this.Document.nativeHeight || 0; } public get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt') @@ -265,29 +241,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed - newBox.heading = 1; - for (let child of this.childDocs) { - if (child.heading === 1) { - newBox.heading = 2; - } + let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0; + if (heading === 0) { + let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 : + DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0); + heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } - PromiseValue(Cast(this.props.Document.ruleProvider, Doc)).then(ruleProvider => { - if (!ruleProvider) ruleProvider = this.props.Document; - // saturation shift - // let col = NumCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - // let back = Utils.fromRGBAstr(StrCast(this.props.Document.backgroundColor)); - // let hsl = Utils.RGBToHSL(back.r, back.g, back.b); - // let newcol = { h: hsl.h, s: hsl.s + col, l: hsl.l }; - // col && (Doc.GetProto(newBox).backgroundColor = Utils.toRGBAstr(Utils.HSLtoRGB(newcol.h, newcol.s, newcol.l))); - // OR transparency set - let col = StrCast(ruleProvider["ruleColor_" + NumCast(newBox.heading)]); - (newBox.backgroundColor === newBox.defaultBackgroundColor) && col && (Doc.GetProto(newBox).backgroundColor = col); - - let round = StrCast(ruleProvider["ruleRounding_" + NumCast(newBox.heading)]); - round && (Doc.GetProto(newBox).borderRounding = round); - newBox.ruleProvider = ruleProvider; - this.addDocument(newBox, false); - }); + !this.Document.isRuleProvider && (newBox.heading = heading); + this.addDocument(newBox, false); } private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); @@ -302,14 +264,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } public getActiveDocuments = () => { const curPage = FieldValue(this.Document.curPage, -1); - return this.childDocs.filter(doc => { - var page = NumCast(doc.page, -1); + return this.childLayoutPairs.filter(pair => { + var page = NumCast(pair.layout.page, -1); return page === curPage || page === -1; - }); + }).map(pair => pair.layout); } @computed get fieldExtensionDoc() { - return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); + return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey); } intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -342,8 +304,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { let z = NumCast(de.data.droppedDocuments[0].z); - let x = (z ? xpo : xp) - de.data.xOffset; - let y = (z ? ypo : yp) - de.data.yOffset; + let x = (z ? xpo : xp) - de.data.offset[0]; + let y = (z ? ypo : yp) - de.data.offset[1]; let dropX = NumCast(de.data.droppedDocuments[0].x); let dropY = NumCast(de.data.droppedDocuments[0].y); de.data.droppedDocuments.forEach(d => { @@ -366,8 +328,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; - let x = xp - de.data.xOffset; - let y = yp - de.data.yOffset; + let x = xp - de.data.offset[0]; + let y = yp - de.data.offset[1]; let dropX = NumCast(de.data.dropDocument.x); let dropY = NumCast(de.data.dropDocument.y); dragDoc.x = x + NumCast(dragDoc.x) - dropX; @@ -383,7 +345,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { tryDragCluster(e: PointerEvent) { let probe = this.getTransform().transformPoint(e.clientX, e.clientY); - let cluster = this.childDocs.reduce((cluster, cd) => { + let cluster = this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { let cx = NumCast(cd.x) - this._clusterDistance; let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(cd.width) + 2 * this._clusterDistance; @@ -394,7 +356,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return cluster; }, -1); if (cluster !== -1) { - let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view let prevSelected = SelectionManager.SelectedDocuments(); @@ -403,13 +365,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { SelectionManager.DeselectAll(); prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); - let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined)); + let de = new DragManager.DocumentDragData(eles); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); - const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); + de.offset = this.getTransform().transformDirection(e.x - left, e.y - top); de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; - de.xOffset = xoff; - de.yOffset = yoff; DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, { handlers: { dragComplete: action(emptyFunction) }, hideSource: !de.dropAction @@ -423,9 +383,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action - updateClusters() { + updateClusters(useClusters: boolean) { + this.Document.useClusters = useClusters; this.sets.length = 0; - this.childDocs.map(c => { + this.childLayoutPairs.map(pair => pair.layout).map(c => { let included = []; for (let i = 0; i < this.sets.length; i++) { for (let member of this.sets[i]) { @@ -453,20 +414,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action updateCluster(doc: Doc) { + let childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.props.Document.useClusters) { this.sets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; this.sets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, this.childDocs) !== -1 && this.boundsOverlap(doc, member)) { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && this.boundsOverlap(doc, member)) { doc.cluster = i; } })); - if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length)) { + if (doc.cluster === -1 && preferredInd !== -1 && (!this.sets[preferredInd] || !this.sets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } this.sets.map((set, i) => { - if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, this.childDocs) !== -1).length) { + if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { doc.cluster = i; } }); @@ -481,22 +443,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } getClusterColor = (doc: Doc) => { - if (this.props.Document.useClusters) { - let cluster = NumCast(doc.cluster); + let clusterColor = ""; + let cluster = NumCast(doc.cluster); + if (this.Document.useClusters) { if (this.sets.length <= cluster) { - setTimeout(() => this.updateCluster(doc), 0);// this.updateClusters(), 0); - return ""; + setTimeout(() => this.updateCluster(doc), 0); + } else { + // choose a cluster color from a palette + let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; + clusterColor = colors[cluster % colors.length]; + let set = this.sets.length > cluster ? this.sets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)) : undefined; + // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document + set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); + set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); } - let set = this.sets.length > cluster ? this.sets[cluster] : undefined; - let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; - let clusterColor = colors[cluster % colors.length]; - set && set.filter(s => !s.isBackground).map(s => - s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); - set && set.filter(s => s.isBackground).map(s => - s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); - return clusterColor; } - return ""; + return clusterColor; } @action @@ -528,40 +490,39 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childDocs || []; + let docs = this.childLayoutPairs.map(pair => pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - // if (!this.isAnnotationOverlay) { - // PDFMenu.Instance.fadeOut(true); - // let minx = docs.length ? NumCast(docs[0].x) : 0; - // let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; - // let miny = docs.length ? NumCast(docs[0].y) : 0; - // let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; - // let ranges = docs.filter(doc => doc).reduce((range, doc) => { - // let x = NumCast(doc.x); - // let xe = x + NumCast(doc.width); - // let y = NumCast(doc.y); - // let ye = y + NumCast(doc.height); - // return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], - // [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; - // }, [[minx, maxx], [miny, maxy]]); - // let ink = Cast(this.fieldExtensionDoc.ink, InkField); - // if (ink && ink.inkData) { - // ink.inkData.forEach((value: StrokeData, key: string) => { - // let bounds = InkingCanvas.StrokeRect(value); - // ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; - // ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; - // }); - // } - - // let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling(), - // this._pheight / this.zoomScaling()); - // let panelwidth = panelDim[0]; - // let panelheight = panelDim[1]; - // if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2; - // if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2; - // if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2; - // if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2; - // } + if (!this.isAnnotationOverlay) { + PDFMenu.Instance.fadeOut(true); + let minx = docs.length ? NumCast(docs[0].x) : 0; + let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; + let miny = docs.length ? NumCast(docs[0].y) : 0; + let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; + let ranges = docs.filter(doc => doc).reduce((range, doc) => { + let x = NumCast(doc.x); + let xe = x + NumCast(doc.width); + let y = NumCast(doc.y); + let ye = y + NumCast(doc.height); + return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], + [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; + }, [[minx, maxx], [miny, maxy]]); + let ink = Cast(this.fieldExtensionDoc.ink, InkField); + if (ink && ink.inkData) { + ink.inkData.forEach((value: StrokeData, key: string) => { + let bounds = InkingCanvas.StrokeRect(value); + ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; + ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; + }); + } + + let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; + let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling() * cscale, + this._pheight / this.zoomScaling() * cscale); + if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2; + if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2; + if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2; + if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2; + } this.setPan(x - dx, y - dy); this._lastX = e.pageX; this._lastY = e.pageY; @@ -575,31 +536,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (BoolCast(this.props.Document.lockedPosition)) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); - return; } - - let childSelected = this.childDocs.some(doc => { - var dv = DocumentManager.Instance.getDocumentView(doc); - return dv && SelectionManager.IsSelected(dv) ? true : false; - }); - if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) { - return; - } - e.stopPropagation(); - - // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix. - // if (e.ctrlKey && this.props.Document.scrollHeight === undefined) { - // let deltaScale = (1 - (e.deltaY / coefficient)); - // let nw = this.nativeWidth * deltaScale; - // let nh = this.nativeHeight * deltaScale; - // if (nw && nh) { - // this.props.Document.nativeWidth = nw; - // this.props.Document.nativeHeight = nh; - // } - // e.preventDefault(); - // } - // else - { + else if (this.props.active()) { + e.stopPropagation(); let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); @@ -628,46 +567,38 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - onDrop = (e: React.DragEvent): void => { + onDrop = async (e: React.DragEvent): Promise<void> => { var pt = this.getTransform().transformPoint(e.pageX, e.pageY); - super.onDrop(e, { x: pt[0], y: pt[1] }); - } - - onDragOver = (): void => { + await super.onDrop(e, { x: pt[0], y: pt[1] }); } bringToFront = (doc: Doc, sendToBack?: boolean) => { if (sendToBack || doc.isBackground) { doc.zIndex = 0; - return; } - const docs = this.childDocs; - docs.slice().sort((doc1, doc2) => { - if (doc1 === doc) return 1; - if (doc2 === doc) return -1; - return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); - }).forEach((doc, index) => doc.zIndex = index + 1); - doc.zIndex = docs.length + 1; + else { + const docs = this.childLayoutPairs.map(pair => pair.layout); + docs.slice().sort((doc1, doc2) => { + if (doc1 === doc) return 1; + if (doc2 === doc) return -1; + return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); + }).forEach((doc, index) => doc.zIndex = index + 1); + doc.zIndex = docs.length + 1; + } } - focusDocument = (doc: Doc, willZoom: boolean, scale?: number) => { - const panX = this.Document.panX; - const panY = this.Document.panY; - const id = this.Document[Id]; + focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => { const state = HistoryUtil.getState(); - state.initializers = state.initializers || {}; // TODO This technically isn't correct if type !== "doc", as // currently nothing is done, but we should probably push a new state - if (state.type === "doc" && panX !== undefined && panY !== undefined) { - const init = state.initializers[id]; + if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) { + const init = state.initializers![this.Document[Id]]; if (!init) { - state.initializers[id] = { - panX, panY - }; + state.initializers![this.Document[Id]] = { panX: this.Document.panX, panY: this.Document.panY }; HistoryUtil.pushState(state); - } else if (init.panX !== panX || init.panY !== panY) { - init.panX = panX; - init.panY = panY; + } else if (init.panX !== this.Document.panX || init.panY !== this.Document.panY) { + init.panX = this.Document.panX; + init.panY = this.Document.panY; HistoryUtil.pushState(state); } } @@ -675,8 +606,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; const newState = HistoryUtil.getState(); - (newState.initializers || (newState.initializers = {}))[id] = { panX: newPanX, panY: newPanY }; + newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); + + let px = this.Document.panX; + let py = this.Document.panY; + let s = this.Document.scale; this.setPan(newPanX, newPanY); this.props.Document.panTransformType = "Ease"; @@ -684,7 +619,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (willZoom) { this.setScaleToZoom(doc, scale); } - + console.log("Focused " + this.Document.title + " " + s); + afterFocus && setTimeout(() => { + if (afterFocus && afterFocus()) { + console.log("UnFocused " + this.Document.title + " " + s); + this.Document.panX = px; + this.Document.panY = py; + this.Document.scale = s; + } + }, 1000); } setScaleToZoom = (doc: Doc, scale: number = 0.5) => { @@ -716,13 +659,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, - onClick: this.props.onClick, + ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves + onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, PanelWidth: childLayout[WidthSym], PanelHeight: childLayout[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.CollectionView.props.Document, focus: this.focusDocument, backgroundColor: this.getClusterColor, parentActive: this.props.active, @@ -741,6 +686,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, + ruleProvider: this.props.ruleProvider, onClick: this.props.onClick, ScreenToLocalTransform: this.getTransform, renderDepth: this.props.renderDepth, @@ -748,6 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { PanelHeight: layoutDoc[HeightSym], ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.CollectionView.props.Document, focus: this.focusDocument, backgroundColor: returnEmptyString, parentActive: this.props.active, @@ -760,13 +707,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }; } - getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } { - const result = script.script.run(params); - if (!result.success) { - return {}; + getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } { + const script = this.Document.arrangeScript; + const result = script && script.script.run(params, console.log); + if (result && result.success) { + return { ...result, transition: "transform 1s" }; } - let doc = params.doc; - return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result; + return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; } viewDefsToJSX = (views: PivotView.PivotData[]) => { @@ -829,14 +776,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (this.Document.usePivotLayout) return PivotView.elements(this); let curPage = FieldValue(this.Document.curPage, -1); const initScript = this.Document.arrangeInit; - const script = this.Document.arrangeScript; let state: any = undefined; - let docs = this.childDocs; - let overlayDocs = DocListCast(this.props.Document.localOverlays); - overlayDocs && docs.push(...overlayDocs); + let pairs = this.childLayoutPairs; let elements: ViewDefResult[] = []; if (initScript) { - const initResult = initScript.script.run({ docs, collection: this.Document }); + const initResult = initScript.script.run({ docs: pairs.map(pair => pair.layout), collection: this.Document }, console.log); if (initResult.success) { const result = initResult.result; const { state: scriptState, views } = result; @@ -844,25 +788,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { elements = this.viewDefsToJSX(views); } } - let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { - var page = NumCast(doc.page, -1); - if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { - let minim = BoolCast(doc.isMinimized); - if (minim === undefined || !minim) { - const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : - { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; - state = pos.state === undefined ? state : pos.state; - let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, doc); - if (pair.layout && !(pair.data instanceof Promise)) { - prev.push({ - ele: <CollectionFreeFormDocumentView key={doc[Id]} - jitterRotation={NumCast(this.props.Document.jitterRotation)} - x={script ? pos.x : undefined} y={script ? pos.y : undefined} - width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, - bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } - }); - } - } + let docviews = pairs.reduce((prev, pair) => { + var page = NumCast(pair.layout.page, -1); + if (!pair.layout.isMinimized && ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1)) { + const pos = this.getCalculatedPositions({ doc: pair.layout, index: prev.length, collection: this.Document, docs: pairs.map(pair => pair.layout), state }); + state = pos.state === undefined ? state : pos.state; + prev.push({ + ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} + ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} + jitterRotation={NumCast(this.props.Document.jitterRotation)} + transition={pos.transition} x={pos.x} y={pos.y} width={pos.width} height={pos.height} + {...this.getChildDocumentViewProps(pair.layout, pair.data)} />, + bounds: { x: pos.x || 0, y: pos.y || 0, z: pos.z, width: pos.width || 0, height: pos.height || 0 } + }); } return prev; }, elements); @@ -872,22 +810,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed.struct get views() { - let source = this.elements; - return source.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed.struct get overlayViews() { return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); } - @action onCursorMove = (e: React.PointerEvent) => { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } - fitToContainer = async () => this.props.Document.fitToBox = !this.fitToBox; - arrangeContents = async () => { const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); UndoManager.RunInBatch(() => { @@ -912,98 +846,86 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, "arrange contents"); } + autoFormat = () => { + this.Document.isRuleProvider = !this.Document.isRuleProvider; + // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading. + this.Document.isRuleProvider && this.childLayoutPairs.map(pair => + // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template) + DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => { + let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); + let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout; + if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) { + Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor; + } + }) + ); + } + analyzeStrokes = async () => { - let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); - if (!data) { - return; + let data = Cast(this.fieldExtensionDoc.ink, InkField); + if (data) { + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData); } - let relevantKeys = ["inkAnalysis", "handwriting"]; - CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, relevantKeys, data.inkData); } onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; - if (this.childDocs.some(d => d.isTemplate)) { - layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); + if (this.childDocs.some(d => BoolCast(d.isTemplate))) { + layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); } layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); - layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); - layoutItems.push({ - description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, - event: async () => { - Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes - Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; - this.props.Document.useClusters = !this.props.Document.useClusters; - this.updateClusters(); - }, - icon: !this.props.Document.useClusters ? "braille" : "braille" - }); - this.props.Document.useClusters && layoutItems.push({ - description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, - event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, - icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard" - }); + layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); + layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" }); layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" }); layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" }); layoutItems.push({ - description: "Import document", icon: "upload", event: () => { + description: "Import document", icon: "upload", event: ({ x, y }) => { const input = document.createElement("input"); input.type = "file"; input.accept = ".zip"; input.onchange = async _e => { - const files = input.files; - if (!files) return; - const file = files[0]; - let formData = new FormData(); - formData.append('file', file); - formData.append('remap', "true"); const upload = Utils.prepend("/uploadDoc"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json === "error") { - return; - } - const doc = await DocServer.GetRefField(json); - if (!doc || !(doc instanceof Doc)) { - return; + let formData = new FormData(); + const file = input.files && input.files[0]; + if (file) { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + if (doc instanceof Doc) { + const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument && this.props.addDocument(doc, false); + } + } } - const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY); - doc.x = x, doc.y = y; - this.props.addDocument && - this.props.addDocument(doc, false); }; input.click(); } }); - let noteItems: ContextMenuProps[] = []; - if (CurrentUserUtils.UserDocument) { - let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); - notes.map((node, i) => noteItems.push({ description: (i + 1) + ": " + StrCast(node.title), event: () => this.createText(i), icon: "eye" })); - } - layoutItems.push({ description: "Add Note ...", subitems: noteItems, icon: "eye" }); + layoutItems.push({ + description: "Add Note ...", + subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({ + description: (i + 1) + ": " + StrCast(note.title), + event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument({ width: 200, height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], autoHeight: true, layout: note, title: StrCast(note.title) })), + icon: "eye" + })) as ContextMenuProps[], + icon: "eye" + }); ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" }); } - createText = (noteStyle: number) => { - let pt = this.getTransform().transformPoint(ContextMenu.Instance.pageX, ContextMenu.Instance.pageY); - if (CurrentUserUtils.UserDocument) { - let notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data); - let text = Docs.Create.TextDocument({ width: 200, height: 100, x: pt[0], y: pt[1], autoHeight: true, title: StrCast(notes[noteStyle % notes.length].title) }); - text.layout = notes[noteStyle % notes.length]; - this.addLiveTextBox(text); - } - } private childViews = () => [ <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />, ...this.views ] - private overlayChildViews = () => { - return [...this.overlayViews]; - } public static AddCustomLayout(doc: Doc, dataKey: string): () => void { return () => { @@ -1034,15 +956,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } render() { + // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) this.props.Document.fitX = this.actualContentBounds && this.actualContentBounds.x; this.props.Document.fitY = this.actualContentBounds && this.actualContentBounds.y; this.props.Document.fitW = this.actualContentBounds && (this.actualContentBounds.r - this.actualContentBounds.x); this.props.Document.fitH = this.actualContentBounds && (this.actualContentBounds.b - this.actualContentBounds.y); + // if fieldExt is set, then children will be stored in the extension document for the fieldKey. + // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document + Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey); const easing = () => this.props.Document.panTransformType === "Ease"; - Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); return ( <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel} - onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onContextMenu={this.onContextMenu}> + onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected} addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}> @@ -1056,7 +981,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> </CollectionFreeFormViewPannableContents> </MarqueeView> - {this.overlayChildViews()} + {this.overlayViews} <CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} /> </div> ); @@ -1077,7 +1002,6 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & @observer class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { @computed get backgroundView() { - let props = this.props; return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index fe48a3485..bbea4a555 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,28 +1,24 @@ -import * as htmlToImage from "html-to-image"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, FieldResult, DocListCast } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Doc, DocListCast } from "../../../../new_fields/Doc"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; +import { listSpec } from "../../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; +import { ComputedField } from "../../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../../new_fields/Types"; +import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; import { Utils } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; -import { Templates } from "../../Templates"; import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); -import { SchemaHeaderField, RandomPastel } from "../../../../new_fields/SchemaHeaderField"; -import { string } from "prop-types"; -import { listSpec } from "../../../../new_fields/Schema"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -232,18 +228,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; } - get ink() { - let container = this.props.container.props.Document; - let containerKey = this.props.container.props.fieldKey; - let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); - return Cast(extensionDoc.ink, InkField); + get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. + let cprops = this.props.container.props; + return Cast(Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink, InkField); } set ink(value: InkField | undefined) { - let container = Doc.GetProto(this.props.container.props.Document); - let containerKey = this.props.container.props.fieldKey; - let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); - extensionDoc.ink = value; + let cprops = this.props.container.props; + Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink = value; } @undoBatch @@ -287,7 +279,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map<string, number>(); [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { - let bg = StrCast(child.backgroundColor); + let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); @@ -309,13 +301,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps> defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : chosenColor, width: bounds.width, height: bounds.height, - title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", + title: "a nested collection", }); let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; this.marqueeInkDelete(inkData); - if (e.key === "s") { + if (e.key === "s" || e.key === "S") { selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; @@ -324,39 +316,23 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return d; }); newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - newCollection.proto!.summaryDoc = summary; - selected = [newCollection]; + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]); newCollection.x = bounds.left + bounds.width; - summary.proto!.subBulletDocs = new List<Doc>(selected); - summary.templates = new List<string>([Templates.Bullet.Layout]); - let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); - container.viewType = CollectionViewType.Stacking; - container.autoHeight = true; - this.props.addLiveTextDocument(container); - // }); - } else if (e.key === "S") { - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; - return d; - }); - newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - newCollection.proto!.summaryDoc = summary; - selected = [newCollection]; - newCollection.x = bounds.left + bounds.width; - //this.props.addDocument(newCollection, false); - summary.proto!.summarizedDocs = new List<Doc>(selected); - summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" - summary.autoHeight = true; - - this.props.addLiveTextDocument(summary); + Doc.GetProto(newCollection).summaryDoc = summary; + Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); + if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); + container.viewType = CollectionViewType.Stacking; + container.autoHeight = true; + Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" + this.props.addLiveTextDocument(container); + } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them + Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight" + this.props.addLiveTextDocument(summary); + } } else { - newCollection.ruleProvider = this.props.container.props.Document; this.props.addDocument(newCollection, false); this.props.selectDocuments([newCollection]); } |
