diff options
Diffstat (limited to 'src/client/views/collections')
17 files changed, 1014 insertions, 832 deletions
| diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 0c1cd7b8f..76adfcdcd 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -1,13 +1,14 @@  import { action, computed } from 'mobx';  import { observer } from 'mobx-react';  import * as React from 'react'; -import { Document } from '../../../fields/Document'; -import { Field, FieldValue, FieldWaiting } from '../../../fields/Field'; -import { KeyStore } from '../../../fields/KeyStore'; -import { ListField } from '../../../fields/ListField'; -import { NumberField } from '../../../fields/NumberField';  import { ContextMenu } from '../ContextMenu';  import { FieldViewProps } from '../nodes/FieldView'; +import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types'; +import { Doc, FieldResult, Opt } from '../../../new_fields/Doc'; +import { listSpec } from '../../../new_fields/Schema'; +import { List } from '../../../new_fields/List'; +import { Id } from '../../../new_fields/RefField'; +import { SelectionManager } from '../../util/SelectionManager';  export enum CollectionViewType {      Invalid, @@ -18,9 +19,9 @@ export enum CollectionViewType {  }  export interface CollectionRenderProps { -    addDocument: (document: Document, allowDuplicates?: boolean) => boolean; -    removeDocument: (document: Document) => boolean; -    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; +    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; +    removeDocument: (document: Doc) => boolean; +    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;      active: () => boolean;      whenActiveChanged: (isActive: boolean) => void;  } @@ -37,11 +38,9 @@ export interface CollectionViewProps extends FieldViewProps {  export class CollectionBaseView extends React.Component<CollectionViewProps> {      get collectionViewType(): CollectionViewType | undefined {          let Document = this.props.Document; -        let viewField = Document.GetT(KeyStore.ViewType, NumberField); -        if (viewField === FieldWaiting) { -            return undefined; -        } else if (viewField) { -            return viewField.Data; +        let viewField = Cast(Document.viewType, "number"); +        if (viewField !== undefined) { +            return viewField;          } else {              return CollectionViewType.Invalid;          } @@ -60,101 +59,73 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {          this.props.whenActiveChanged(isActive);      } -    createsCycle(documentToAdd: Document, containerDocument: Document): boolean { -        if (!(documentToAdd instanceof Document)) { +    createsCycle(documentToAdd: Doc, containerDocument: Doc): boolean { +        if (!(documentToAdd instanceof Doc)) {              return false;          } -        let data = documentToAdd.GetList(KeyStore.Data, [] as Document[]); +        let data = Cast(documentToAdd.data, listSpec(Doc), []);          for (const doc of data.filter(d => d instanceof Document)) {              if (this.createsCycle(doc, containerDocument)) {                  return true;              }          } -        let annots = documentToAdd.GetList(KeyStore.Annotations, [] as Document[]); +        let annots = Cast(documentToAdd.annotations, listSpec(Doc), []);          for (const annot of annots) {              if (this.createsCycle(annot, containerDocument)) {                  return true;              }          } -        for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) { -            if (containerProto.Id === documentToAdd.Id) { +        for (let containerProto: Opt<Doc> = containerDocument; containerProto !== undefined; containerProto = FieldValue(containerProto.proto)) { +            if (containerProto[Id] === documentToAdd[Id]) {                  return true;              }          }          return false;      } -    @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? +    @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }      @action.bound -    addDocument(doc: Document, allowDuplicates: boolean = false): boolean { +    addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {          let props = this.props; -        var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); -        doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); +        var curPage = Cast(props.Document.curPage, "number", -1); +        Doc.SetOnPrototype(doc, "page", curPage);          if (curPage >= 0) { -            doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document); +            Doc.SetOnPrototype(doc, "annotationOn", props.Document);          } -        if (props.Document.Get(props.fieldKey) instanceof Field) { +        if (!this.createsCycle(doc, props.Document)) {              //TODO This won't create the field if it doesn't already exist -            const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()); -            if (!this.createsCycle(doc, props.Document)) { -                if (!value.some(v => v.Id === doc.Id) || allowDuplicates) { +            const value = Cast(props.Document[props.fieldKey], listSpec(Doc)); +            if (value !== undefined) { +                if (allowDuplicates || !value.some(v => v[Id] === doc[Id])) {                      value.push(doc);                  } -                return true; -            } -            else { -                return false; +            } else { +                this.props.Document[this.props.fieldKey] = new List([doc]);              } -        } else { -            let proto = props.Document.GetPrototype(); -            if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) { -                const field = new ListField([doc]); -                // const script = CompileScript(` -                //     if(added) { -                //         console.log("added " + field.Title + " " + doc.Title); -                //     } else { -                //         console.log("removed " + field.Title + " " + doc.Title); -                //     } -                // `, { -                //         addReturn: false, -                //         params: { -                //             field: Document.name, -                //             added: "boolean" -                //         }, -                //         capturedVariables: { -                //             doc: this.props.Document -                //         } -                //     }); -                // if (script.compiled) { -                //     field.addScript(new ScriptField(script)); -                // } -                props.Document.SetOnPrototype(props.fieldKey, field); +            // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument? +            if (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid) { +                let zoom = NumCast(this.props.Document.scale, 1); +                doc.zoomBasis = zoom;              } -            else { -                return false; -            } -        } -        if (true || this.isAnnotationOverlay) { -            doc.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));          }          return true;      }      @action.bound -    removeDocument(doc: Document): boolean { +    removeDocument(doc: Doc): boolean {          const props = this.props;          //TODO This won't create the field if it doesn't already exist -        const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()); +        const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);          let index = -1;          for (let i = 0; i < value.length; i++) { -            if (value[i].Id === doc.Id) { +            if (value[i][Id] === doc[Id]) {                  index = i;                  break;              }          } -        doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => { +        PromiseValue(Cast(doc.annotationOn, Doc)).then((annotationOn) => {              if (annotationOn === props.Document) { -                doc.Set(KeyStore.AnnotationOn, undefined, true); +                doc.annotationOn = undefined;              }          }); @@ -169,11 +140,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {      }      @action.bound -    moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => boolean): boolean { +    moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {          if (this.props.Document === targetCollection) {              return true;          }          if (this.removeDocument(doc)) { +            SelectionManager.DeselectAll();              return addDocument(doc);          }          return false; @@ -189,7 +161,9 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {          };          const viewtype = this.collectionViewType;          return ( -            <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}> +            <div className={this.props.className || "collectionView-cont"} +                style={{ borderRadius: "inherit", pointerEvents: "all" }} +                onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>                  {viewtype !== undefined ? this.props.children(viewtype, props) : (null)}              </div>          ); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e4c647635..cfb1aef7d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,36 +1,37 @@  import * as GoldenLayout from "golden-layout";  import 'golden-layout/src/css/goldenlayout-base.css';  import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, observable, reaction, trace } from "mobx"; +import { action, observable, reaction, trace, runInAction } from "mobx";  import { observer } from "mobx-react";  import * as ReactDOM from 'react-dom'; -import { Document } from "../../../fields/Document"; -import { KeyStore } from "../../../fields/KeyStore";  import Measure from "react-measure"; -import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field"; -import { Utils, returnTrue, emptyFunction, emptyDocFunction, returnOne } from "../../../Utils"; -import { Server } from "../../Server"; -import { undoBatch } from "../../util/UndoManager"; +import { Utils, returnTrue, emptyFunction, returnOne, returnZero } from "../../../Utils"; +import { undoBatch, UndoManager } from "../../util/UndoManager";  import { DocumentView } from "../nodes/DocumentView";  import "./CollectionDockingView.scss";  import React = require("react");  import { SubCollectionViewProps } from "./CollectionSubView"; -import { ServerUtils } from "../../../server/ServerUtil";  import { DragManager, DragLinksAsDocuments } from "../../util/DragManager"; -import { TextField } from "../../../fields/TextField"; -import { ListField } from "../../../fields/ListField"; -import { Transform } from '../../util/Transform' +import { Transform } from '../../util/Transform'; +import { Doc, Opt, Field } from "../../../new_fields/Doc"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { List } from "../../../new_fields/List"; +import { DocServer } from "../../DocServer"; +import { listSpec } from "../../../new_fields/Schema"; +import { Id, FieldId } from "../../../new_fields/RefField"; +import { faSignInAlt } from "@fortawesome/free-solid-svg-icons";  @observer  export class CollectionDockingView extends React.Component<SubCollectionViewProps> {      public static Instance: CollectionDockingView; -    public static makeDocumentConfig(document: Document) { +    public static makeDocumentConfig(document: Doc, width?: number) {          return {              type: 'react-component',              component: 'DocumentFrameRenderer', -            title: document.Title, +            title: document.title, +            width: width,              props: { -                documentId: document.Id, +                documentId: document[Id],                  //collectionDockingView: CollectionDockingView.Instance              }          }; @@ -38,7 +39,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      private _goldenLayout: any = null;      private _containerRef = React.createRef<HTMLDivElement>(); -    private _fullScreen: any = null;      private _flush: boolean = false;      private _ignoreStateChange = ""; @@ -48,14 +48,18 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp          (window as any).React = React;          (window as any).ReactDOM = ReactDOM;      } -    public StartOtherDrag(dragDocs: Document[], e: any) { +    hack: boolean = false; +    undohack: any = null; +    public StartOtherDrag(dragDocs: Doc[], e: any) { +        this.hack = true; +        this.undohack = UndoManager.StartBatch("goldenDrag");          dragDocs.map(dragDoc =>              this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.                  onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));      }      @action -    public OpenFullScreen(document: Document) { +    public OpenFullScreen(document: Doc) {          let newItemStackConfig = {              type: 'stack',              content: [CollectionDockingView.makeDocumentConfig(document)] @@ -64,26 +68,19 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp          this._goldenLayout.root.contentItems[0].addChild(docconfig);          docconfig.callDownwards('_$init');          this._goldenLayout._$maximiseItem(docconfig); -        this._fullScreen = docconfig;          this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());          this.stateChanged();      } -    @action -    public CloseFullScreen() { -        if (this._fullScreen) { -            this._goldenLayout._$minimiseItem(this._fullScreen); -            this._goldenLayout.root.contentItems[0].removeChild(this._fullScreen); -            this._fullScreen = null; -            this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); -            this.stateChanged(); -        } -    }      //      //  Creates a vertical split on the right side of the docking view, and then adds the Document to that split      //      @action -    public AddRightSplit(document: Document, minimize: boolean = false) { +    public AddRightSplit(document: Doc, minimize: boolean = false) { +        let docs = Cast(this.props.Document.data, listSpec(Doc)); +        if (docs) { +            docs.push(document); +        }          let newItemStackConfig = {              type: 'stack',              content: [CollectionDockingView.makeDocumentConfig(document)] @@ -119,7 +116,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      }      setupGoldenLayout() { -        var config = this.props.Document.GetText(KeyStore.Data, ""); +        var config = StrCast(this.props.Document.dockingConfig);          if (config) {              if (!this._goldenLayout) {                  this._goldenLayout = new GoldenLayout(JSON.parse(config)); @@ -154,7 +151,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      componentDidMount: () => void = () => {          if (this._containerRef.current) {              reaction( -                () => this.props.Document.GetText(KeyStore.Data, ""), +                () => StrCast(this.props.Document.dockingConfig),                  () => {                      if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {                          setTimeout(() => this.setupGoldenLayout(), 1); @@ -202,8 +199,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp              let y = e.clientY;              let docid = (e.target as any).DashDocId;              let tab = (e.target as any).parentElement as HTMLElement; -            Server.GetField(docid, action(async (sourceDoc: Opt<Field>) => -                (sourceDoc instanceof Document) && DragLinksAsDocuments(tab, x, y, sourceDoc))); +            DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) => +                (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));          } else              if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {                  e.stopPropagation(); @@ -212,12 +209,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp                  let y = e.clientY;                  let docid = (e.target as any).DashDocId;                  let tab = (e.target as any).parentElement as HTMLElement; -                Server.GetField(docid, action((f: Opt<Field>) => { -                    if (f instanceof Document) { +                DocServer.GetRefField(docid).then(action((f: Opt<Field>) => { +                    if (f instanceof Doc) {                          DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,                              {                                  handlers: { -                                    dragComplete: action(emptyFunction), +                                    dragComplete: emptyFunction,                                  },                                  hideSource: false                              }); @@ -235,7 +232,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      @undoBatch      stateChanged = () => {          var json = JSON.stringify(this._goldenLayout.toConfig()); -        this.props.Document.SetText(KeyStore.Data, json); +        this.props.Document.dockingConfig = json; +        if (this.undohack && !this.hack) { +            this.undohack.end(); +            this.undohack = undefined; +        } +        this.hack = false;      }      itemDropped = () => { @@ -251,35 +253,38 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp      tabCreated = (tab: any) => {          if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { -            Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => { -                if (f !== undefined && f instanceof Document) { -                    f.GetTAsync(KeyStore.Title, TextField, (tfield) => { -                        if (tfield !== undefined) { -                            tab.titleElement[0].textContent = f.Title; -                        } -                    }); -                    f.GetTAsync(KeyStore.LinkedFromDocs, ListField).then(lf => -                        f.GetTAsync(KeyStore.LinkedToDocs, ListField).then(lt => { -                            let count = (lf ? lf.Data.length : 0) + (lt ? lt.Data.length : 0); -                            let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`); -                            tab.element.append(counter); -                            counter.DashDocId = tab.contentItem.config.props.documentId; -                            tab.reactionDisposer = reaction(() => [f.GetT(KeyStore.LinkedFromDocs, ListField), f.GetT(KeyStore.LinkedToDocs, ListField)], -                                (lists) => { -                                    let count = (lists.length > 0 && lists[0] && lists[0]!.Data ? lists[0]!.Data.length : 0) + -                                        (lists.length > 1 && lists[1] && lists[1]!.Data ? lists[1]!.Data.length : 0); -                                    counter.innerHTML = count; -                                }); -                        })); +            DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async f => { +                if (f instanceof Doc) { +                    const title = Cast(f.title, "string"); +                    if (title !== undefined) { +                        tab.titleElement[0].textContent = title; +                    } +                    const lf = await Cast(f.linkedFromDocs, listSpec(Doc)); +                    const lt = await Cast(f.linkedToDocs, listSpec(Doc)); +                    let count = (lf ? lf.length : 0) + (lt ? lt.length : 0); +                    let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`); +                    tab.element.append(counter); +                    counter.DashDocId = tab.contentItem.config.props.documentId; +                    tab.reactionDisposer = reaction((): [List<Field> | null | undefined, List<Field> | null | undefined] => [lf, lt], +                        ([linkedFrom, linkedTo]) => { +                            let count = (linkedFrom ? linkedFrom.length : 0) + (linkedTo ? linkedTo.length : 0); +                            counter.innerHTML = count; +                        });                      tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;                  } -            })); +            });          }          tab.closeElement.off('click') //unbind the current click handler              .click(function () {                  if (tab.reactionDisposer) {                      tab.reactionDisposer();                  } +                DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async f => runInAction(() => { +                    if (f instanceof Doc) { +                        let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc)); +                        docs && docs.indexOf(f) !== -1 && docs.splice(docs.indexOf(f), 1); +                    } +                }));                  tab.contentItem.remove();              });      } @@ -296,7 +301,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp          stack.header.controlsContainer.find('.lm_popout') //get the close icon              .off('click') //unbind the current click handler              .click(action(function () { -                var url = ServerUtils.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId); +                var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);                  let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");              }));      } @@ -315,30 +320,29 @@ interface DockedFrameProps {  }  @observer  export class DockedFrameRenderer extends React.Component<DockedFrameProps> { -      _mainCont = React.createRef<HTMLDivElement>();      @observable private _panelWidth = 0;      @observable private _panelHeight = 0; -    @observable private _document: Opt<Document>; +    @observable private _document: Opt<Doc>;      constructor(props: any) {          super(props); -        Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document)); +        DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));      } -    nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); -    nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); +    nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth); +    nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);      contentScaling = () => { -        let wscale = this._panelWidth / (this.nativeWidth() ? this.nativeWidth() : this._panelWidth); -        if (wscale * this.nativeHeight() > this._panelHeight) -            return this._panelHeight / (this.nativeHeight() ? this.nativeHeight() : this._panelHeight); -        return wscale; +        const nativeH = this.nativeHeight(); +        const nativeW = this.nativeWidth(); +        let wscale = this._panelWidth / nativeW; +        return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;      }      ScreenToLocalTransform = () => {          if (this._mainCont.current && this._mainCont.current.children) { -            let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement); -            scale = Utils.GetScreenTransform(this._mainCont.current!).scale; +            let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement); +            scale = Utils.GetScreenTransform(this._mainCont.current).scale;              return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());          }          return Transform.Identity(); @@ -346,10 +350,13 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {      get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }      get content() { +        if (!this._document) +            return (null);          return (              <div className="collectionDockingView-content" ref={this._mainCont}                  style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> -                <DocumentView key={this._document!.Id} Document={this._document!} +                <DocumentView key={this._document![Id]} Document={this._document!} +                    toggleMinimized={emptyFunction}                      addDocument={undefined}                      removeDocument={undefined}                      ContentScaling={this.contentScaling} @@ -360,15 +367,16 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {                      selectOnLoad={false}                      parentActive={returnTrue}                      whenActiveChanged={emptyFunction} -                    focus={emptyDocFunction} +                    focus={emptyFunction}                      ContainingCollectionView={undefined} /> -            </div>); +            </div >);      }      render() { +        let theContent = this.content;          return !this._document ? (null) :              <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}> -                {({ measureRef }) => <div ref={measureRef}>  {this.content} </div>} +                {({ measureRef }) => <div ref={measureRef}>  {theContent} </div>}              </Measure>;      }  }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss index 0eca3f1cd..f6fb79582 100644 --- a/src/client/views/collections/CollectionPDFView.scss +++ b/src/client/views/collections/CollectionPDFView.scss @@ -1,20 +1,39 @@  .collectionPdfView-buttonTray { -    top : 25px; +    top : 15px;      left : 20px;      position: relative;       transform-origin: left top;      position: absolute;  } +.collectionPdfView-thumb { +    width:25px; +    height:25px; +    transform-origin: left top; +    position: absolute; +    background: darkgray; +} +.collectionPdfView-slider { +    width:25px; +    height:25px; +    transform-origin: left top; +    position: absolute; +    background: lightgray; +}  .collectionPdfView-cont{      width: 100%;      height: 100%;      position: absolute;       top: 0;      left:0; -     +} +.collectionPdfView-cont-dragging { +    span { +        user-select: none; +    }  }  .collectionPdfView-backward {      color : white; +    font-size: 24px;      top :0px;      left : 0px;      position: absolute; @@ -22,8 +41,9 @@  }  .collectionPdfView-forward {      color : white; +    font-size: 24px;      top :0px; -    left : 35px; +    left : 45px;      position: absolute;      background-color: rgba(50, 50, 50, 0.2);  }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 229bc4059..b3762206a 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,6 +1,5 @@ -import { action } from "mobx"; +import { action, observable } from "mobx";  import { observer } from "mobx-react"; -import { KeyStore } from "../../../fields/KeyStore";  import { ContextMenu } from "../ContextMenu";  import "./CollectionPDFView.scss";  import React = require("react"); @@ -8,32 +7,60 @@ import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormV  import { FieldView, FieldViewProps } from "../nodes/FieldView";  import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";  import { emptyFunction } from "../../../Utils"; +import { NumCast } from "../../../new_fields/Types"; +import { Id } from "../../../new_fields/RefField";  @observer  export class CollectionPDFView extends React.Component<FieldViewProps> { -    public static LayoutString(fieldKey: string = "DataKey") { +    public static LayoutString(fieldKey: string = "data") {          return FieldView.LayoutString(CollectionPDFView, fieldKey);      } +    @observable _inThumb = false; -    private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); } -    private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); } -    @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : -1; -    @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1; +    private set curPage(value: number) { this.props.Document.curPage = value; } +    private get curPage() { return NumCast(this.props.Document.curPage, -1); } +    private get numPages() { return NumCast(this.props.Document.numPages); } +    @action onPageBack = () => this.curPage > 1 ? (this.props.Document.curPage = this.curPage - 1) : -1; +    @action onPageForward = () => this.curPage < this.numPages ? (this.props.Document.curPage = this.curPage + 1) : -1; +    @action +    onThumbDown = (e: React.PointerEvent) => { +        document.addEventListener("pointermove", this.onThumbMove, false); +        document.addEventListener("pointerup", this.onThumbUp, false); +        e.stopPropagation(); +        this._inThumb = true; +    } +    @action +    onThumbMove = (e: PointerEvent) => { +        let pso = (e.clientY - (e as any).target.parentElement.getBoundingClientRect().top) / (e as any).target.parentElement.getBoundingClientRect().height; +        this.curPage = Math.trunc(Math.min(this.numPages, pso * this.numPages + 1)); +        e.stopPropagation(); +    } +    @action +    onThumbUp = (e: PointerEvent) => { +        this._inThumb = false; +        document.removeEventListener("pointermove", this.onThumbMove); +        document.removeEventListener("pointerup", this.onThumbUp); +    } +    nativeWidth = () => NumCast(this.props.Document.nativeWidth); +    nativeHeight = () => NumCast(this.props.Document.nativeHeight);      private get uIButtons() { -        let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); +        let ratio = (this.curPage - 1) / this.numPages * 100;          return ( -            <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}> +            <div className="collectionPdfView-buttonTray" key="tray" style={{ height: "100%" }}>                  <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>                  <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button> +                <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} > +                    <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} /> +                </div>              </div>          );      }      onContextMenu = (e: React.MouseEvent): void => { -        if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 +        if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7              ContextMenu.Instance.addItem({ description: "PDFOptions", event: emptyFunction });          }      } @@ -50,7 +77,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {      render() {          return ( -            <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}> +            <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}>                  {this.subView}              </CollectionBaseView>          ); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 90077b053..67784fa81 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -2,20 +2,14 @@ import React = require("react");  import { library } from '@fortawesome/fontawesome-svg-core';  import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, untracked } from "mobx"; +import { action, computed, observable, untracked, runInAction } from "mobx";  import { observer } from "mobx-react";  import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; -import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss' +import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';  import "react-table/react-table.css"; -import { Document } from "../../../fields/Document"; -import { Field, Opt } from "../../../fields/Field"; -import { Key } from "../../../fields/Key"; -import { KeyStore } from "../../../fields/KeyStore"; -import { ListField } from "../../../fields/ListField"; -import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils"; -import { Server } from "../../Server"; +import { emptyFunction, returnFalse, returnZero } from "../../../Utils";  import { SetupDrag } from "../../util/DragManager"; -import { CompileScript, ToField } from "../../util/Scripting"; +import { CompileScript } from "../../util/Scripting";  import { Transform } from "../../util/Transform";  import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";  import { anchorPoints, Flyout } from "../DocumentDecorations"; @@ -25,44 +19,47 @@ import { DocumentView } from "../nodes/DocumentView";  import { FieldView, FieldViewProps } from "../nodes/FieldView";  import "./CollectionSchemaView.scss";  import { CollectionSubView } from "./CollectionSubView"; +import { Opt, Field, Doc } from "../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { listSpec } from "../../../new_fields/Schema"; +import { List } from "../../../new_fields/List"; +import { Id } from "../../../new_fields/RefField";  // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657  @observer -class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggle: (key: Key) => void }> { -    @observable key: Key | undefined; - +class KeyToggle extends React.Component<{ keyName: string, checked: boolean, toggle: (key: string) => void }> {      constructor(props: any) {          super(props); -        Server.GetField(this.props.keyId, action((field: Opt<Field>) => field instanceof Key && (this.key = field)));      }      render() { -        return !this.key ? (null) : -            (<div key={this.key.Id}> -                <input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} /> -                {this.key.Name} -            </div>); +        return ( +            <div key={this.props.keyName}> +                <input type="checkbox" checked={this.props.checked} onChange={() => this.props.toggle(this.props.keyName)} /> +                {this.props.keyName} +            </div> +        );      }  }  @observer -export class CollectionSchemaView extends CollectionSubView { +export class CollectionSchemaView extends CollectionSubView(doc => doc) {      private _mainCont?: HTMLDivElement;      private _startSplitPercent = 0;      private DIVIDER_WIDTH = 4; -    @observable _columns: Array<Key> = [KeyStore.Title, KeyStore.Data, KeyStore.Author]; +    @observable _columns: Array<string> = ["title", "data", "author"];      @observable _selectedIndex = 0;      @observable _columnsPercentage = 0; -    @observable _keys: Key[] = []; +    @observable _keys: string[] = [];      @observable _newKeyName: string = ""; -    @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); } -    @computed get columns() { return this.props.Document.GetList(KeyStore.ColumnsKey, [] as Key[]); } -    @computed get borderWidth() { return COLLECTION_BORDER_WIDTH; } +    @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); } +    @computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); } +    @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }      renderCell = (rowProps: CellInfo) => {          let props: FieldViewProps = { @@ -74,43 +71,38 @@ export class CollectionSchemaView extends CollectionSubView {              isTopMost: false,              selectOnLoad: false,              ScreenToLocalTransform: Transform.Identity, -            focus: emptyDocFunction, +            focus: emptyFunction,              active: returnFalse,              whenActiveChanged: emptyFunction, +            PanelHeight: returnZero, +            PanelWidth: returnZero,          };          let contents = (              <FieldView {...props} />          );          let reference = React.createRef<HTMLDivElement>();          let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument); -        let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => { +        let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {              const res = run({ this: doc });              if (!res.success) return false;              const field = res.result; -            if (field instanceof Field) { -                doc.Set(props.fieldKey, field); -                return true; -            } else { -                let dataField = ToField(field); -                if (dataField) { -                    doc.Set(props.fieldKey, dataField); -                    return true; -                } -            } -            return false; +            doc[props.fieldKey] = field; +            return true;          };          return ( -            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document.Id} ref={reference}> +            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document[Id]} ref={reference}>                  <EditableView                      display={"inline"}                      contents={contents}                      height={Number(MAX_ROW_HEIGHT)}                      GetValue={() => { -                        let field = props.Document.Get(props.fieldKey); -                        if (field && field instanceof Field) { -                            return field.ToScriptString(); +                        let field = props.Document[props.fieldKey]; +                        if (field) { +                            //TODO Types +                            // return field.ToScriptString(); +                            return String(field);                          } -                        return field || ""; +                        return "";                      }}                      SetValue={(value: string) => {                          let script = CompileScript(value, { addReturn: true, params: { this: Document.name } }); @@ -126,14 +118,13 @@ export class CollectionSchemaView extends CollectionSubView {                          }                          const run = script.run;                          //TODO This should be able to be refactored to compile the script once -                        this.props.Document.GetTAsync<ListField<Document>>(this.props.fieldKey, ListField).then((val) => { -                            if (val) { -                                val.Data.forEach(doc => applyToDoc(doc, run)); -                            } -                        }); +                        const val = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)); +                        if (val) { +                            val.forEach(doc => applyToDoc(doc, run)); +                        }                      }}>                  </EditableView> -            </div> +            </div >          );      } @@ -164,36 +155,37 @@ export class CollectionSchemaView extends CollectionSubView {      }      @action -    toggleKey = (key: Key) => { -        this.props.Document.GetOrCreateAsync<ListField<Key>>(KeyStore.ColumnsKey, ListField, -            (field) => { -                const index = field.Data.indexOf(key); -                if (index === -1) { -                    this.columns.push(key); -                } else { -                    this.columns.splice(index, 1); -                } - -            }); +    toggleKey = (key: string) => { +        let list = Cast(this.props.Document.schemaColumns, listSpec("string")); +        if (list === undefined) { +            this.props.Document.schemaColumns = list = new List<string>([key]); +        } else { +            const index = list.indexOf(key); +            if (index === -1) { +                list.push(key); +            } else { +                list.splice(index, 1); +            } +        }      }      //toggles preview side-panel of schema      @action      toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => { -        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); +        this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;      }      @action      onDividerMove = (e: PointerEvent): void => {          let nativeWidth = this._mainCont!.getBoundingClientRect(); -        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100))); +        this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));      }      @action      onDividerUp = (e: PointerEvent): void => {          document.removeEventListener("pointermove", this.onDividerMove);          document.removeEventListener('pointerup', this.onDividerUp);          if (this._startSplitPercent === this.splitPercentage) { -            this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); +            this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;          }      }      onDividerDown = (e: React.PointerEvent) => { @@ -206,8 +198,7 @@ export class CollectionSchemaView extends CollectionSubView {      onPointerDown = (e: React.PointerEvent): void => {          if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { -            if (this.props.isSelected()) -                e.stopPropagation(); +            if (this.props.isSelected()) e.stopPropagation();              else e.preventDefault();          }      } @@ -220,7 +211,7 @@ export class CollectionSchemaView extends CollectionSubView {      @action      addColumn = () => { -        this.columns.push(new Key(this._newKeyName)); +        this.columns.push(this._newKeyName);          this._newKeyName = "";      } @@ -235,21 +226,22 @@ export class CollectionSchemaView extends CollectionSubView {          this.previewScript = e.currentTarget.value;      } -    get previewDocument(): Document | undefined { -        const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); -        const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; -        return selected ? (this.previewScript ? selected.Get(new Key(this.previewScript)) as Document : selected) : undefined; +    get previewDocument(): Doc | undefined { +        const children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); +        const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined; +        return selected ? (this.previewScript ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;      }      get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }      get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }      get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; } -    private previewDocNativeWidth = () => this.previewDocument!.GetNumber(KeyStore.NativeWidth, this.previewRegionWidth); -    private previewDocNativeHeight = () => this.previewDocument!.GetNumber(KeyStore.NativeHeight, this.previewRegionHeight); +    private previewDocNativeWidth = () => Cast(this.previewDocument!.nativeWidth, "number", this.previewRegionWidth); +    private previewDocNativeHeight = () => Cast(this.previewDocument!.nativeHeight, "number", this.previewRegionHeight);      private previewContentScaling = () => {          let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth); -        if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) +        if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) {              return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight); +        }          return wscale;      }      private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling(); @@ -257,21 +249,23 @@ export class CollectionSchemaView extends CollectionSubView {      get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; }      getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(          - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset, -        - this.borderWidth).scale(1 / this.previewContentScaling()); +        - this.borderWidth).scale(1 / this.previewContentScaling())      @computed      get previewPanel() {          // let doc = CompileScript(this.previewScript, { this: selected }, true)(); -        return !this.previewDocument ? (null) : ( +        const previewDoc = this.previewDocument; +        return !previewDoc ? (null) : (              <div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>                  <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> -                    <DocumentView Document={this.previewDocument} isTopMost={false} selectOnLoad={false} +                    <DocumentView Document={previewDoc} isTopMost={false} selectOnLoad={false} +                        toggleMinimized={emptyFunction}                          addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}                          ScreenToLocalTransform={this.getPreviewTransform}                          ContentScaling={this.previewContentScaling}                          PanelWidth={this.previewPanelWidth} PanelHeight={this.previewPanelHeight}                          ContainingCollectionView={this.props.CollectionView} -                        focus={emptyDocFunction} +                        focus={emptyFunction}                          parentActive={this.props.active}                          whenActiveChanged={this.props.whenActiveChanged}                      /> @@ -283,24 +277,25 @@ export class CollectionSchemaView extends CollectionSubView {      }      get documentKeysCheckList() { -        const docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); -        let keys: { [id: string]: boolean } = {}; +        const docs = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); +        let keys: { [key: string]: boolean } = {};          // bcz: ugh.  this is untracked since otherwise a large collection of documents will blast the server for all their fields.          //  then as each document's fields come back, we update the documents _proxies.  Each time we do this, the whole schema will be          //  invalidated and re-rendered.   This workaround will inquire all of the document fields before the options button is clicked.          //  then by the time the options button is clicked, all of the fields should be in place.  If a new field is added while this menu          //  is displayed (unlikely) it won't show up until something else changes. -        untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false)))); +        //TODO Types +        untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false)))); -        this.columns.forEach(key => keys[key.Id] = true); +        this.columns.forEach(key => keys[key] = true);          return Array.from(Object.keys(keys)).map(item => -            (<KeyToggle checked={keys[item]} key={item} keyId={item} toggle={this.toggleKey} />)); +            (<KeyToggle checked={keys[item]} key={item} keyName={item} toggle={this.toggleKey} />));      }      get tableOptionsPanel() {          return !this.props.active() ? (null) :              (<Flyout -                anchorPoint={anchorPoints.LEFT_TOP} +                anchorPoint={anchorPoints.RIGHT_TOP}                  content={<div>                      <div id="schema-options-header"><h5><b>Options</b></h5></div>                      <div id="options-flyout-div"> @@ -328,16 +323,17 @@ export class CollectionSchemaView extends CollectionSubView {      render() {          library.add(faCog);          library.add(faPlus); -        const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); +        //This can't just pass FieldValue to filter because filter passes other arguments to the passed in function, which end up as default values in FieldValue +        const children = (this.children || []).filter(doc => FieldValue(doc));          return (              <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}                  onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>                  <div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}>                      <ReactTable data={children} page={0} pageSize={children.length} showPagination={false}                          columns={this.columns.map(col => ({ -                            Header: col.Name, -                            accessor: (doc: Document) => [doc, col], -                            id: col.Id +                            Header: col, +                            accessor: (doc: Doc) => [doc, col], +                            id: col                          }))}                          column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}                          getTrProps={this.getTrProps} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5c3b2e586..828ac880a 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,28 +1,27 @@  import { action, runInAction } from "mobx"; -import { Document } from "../../../fields/Document"; -import { ListField } from "../../../fields/ListField";  import React = require("react"); -import { KeyStore } from "../../../fields/KeyStore"; -import { FieldWaiting, Opt } from "../../../fields/Field";  import { undoBatch, UndoManager } from "../../util/UndoManager";  import { DragManager } from "../../util/DragManager"; -import { Documents, DocumentOptions } from "../../documents/Documents"; +import { Docs, DocumentOptions } from "../../documents/Documents";  import { RouteStore } from "../../../server/RouteStore"; -import { TupleField } from "../../../fields/TupleField";  import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { NumberField } from "../../../fields/NumberField"; -import { ServerUtils } from "../../../server/ServerUtil"; -import { Server } from "../../Server";  import { FieldViewProps } from "../nodes/FieldView";  import * as rp from 'request-promise';  import { CollectionView } from "./CollectionView";  import { CollectionPDFView } from "./CollectionPDFView";  import { CollectionVideoView } from "./CollectionVideoView"; +import { Doc, Opt } from "../../../new_fields/Doc"; +import { DocComponent } from "../DocComponent"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, PromiseValue, FieldValue } from "../../../new_fields/Types"; +import { List } from "../../../new_fields/List"; +import { DocServer } from "../../DocServer"; +import { ObjectField } from "../../../new_fields/ObjectField";  export interface CollectionViewProps extends FieldViewProps { -    addDocument: (document: Document, allowDuplicates?: boolean) => boolean; -    removeDocument: (document: Document) => boolean; -    moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; +    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; +    removeDocument: (document: Doc) => boolean; +    moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;      PanelWidth: () => number;      PanelHeight: () => number;  } @@ -33,198 +32,196 @@ export interface SubCollectionViewProps extends CollectionViewProps {  export type CursorEntry = TupleField<[string, string], [number, number]>; -export class CollectionSubView extends React.Component<SubCollectionViewProps> { -    private dropDisposer?: DragManager.DragDropDisposer; -    protected createDropTarget = (ele: HTMLDivElement) => { -        if (this.dropDisposer) { -            this.dropDisposer(); +export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { +    class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) { +        private dropDisposer?: DragManager.DragDropDisposer; +        protected createDropTarget = (ele: HTMLDivElement) => { +            if (this.dropDisposer) { +                this.dropDisposer(); +            } +            if (ele) { +                this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); +            }          } -        if (ele) { -            this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); +        protected CreateDropTarget(ele: HTMLDivElement) { +            this.createDropTarget(ele);          } -    } -    protected CreateDropTarget(ele: HTMLDivElement) { -        this.createDropTarget(ele); -    } -    @action -    protected setCursorPosition(position: [number, number]) { -        let ind; -        let doc = this.props.Document; -        let id = CurrentUserUtils.id; -        let email = CurrentUserUtils.email; -        if (id && email) { -            let textInfo: [string, string] = [id, email]; -            doc.GetTAsync(KeyStore.Prototype, Document).then(proto => { +        get children() { +            //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.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => FieldValue(doc)); +        } + +        @action +        protected async setCursorPosition(position: [number, number]) { +            return; +            let ind; +            let doc = this.props.Document; +            let id = CurrentUserUtils.id; +            let email = CurrentUserUtils.email; +            if (id && email) { +                let textInfo: [string, string] = [id, email]; +                const proto = await doc.proto;                  if (!proto) {                      return;                  } -                proto.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, action((field: ListField<CursorEntry>) => { -                    let cursors = field.Data; -                    if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) { -                        cursors[ind].Data[1] = position; -                    } else { -                        let entry = new TupleField<[string, string], [number, number]>([textInfo, position]); -                        cursors.push(entry); -                    } -                })); -            }); +                let cursors = await Cast(proto.cursors, listSpec(ObjectField)); +                if (!cursors) { +                    proto.cursors = cursors = new List<ObjectField>(); +                } +                if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) { +                    cursors[ind].Data[1] = position; +                } else { +                    let entry = new TupleField<[string, string], [number, number]>([textInfo, position]); +                    cursors.push(entry); +                } +            }          } -    } -    @undoBatch -    @action -    protected drop(e: Event, de: DragManager.DropEvent): boolean { -        if (de.data instanceof DragManager.DocumentDragData) { -            if (de.data.aliasOnDrop || de.data.copyOnDrop) { -                [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key => -                    de.data.draggedDocuments.map((draggedDocument: Document, i: number) => -                        draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null))); -            } -            let added = false; -            if (de.data.aliasOnDrop || de.data.copyOnDrop) { -                added = de.data.droppedDocuments.reduce((added: boolean, d) => { -                    let moved = this.props.addDocument(d); -                    return moved || added; -                }, false); -            } else if (de.data.moveDocument) { -                const move = de.data.moveDocument; -                added = de.data.droppedDocuments.reduce((added: boolean, d) => { -                    let moved = move(d, this.props.Document, this.props.addDocument); -                    return moved || added; -                }, false); -            } else { -                added = de.data.droppedDocuments.reduce((added: boolean, d) => { -                    let moved = this.props.addDocument(d); -                    return moved || added; -                }, false); +        @undoBatch +        @action +        protected drop(e: Event, de: DragManager.DropEvent): boolean { +            if (de.data instanceof DragManager.DocumentDragData) { +                if (de.data.dropAction || de.data.userDropAction) { +                    ["width", "height", "curPage"].map(key => +                        de.data.draggedDocuments.map((draggedDocument: Doc, i: number) => +                            PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f)))); +                } +                let added = false; +                if (de.data.dropAction || de.data.userDropAction) { +                    added = de.data.droppedDocuments.reduce((added: boolean, d) => { +                        let moved = this.props.addDocument(d); +                        return moved || added; +                    }, false); +                } else if (de.data.moveDocument) { +                    const move = de.data.moveDocument; +                    added = de.data.droppedDocuments.reduce((added: boolean, d) => { +                        let moved = move(d, this.props.Document, this.props.addDocument); +                        return moved || added; +                    }, false); +                } else { +                    added = de.data.droppedDocuments.reduce((added: boolean, d) => { +                        let moved = this.props.addDocument(d); +                        return moved || added; +                    }, false); +                } +                e.stopPropagation(); +                return added;              } -            e.stopPropagation(); -            return added; +            return false;          } -        return false; -    } -    protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Document>> { -        let ctor: ((path: string, options: DocumentOptions) => (Document | Promise<Document | undefined>)) | undefined = undefined; -        if (type.indexOf("image") !== -1) { -            ctor = Documents.ImageDocument; -        } -        if (type.indexOf("video") !== -1) { -            ctor = Documents.VideoDocument; -        } -        if (type.indexOf("audio") !== -1) { -            ctor = Documents.AudioDocument; -        } -        if (type.indexOf("pdf") !== -1) { -            ctor = Documents.PdfDocument; -            options.nativeWidth = 1200; -        } -        if (type.indexOf("excel") !== -1) { -            ctor = Documents.DBDocument; -            options.copyDraggedItems = true; -        } -        if (type.indexOf("html") !== -1) { -            if (path.includes('localhost')) { -                let s = path.split('/'); -                let id = s[s.length - 1]; -                Server.GetField(id).then(field => { -                    if (field instanceof Document) { -                        let alias = field.CreateAlias(); -                        alias.SetNumber(KeyStore.X, options.x || 0); -                        alias.SetNumber(KeyStore.Y, options.y || 0); -                        alias.SetNumber(KeyStore.Width, options.width || 300); -                        alias.SetNumber(KeyStore.Height, options.height || options.width || 300); -                        this.props.addDocument(alias, false); -                    } -                }); -                return undefined; +        protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> { +            let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined; +            if (type.indexOf("image") !== -1) { +                ctor = Docs.ImageDocument; +            } +            if (type.indexOf("video") !== -1) { +                ctor = Docs.VideoDocument; +            } +            if (type.indexOf("audio") !== -1) { +                ctor = Docs.AudioDocument; +            } +            if (type.indexOf("pdf") !== -1) { +                ctor = Docs.PdfDocument; +                options.nativeWidth = 1200;              } -            ctor = Documents.WebDocument; -            options = { height: options.width, ...options, title: path }; +            if (type.indexOf("excel") !== -1) { +                ctor = Docs.DBDocument; +                options.dropAction = "copy"; +            } +            if (type.indexOf("html") !== -1) { +                if (path.includes('localhost')) { +                    let s = path.split('/'); +                    let id = s[s.length - 1]; +                    DocServer.GetRefField(id).then(field => { +                        if (field instanceof Doc) { +                            let alias = Doc.MakeAlias(field); +                            alias.x = options.x || 0; +                            alias.y = options.y || 0; +                            alias.width = options.width || 300; +                            alias.height = options.height || options.width || 300; +                            this.props.addDocument(alias, false); +                        } +                    }); +                    return undefined; +                } +                ctor = Docs.WebDocument; +                options = { height: options.width, ...options, title: path, nativeWidth: undefined }; +            } +            return ctor ? ctor(path, options) : undefined;          } -        return ctor ? ctor(path, options) : undefined; -    } -    @undoBatch -    @action -    protected onDrop(e: React.DragEvent, options: DocumentOptions): void { -        let html = e.dataTransfer.getData("text/html"); -        let text = e.dataTransfer.getData("text/plain"); +        @undoBatch +        @action +        protected onDrop(e: React.DragEvent, options: DocumentOptions): void { +            let html = e.dataTransfer.getData("text/html"); +            let text = e.dataTransfer.getData("text/plain"); -        if (text && text.startsWith("<div")) { -            return; -        } -        e.stopPropagation(); -        e.preventDefault(); - -        if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) { -            console.log("not good"); -            let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 }); -            htmlDoc.SetText(KeyStore.DocumentText, text); -            this.props.addDocument(htmlDoc, false); -            return; -        } +            if (text && text.startsWith("<div")) { +                return; +            } +            e.stopPropagation(); +            e.preventDefault(); -        let batch = UndoManager.StartBatch("collection view drop"); -        let promises: Promise<void>[] = []; -        // tslint:disable-next-line:prefer-for-of -        for (let i = 0; i < e.dataTransfer.items.length; i++) { -            const upload = window.location.origin + RouteStore.upload; -            let item = e.dataTransfer.items[i]; -            if (item.kind === "string" && item.type.indexOf("uri") !== -1) { -                let str: string; -                let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve)) -                    .then(action((s: string) => rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + (str = s))))) -                    .then(result => { -                        let type = result.headers["content-type"]; -                        if (type) { -                            this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }) -                                .then(doc => doc && this.props.addDocument(doc, false)); -                        } -                    }); -                promises.push(prom); +            if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) { +                let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text }); +                this.props.addDocument(htmlDoc, false); +                return;              } -            let type = item.type; -            if (item.kind === "file") { -                let file = item.getAsFile(); -                let formData = new FormData(); -                if (file) { -                    formData.append('file', file); -                } -                let dropFileName = file ? file.name : "-empty-"; - -                let prom = fetch(upload, { -                    method: 'POST', -                    body: formData -                }).then(async (res: Response) => { -                    (await res.json()).map(action((file: any) => { -                        let path = window.location.origin + file; -                        let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName }); - -                        docPromise.then(action((doc?: Document) => { -                            let docs = this.props.Document.GetT(KeyStore.Data, ListField); -                            if (docs !== FieldWaiting) { -                                if (!docs) { -                                    docs = new ListField<Document>(); -                                    this.props.Document.Set(KeyStore.Data, docs); -                                } -                                if (doc) { -                                    docs.Data.push(doc); -                                } +            let batch = UndoManager.StartBatch("collection view drop"); +            let promises: Promise<void>[] = []; +            // tslint:disable-next-line:prefer-for-of +            for (let i = 0; i < e.dataTransfer.items.length; i++) { +                const upload = window.location.origin + RouteStore.upload; +                let item = e.dataTransfer.items[i]; +                if (item.kind === "string" && item.type.indexOf("uri") !== -1) { +                    let str: string; +                    let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve)) +                        .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s))))) +                        .then(result => { +                            let type = result["content-type"]; +                            if (type) { +                                this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }) +                                    .then(doc => doc && this.props.addDocument(doc, false));                              } +                        }); +                    promises.push(prom); +                } +                let type = item.type; +                if (item.kind === "file") { +                    let file = item.getAsFile(); +                    let formData = new FormData(); + +                    if (file) { +                        formData.append('file', file); +                    } +                    let dropFileName = file ? file.name : "-empty-"; + +                    let prom = fetch(upload, { +                        method: 'POST', +                        body: formData +                    }).then(async (res: Response) => { +                        (await res.json()).map(action((file: any) => { +                            let path = window.location.origin + file; +                            let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName }); + +                            docPromise.then(doc => doc && this.props.addDocument(doc));                          })); -                    })); -                }); -                promises.push(prom); +                    }); +                    promises.push(prom); +                }              } -        } -        if (promises.length) { -            Promise.all(promises).finally(() => batch.end()); -        } else { -            batch.end(); +            if (promises.length) { +                Promise.all(promises).finally(() => batch.end()); +            } else { +                batch.end(); +            }          }      } +    return CollectionSubView;  } + diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 8ecc5b67b..19d4abc05 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -33,18 +33,24 @@      }      .bullet { +        position: absolute;          width: 1.5em;          display: inline-block;          color: $intermediate-color; +        margin-top: 3px; +        transform: scale(1.3,1.3);      }      .coll-title { +        width:max-content; +        display: block;          font-size: 24px; -        margin-bottom: 20px;      }      .docContainer { -        display: inline-table; +        margin-left: 10px; +        display: block; +        width: max-content;      }      .docContainer:hover { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index e0387f4b4..b67d6f965 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,24 +1,30 @@  import { IconProp, library } from '@fortawesome/fontawesome-svg-core';  import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable } from "mobx"; +import { action, observable, trace } from "mobx";  import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; -import { FieldWaiting } from "../../../fields/Field"; -import { KeyStore } from "../../../fields/KeyStore"; -import { ListField } from "../../../fields/ListField"; -import { DragManager, SetupDrag } from "../../util/DragManager"; +import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";  import { EditableView } from "../EditableView";  import { CollectionSubView } from "./CollectionSubView";  import "./CollectionTreeView.scss";  import React = require("react"); +import { Document, listSpec } from '../../../new_fields/Schema'; +import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types'; +import { Doc } from '../../../new_fields/Doc'; +import { Id } from '../../../new_fields/RefField'; +import { ContextMenu } from '../ContextMenu'; +import { undoBatch } from '../../util/UndoManager'; +import { Main } from '../Main'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { CollectionDockingView } from './CollectionDockingView'; +import { DocumentManager } from '../../util/DocumentManager';  export interface TreeViewProps { -    document: Document; -    deleteDoc: (doc: Document) => void; +    document: Doc; +    deleteDoc: (doc: Doc) => void;      moveDocument: DragManager.MoveFunction; -    copyOnDrag: boolean; +    dropAction: "alias" | "copy" | undefined;  }  export enum BulletType { @@ -41,11 +47,15 @@ class TreeView extends React.Component<TreeViewProps> {      delete = () => this.props.deleteDoc(this.props.document); +    get children() { +        return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed?    .filter(doc => FieldValue(doc)); +    } +      @action      remove = (document: Document) => { -        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); -        if (children && children !== FieldWaiting) { -            children.Data.splice(children.Data.indexOf(document), 1); +        let children = Cast(this.props.document.data, listSpec(Doc), []); +        if (children) { +            children.splice(children.indexOf(document), 1);          }      } @@ -74,83 +84,118 @@ class TreeView extends React.Component<TreeViewProps> {       */      renderTitle() {          let reference = React.createRef<HTMLDivElement>(); -        let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag); +        let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);          let editableView = (titleString: string) =>              (<EditableView                  display={"inline"}                  contents={titleString}                  height={36} -                GetValue={() => this.props.document.Title} +                GetValue={() => StrCast(this.props.document.title)}                  SetValue={(value: string) => { -                    this.props.document.SetText(KeyStore.Title, value); +                    let target = this.props.document.proto ? this.props.document.proto : this.props.document; +                    target.title = value;                      return true;                  }}              />);          return (              <div className="docContainer" ref={reference} onPointerDown={onItemDown}> -                {editableView(this.props.document.Title)} -                <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> +                {editableView(StrCast(this.props.document.title))} +                {/* <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> */}              </div >);      } +    onWorkspaceContextMenu = (e: React.MouseEvent): void => { +        if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 +            ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) }); +            ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) }); +            if (DocumentManager.Instance.getDocumentViews(this.props.document).length) { +                ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) }); +            } +            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.props.deleteDoc(this.props.document)) }); +            ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); +            e.stopPropagation(); +        } +    } + +    onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; } +    onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; } +      render() {          let bulletType = BulletType.List; -        let childElements: JSX.Element | undefined = undefined; -        var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); -        if (children && children !== FieldWaiting) { // add children for a collection +        let contentElement: JSX.Element | null = (null); +        var children = Cast(this.props.document.data, listSpec(Doc)); +        if (children) { // add children for a collection              if (!this._collapsed) {                  bulletType = BulletType.Collapsible; -                childElements = <ul> -                    {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} copyOnDrag={this.props.copyOnDrag} />)} +                contentElement = <ul> +                    {TreeView.GetChildElements(children, this.remove, this.move, this.props.dropAction)}                  </ul >;              }              else bulletType = BulletType.Collapsed;          } -        return <div className="treeViewItem-container" > +        return <div className="treeViewItem-container" +            style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }} +            onContextMenu={this.onWorkspaceContextMenu} +            onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>              <li className="collection-child">                  {this.renderBullet(bulletType)}                  {this.renderTitle()} -                {childElements ? childElements : (null)} +                {contentElement}              </li>          </div>;      } +    public static GetChildElements(docs: Doc[], remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) { +        return docs.filter(child => !child.excludeFromLibrary).filter(doc => FieldValue(doc)).map(child => +            <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />); +    }  }  @observer -export class CollectionTreeView extends CollectionSubView { - +export class CollectionTreeView extends CollectionSubView(Document) {      @action      remove = (document: Document) => { -        var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); -        if (children && children !== FieldWaiting) { -            children.Data.splice(children.Data.indexOf(document), 1); +        let children = Cast(this.props.Document.data, listSpec(Doc), []); +        if (children) { +            children.splice(children.indexOf(document), 1); +        } +    } +    onContextMenu = (e: React.MouseEvent): void => { +        if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 +            ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) }); +        } +        if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) { +            ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });          }      } -      render() { -        let children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); -        let copyOnDrag = this.props.Document.GetBoolean(KeyStore.CopyDraggedItems, false); -        let childrenElement = !children || children === FieldWaiting ? (null) : -            (children.Data.map(value => -                <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} copyOnDrag={copyOnDrag} />) -            ); +        trace(); +        const children = this.children; +        let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType; +        if (!children) { +            return (null); +        } +        let childElements = TreeView.GetChildElements(children, this.remove, this.props.moveDocument, dropAction);          return ( -            <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> +            <div id="body" className="collectionTreeView-dropTarget" +                style={{ borderRadius: "inherit" }} +                onContextMenu={this.onContextMenu} +                onWheel={(e: React.WheelEvent) => e.stopPropagation()} +                onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>                  <div className="coll-title">                      <EditableView -                        contents={this.props.Document.Title} +                        contents={this.props.Document.title}                          display={"inline"}                          height={72} -                        GetValue={() => this.props.Document.Title} +                        GetValue={() => StrCast(this.props.Document.title)}                          SetValue={(value: string) => { -                            this.props.Document.SetText(KeyStore.Title, value); +                            let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document; +                            target.title = value;                              return true;                          }} />                  </div> -                <hr />                  <ul className="no-indent"> -                    {childrenElement} +                    {childElements}                  </ul>              </div >          ); diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 29fb342c6..9dee217cb 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -1,6 +1,5 @@  import { action, observable, trace } from "mobx";  import { observer } from "mobx-react"; -import { KeyStore } from "../../../fields/KeyStore";  import { ContextMenu } from "../ContextMenu";  import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";  import React = require("react"); @@ -8,17 +7,18 @@ import "./CollectionVideoView.scss";  import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";  import { FieldView, FieldViewProps } from "../nodes/FieldView";  import { emptyFunction } from "../../../Utils"; +import { Id } from "../../../new_fields/RefField"; +import { VideoBox } from "../nodes/VideoBox";  @observer  export class CollectionVideoView extends React.Component<FieldViewProps> { -    private _intervalTimer: any = undefined; -    private _player: HTMLVideoElement | undefined = undefined; +    private _videoBox: VideoBox | undefined = undefined; +    @observable _playTimer?: NodeJS.Timeout = undefined;      @observable _currentTimecode: number = 0; -    @observable _isPlaying: boolean = false; -    public static LayoutString(fieldKey: string = "DataKey") { +    public static LayoutString(fieldKey: string = "data") {          return FieldView.LayoutString(CollectionVideoView, fieldKey);      }      private get uIButtons() { @@ -29,7 +29,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {                  <span style={{ fontSize: 8 }}>{" " + Math.round((this._currentTimecode - Math.trunc(this._currentTimecode)) * 100)}</span>              </div>,              <div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}> -                {this._isPlaying ? "\"" : ">"} +                {this._playTimer ? "\"" : ">"}              </div>,              <div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>                  F @@ -38,53 +38,36 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {      }      @action -    mainCont = (ele: HTMLDivElement | null) => { -        if (ele) { -            this._player = ele.getElementsByTagName("video")[0]; -            if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) { -                this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1); -            } +    updateTimecode = () => { +        if (this._videoBox && this._videoBox.player) { +            this._currentTimecode = this._videoBox.player.currentTime; +            this.props.Document.curPage = Math.round(this._currentTimecode);          }      } -    componentDidMount() { -        this._intervalTimer = setInterval(this.updateTimecode, 1000); -    } +    componentDidMount() { this.updateTimecode(); } -    componentWillUnmount() { -        clearInterval(this._intervalTimer); -    } - -    @action -    updateTimecode = () => { -        if (this._player) { -            if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero !== -1) { -                this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero; -                (this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1; -            } else { -                this._currentTimecode = this._player.currentTime; -                this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this._currentTimecode)); -            } -        } -    } +    componentWillUnmount() { if (this._playTimer) clearInterval(this._playTimer); }      @action      onPlayDown = () => { -        if (this._player) { -            if (this._player.paused) { -                this._player.play(); -                this._isPlaying = true; +        if (this._videoBox && this._videoBox.player) { +            if (this._videoBox.player.paused) { +                this._videoBox.player.play(); +                if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 1000);              } else { -                this._player.pause(); -                this._isPlaying = false; +                this._videoBox.player.pause(); +                if (this._playTimer) clearInterval(this._playTimer); +                this._playTimer = undefined; +              }          }      }      @action      onFullDown = (e: React.PointerEvent) => { -        if (this._player) { -            this._player.requestFullscreen(); +        if (this._videoBox && this._videoBox.player) { +            this._videoBox.player.requestFullscreen();              e.stopPropagation();              e.preventDefault();          } @@ -92,33 +75,34 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {      @action      onResetDown = () => { -        if (this._player) { -            this._player.pause(); -            this._player.currentTime = 0; +        if (this._videoBox && this._videoBox.player) { +            this._videoBox.player.pause(); +            this._videoBox.player.currentTime = 0; +            if (this._playTimer) clearInterval(this._playTimer); +            this._playTimer = undefined; +            this.updateTimecode();          } -      }      onContextMenu = (e: React.MouseEvent): void => { -        if (!e.isPropagationStopped() && this.props.Document.Id !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 +        if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7              ContextMenu.Instance.addItem({ description: "VideoOptions", event: emptyFunction });          }      } +    setVideoBox = (player: VideoBox) => { this._videoBox = player; } +      private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {          let props = { ...this.props, ...renderProps }; -        return ( -            <> -                <CollectionFreeFormView {...props} CollectionView={this} /> -                {this.props.isSelected() ? this.uIButtons : (null)} -            </> -        ); +        return (<> +            <CollectionFreeFormView {...props} setVideoBox={this.setVideoBox} CollectionView={this} /> +            {this.props.isSelected() ? this.uIButtons : (null)} +        </>);      }      render() { -        trace();          return ( -            <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}> +            <CollectionBaseView {...this.props} className="collectionVideoView-cont" onContextMenu={this.onContextMenu}>                  {this.subView}              </CollectionBaseView>);      } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 675e720e2..8c1442d38 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -7,14 +7,15 @@ import { CollectionDockingView } from './CollectionDockingView';  import { CollectionTreeView } from './CollectionTreeView';  import { ContextMenu } from '../ContextMenu';  import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; -import { KeyStore } from '../../../fields/KeyStore';  import { observer } from 'mobx-react';  import { undoBatch } from '../../util/UndoManager';  import { trace } from 'mobx'; +import { Id } from '../../../new_fields/RefField'; +import { Main } from '../Main';  @observer  export class CollectionView extends React.Component<FieldViewProps> { -    public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr); } +    public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); }      private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {          let props = { ...this.props, ...renderProps }; @@ -29,13 +30,13 @@ export class CollectionView extends React.Component<FieldViewProps> {          return (null);      } -    get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? +    get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?      onContextMenu = (e: React.MouseEvent): void => { -        if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 -            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) }); -            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) }); -            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) }); +        if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 +            ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform) }); +            ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema) }); +            ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree) });          }      } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 3b2f79be1..3e8a8a442 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -3,4 +3,10 @@      stroke-width: 3;      transform: translate(10000px,10000px);      pointer-events: all; +} +.collectionfreeformlinkview-linkCircle { +    stroke: black; +    stroke-width: 3; +    transform: translate(10000px,10000px); +    pointer-events: all;  }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 20c5a84bf..3b700b053 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,37 +1,58 @@  import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { KeyStore } from "../../../../fields/KeyStore";  import { Utils } from "../../../../Utils";  import "./CollectionFreeFormLinkView.scss";  import React = require("react");  import v5 = require("uuid/v5"); +import { StrCast, NumCast, BoolCast } from "../../../../new_fields/Types"; +import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc"; +import { InkingControl } from "../../InkingControl";  export interface CollectionFreeFormLinkViewProps { -    A: Document; -    B: Document; -    LinkDocs: Document[]; +    A: Doc; +    B: Doc; +    LinkDocs: Doc[]; +    addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; +    removeDocument: (document: Doc) => boolean;  }  @observer  export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {      onPointerDown = (e: React.PointerEvent) => { -        this.props.LinkDocs.map(l => -            console.log("Link:" + l.Title)); +        if (e.button === 0 && !InkingControl.Instance.selectedTool) { +            let a = this.props.A; +            let b = this.props.B; +            let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : a[WidthSym]() / 2); +            let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2); +            let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2); +            let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2); +            this.props.LinkDocs.map(l => { +                let width = l[WidthSym](); +                l.x = (x1 + x2) / 2 - width / 2; +                l.y = (y1 + y2) / 2 + 10; +                if (!this.props.removeDocument(l)) this.props.addDocument(l, false); +            }); +            e.stopPropagation(); +            e.preventDefault(); +        }      }      render() {          let l = this.props.LinkDocs;          let a = this.props.A;          let b = this.props.B; -        let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Width() / 2); -        let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Height() / 2); -        let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Width() / 2); -        let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Height() / 2); +        let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2); +        let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2); +        let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2); +        let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2);          return ( -            <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown} -                style={{ strokeWidth: `${l.length * 5}` }} -                x1={`${x1}`} y1={`${y1}`} -                x2={`${x2}`} y2={`${y2}`} /> +            <> +                <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" +                    style={{ strokeWidth: `${l.length * 5}` }} +                    x1={`${x1}`} y1={`${y1}`} +                    x2={`${x2}`} y2={`${y2}`} /> +                <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" +                    cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={10} onPointerDown={this.onPointerDown} /> +            </>          );      }  }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index ebdb0c75c..b34e0856e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,8 +1,5 @@  import { computed, IReactionDisposer, reaction } from "mobx";  import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { KeyStore } from "../../../../fields/KeyStore"; -import { ListField } from "../../../../fields/ListField";  import { Utils } from "../../../../Utils";  import { DocumentManager } from "../../../util/DocumentManager";  import { DocumentView } from "../../nodes/DocumentView"; @@ -10,54 +7,67 @@ import { CollectionViewProps } from "../CollectionSubView";  import "./CollectionFreeFormLinksView.scss";  import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";  import React = require("react"); +import { Doc } from "../../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; +import { listSpec } from "../../../../new_fields/Schema"; +import { List } from "../../../../new_fields/List"; +import { Id } from "../../../../new_fields/RefField";  @observer  export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {      _brushReactionDisposer?: IReactionDisposer;      componentDidMount() { -        this._brushReactionDisposer = reaction(() => this.props.Document.GetList(this.props.fieldKey, [] as Document[]).map(doc => doc.GetNumber(KeyStore.X, 0)), +        this._brushReactionDisposer = reaction(() => Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).map(doc => NumCast(doc.x)),              () => { -                let views = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc.GetText(KeyStore.BackgroundLayout, "").indexOf("istogram") !== -1); +                let views = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).filter(doc => StrCast(doc.backgroundLayout, "").indexOf("istogram") !== -1);                  for (let i = 0; i < views.length; i++) {                      for (let j = 0; j < views.length; j++) {                          let srcDoc = views[j];                          let dstDoc = views[i]; -                        let x1 = srcDoc.GetNumber(KeyStore.X, 0); -                        let x1w = srcDoc.GetNumber(KeyStore.Width, -1); -                        let x2 = dstDoc.GetNumber(KeyStore.X, 0); -                        let x2w = dstDoc.GetNumber(KeyStore.Width, -1); +                        let x1 = NumCast(srcDoc.x); +                        let x1w = NumCast(srcDoc.width, -1); +                        let x2 = NumCast(dstDoc.x); +                        let x2w = NumCast(dstDoc.width, -1);                          if (x1w < 0 || x2w < 0 || i === j) {                              continue;                          }                          let dstTarg = dstDoc;                          let srcTarg = srcDoc; -                        let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => { -                            let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : []; +                        let findBrush = (field: List<Doc>) => field.findIndex(brush => { +                            let bdocs = brush ? Cast(brush.brushingDocs, listSpec(Doc), []) : [];                              return (bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false);                          }); -                        let brushAction = (field: ListField<Document>) => { +                        let brushAction = (field: List<Doc>) => {                              let found = findBrush(field);                              if (found !== -1) {                                  console.log("REMOVE BRUSH " + srcTarg.Title + " " + dstTarg.Title); -                                field.Data.splice(found, 1); +                                field.splice(found, 1);                              }                          };                          if (Math.abs(x1 + x1w - x2) < 20) { -                            let linkDoc: Document = new Document(); -                            linkDoc.SetText(KeyStore.Title, "Histogram Brush"); -                            linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title); -                            linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); +                            let linkDoc: Doc = new Doc(); +                            linkDoc.title = "Histogram Brush"; +                            linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title); +                            linkDoc.brushingDocs = new List([dstTarg, srcTarg]); -                            brushAction = (field: ListField<Document>) => { +                            brushAction = (field: List<Doc>) => {                                  if (findBrush(field) === -1) {                                      console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title); -                                    (findBrush(field) === -1) && field.Data.push(linkDoc); +                                    (findBrush(field) === -1) && field.push(linkDoc);                                  }                              };                          } -                        dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); -                        srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); +                        let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc)); +                        if (dstBrushDocs === undefined) { +                            dstTarg.brushingDocs = dstBrushDocs = new List<Doc>(); +                        } +                        let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc)); +                        if (srcBrushDocs === undefined) { +                            srcTarg.brushingDocs = srcBrushDocs = new List<Doc>(); +                        } +                        brushAction(dstBrushDocs); +                        brushAction(srcBrushDocs);                      }                  } @@ -70,9 +80,17 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP      }      documentAnchors(view: DocumentView) {          let equalViews = [view]; -        let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document); -        if (containerDoc && containerDoc instanceof Document) { -            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!); +        let containerDoc = FieldValue(Cast(view.props.Document.annotationOn, Doc)); +        if (containerDoc) { +            equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!); +        } +        if (view.props.ContainingCollectionView) { +            let collid = view.props.ContainingCollectionView.props.Document[Id]; +            Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []). +                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);      } @@ -82,12 +100,12 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP          let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {              let srcViews = this.documentAnchors(connection.a);              let targetViews = this.documentAnchors(connection.b); -            let possiblePairs: { a: Document, b: Document, }[] = []; +            let possiblePairs: { a: Doc, b: Doc, }[] = [];              srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));              possiblePairs.map(possiblePair =>                  drawnPairs.reduce((found, drawnPair) => {                      let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b); -                    if (match && !drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) { +                    if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) {                          drawnPair.l.push(connection.l);                      }                      return match || found; @@ -96,8 +114,9 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP                  drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })              );              return drawnPairs; -        }, [] as { a: Document, b: Document, l: Document[] }[]); -        return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); +        }, [] as { a: Doc, b: Doc, l: Doc[] }[]); +        return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} +            removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />);      }      render() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index cf0a6de00..036745eca 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,6 +1,5 @@  import { computed } from "mobx";  import { observer } from "mobx-react"; -import { KeyStore } from "../../../../fields/KeyStore";  import { CollectionViewProps, CursorEntry } from "../CollectionSubView";  import "./CollectionFreeFormView.scss";  import React = require("react"); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 57706b51e..cb849b325 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,90 +1,103 @@  @import "../../globalCssVariables"; -.collectionfreeformview { -  position: inherit; -  top: 0; -  left: 0; -  width: 100%; -  height: 100%; -  transform-origin: left top; + +.collectionfreeformview-ease { +    position: absolute; +    top: 0; +    left: 0; +    width: 100%; +    height: 100%; +    transform-origin: left top; +    transition: transform 1s;  } -.collectionfreeformview-container { -  .collectionfreeformview > .jsx-parser { + +.collectionfreeformview-none {      position: inherit; -    height: 100%; +    top: 0; +    left: 0;      width: 100%; -  } +    height: 100%; +    transform-origin: left top; +} -  //nested freeform views -  // .collectionfreeformview-container { +.collectionfreeformview-container { +    .collectionfreeformview>.jsx-parser { +        position: inherit; +        height: 100%; +        width: 100%; +    } + +    //nested freeform views +    // .collectionfreeformview-container {      // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),      //   linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);      // background-size: 30px 30px; -  // } - -  border-width: $COLLECTION_BORDER_WIDTH; -  box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; -  border-color:  $light-color-secondary; -  border-style: solid;  -  border-radius: $border-radius; -  box-sizing: border-box; -  position: absolute; -  overflow: hidden; -  top: 0; -  left: 0; -  width: 100%; -  height: 100%; +    // } +    box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; +    border: 0px solid $light-color-secondary; +    border-radius: $border-radius; +    box-sizing: border-box; +    position: absolute; +    overflow: hidden; +    top: 0; +    left: 0; +    width: 100%; +    height: 100%;  } +   +  .collectionfreeformview-overlay { -  .collectionfreeformview > .jsx-parser { -    position: inherit; -    height: 100%; -  } -  .formattedTextBox-cont { -    background: $light-color-secondary; -    overflow: visible; -  } -  -  opacity: 0.99; -  border-width: 0;  -  border-color: transparent; -  border-style: solid;  -  border-radius: $border-radius; -  box-sizing: border-box; -  position: absolute; -  overflow: hidden; -  top: 0; -  left: 0; -  width: 100%; -  height: 100%; -  .collectionfreeformview { +    .collectionfreeformview>.jsx-parser { +        position: inherit; +        height: 100%; +    } +      .formattedTextBox-cont { -        background:yellow; +        background: $light-color-secondary; +        overflow: visible; +    } + +    opacity: 0.99; +    border: 0px solid transparent; +    border-radius: $border-radius; +    box-sizing: border-box; +    position:absolute; +    overflow: hidden; +    top: 0; +    left: 0; +    width: 100%; +    height: 100%; + +    .collectionfreeformview { +        .formattedTextBox-cont { +            background: yellow; +        }      } -  }  }  // selection border...?  .border { -  border-style: solid; -  box-sizing: border-box; -  width: 98%; -  height: 98%; -  border-radius: $border-radius; +    border-style: solid; +    box-sizing: border-box; +    width: 98%; +    height: 98%; +    border-radius: $border-radius;  }  //this is an animation for the blinking cursor!  @keyframes blink { -  0% { -    opacity: 0; -  } -  49% { -    opacity: 0; -  } -  50% { -    opacity: 1; -  } +    0% { +        opacity: 0; +    } + +    49% { +        opacity: 0; +    } + +    50% { +        opacity: 1; +    }  }  #prevCursor { -  animation: blink 1s infinite; -} +    animation: blink 1s infinite; +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1de4d157f..d81a340b0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,5 @@ -import { action, computed, observable, trace } from "mobx"; +import { action, computed, trace } from "mobx";  import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { KeyStore } from "../../../../fields/KeyStore";  import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";  import { DocumentManager } from "../../../util/DocumentManager";  import { DragManager } from "../../../util/DragManager"; @@ -10,10 +8,9 @@ import { Transform } from "../../../util/Transform";  import { undoBatch } from "../../../util/UndoManager";  import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";  import { InkingCanvas } from "../../InkingCanvas"; -import { MainOverlayTextBox } from "../../MainOverlayTextBox";  import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";  import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { DocumentViewProps } from "../../nodes/DocumentView"; +import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";  import { CollectionSubView } from "../CollectionSubView";  import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";  import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; @@ -21,75 +18,88 @@ import "./CollectionFreeFormView.scss";  import { MarqueeView } from "./MarqueeView";  import React = require("react");  import v5 = require("uuid/v5"); -import { BooleanField } from "../../../../fields/BooleanField"; +// import { BooleanField } from "../../../../fields/BooleanField";  import { Timeline } from "../../nodes/Timeline"; +import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema"; +import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc"; +import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types"; +import { pageSchema } from "../../nodes/ImageBox"; +import { Id } from "../../../../new_fields/RefField"; + +export const panZoomSchema = createSchema({ +    panX: "number", +    panY: "number", +    scale: "number" +}); + +type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; +const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema);  @observer -export class CollectionFreeFormView extends CollectionSubView { +export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { +    public static RIGHT_BTN_DRAG = false;      private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)      private _lastX: number = 0;      private _lastY: number = 0;      private get _pwidth() { return this.props.PanelWidth(); }      private get _pheight() { return this.props.PanelHeight(); } -    @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } -    @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } +    @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } +    @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }      private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } -    private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's? -    private childViews = () => this.views; -    private panX = () => this.props.Document.GetNumber(KeyStore.PanX, 0); -    private panY = () => this.props.Document.GetNumber(KeyStore.PanY, 0); -    private zoomScaling = () => this.props.Document.GetNumber(KeyStore.Scale, 1); +    private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } +    private panX = () => FieldValue(this.Document.panX, 0); +    private panY = () => FieldValue(this.Document.panY, 0); +    private zoomScaling = () => FieldValue(this.Document.scale, 1);      private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0;  // shift so pan position is at center of window for non-overlay collections      private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections      private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());      private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);      private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); -    private addLiveTextBox = (newBox: Document) => { -        this._selectOnLoaded = newBox.Id;// track the new text box so we can give it a prop that tells it to focus itself when it's displayed +    private addLiveTextBox = (newBox: Doc) => { +        this._selectOnLoaded = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed          this.addDocument(newBox, false);      } -    private addDocument = (newBox: Document, allowDuplicates: boolean) => { -        newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1)); -        return this.props.addDocument(this.bringToFront(newBox), false); +    private addDocument = (newBox: Doc, allowDuplicates: boolean) => { +        this.props.addDocument(newBox, false); +        this.bringToFront(newBox); +        return true;      } -    private selectDocuments = (docs: Document[]) => { +    private selectDocuments = (docs: Doc[]) => {          SelectionManager.DeselectAll;          docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>              SelectionManager.SelectDoc(dv!, true));      }      public getActiveDocuments = () => { -        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); -        return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => { -            var page = doc.GetNumber(KeyStore.Page, -1); -            if (page === curPage || page === -1) { -                active.push(doc); -            } -            return active; -        }, [] as Document[]); +        const curPage = FieldValue(this.Document.curPage, -1); +        return FieldValue(this.children, [] as Doc[]).filter(doc => { +            var page = NumCast(doc.page, -1); +            return page === curPage || page === -1; +        });      }      @undoBatch      @action      drop = (e: Event, de: DragManager.DropEvent) => {          if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) { -            const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);              if (de.data.droppedDocuments.length) {                  let dragDoc = de.data.droppedDocuments[0]; -                let dropX = dragDoc.GetNumber(KeyStore.X, 0); -                let dropY = dragDoc.GetNumber(KeyStore.Y, 0); +                let zoom = NumCast(dragDoc.zoomBasis, 1); +                let [xp, yp] = this.getTransform().transformPoint(de.x, de.y); +                let x = xp - de.data.xOffset / zoom; +                let y = yp - de.data.yOffset / zoom; +                let dropX = NumCast(de.data.droppedDocuments[0].x); +                let dropY = NumCast(de.data.droppedDocuments[0].y);                  de.data.droppedDocuments.map(d => { -                    d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0)) - dropX); -                    d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0)) - dropY); -                    if (!d.GetBoolean(KeyStore.IsMinimized, false)) { -                        if (!d.GetNumber(KeyStore.Width, 0)) { -                            d.SetNumber(KeyStore.Width, 300); -                        } -                        if (!d.GetNumber(KeyStore.Height, 0)) { -                            let nw = d.GetNumber(KeyStore.NativeWidth, 0); -                            let nh = d.GetNumber(KeyStore.NativeHeight, 0); -                            d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300); -                        } +                    d.x = x + NumCast(d.x) - dropX; +                    d.y = y + NumCast(d.y) - dropY; +                    if (!NumCast(d.width)) { +                        d.width = 300; +                    } +                    if (!NumCast(d.height)) { +                        let nw = NumCast(d.nativeWidth); +                        let nh = NumCast(d.nativeHeight); +                        d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;                      }                      this.bringToFront(d);                  }); @@ -101,51 +111,47 @@ export class CollectionFreeFormView extends CollectionSubView {      }      @action -    cleanupInteractions = () => { -        document.removeEventListener("pointermove", this.onPointerMove); -        document.removeEventListener("pointerup", this.onPointerUp); -    } - -    @action      onPointerDown = (e: React.PointerEvent): void => { -        let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => { +        let childSelected = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), [] as Doc[]).filter(doc => doc).reduce((childSelected, doc) => {              var dv = DocumentManager.Instance.getDocumentView(doc);              return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false);          }, false); -        if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) || (e.button === 0 && e.altKey)) && (childSelected || this.props.active())) { +        if ((CollectionFreeFormView.RIGHT_BTN_DRAG && +            (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) || +                (e.button === 0 && e.altKey)) && (childSelected || this.props.active()))) || +            (!CollectionFreeFormView.RIGHT_BTN_DRAG && +                ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && (childSelected || this.props.active())))) {              document.removeEventListener("pointermove", this.onPointerMove); -            document.addEventListener("pointermove", this.onPointerMove);              document.removeEventListener("pointerup", this.onPointerUp); +            document.addEventListener("pointermove", this.onPointerMove);              document.addEventListener("pointerup", this.onPointerUp);              this._lastX = e.pageX;              this._lastY = e.pageY;          }      } -    @action      onPointerUp = (e: PointerEvent): void => { -        e.stopPropagation(); - -        this.cleanupInteractions(); +        document.removeEventListener("pointermove", this.onPointerMove); +        document.removeEventListener("pointerup", this.onPointerUp);      }      @action      onPointerMove = (e: PointerEvent): void => {          if (!e.cancelBubble) { -            let x = this.props.Document.GetNumber(KeyStore.PanX, 0); -            let y = this.props.Document.GetNumber(KeyStore.PanY, 0); -            let docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); +            let x = Cast(this.props.Document.panX, "number", 0); +            let y = Cast(this.props.Document.panY, "number", 0); +            let docs = this.children || [];              let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);              if (!this.isAnnotationOverlay) { -                let minx = docs.length ? docs[0].GetNumber(KeyStore.X, 0) : 0; -                let maxx = docs.length ? docs[0].Width() + minx : minx; -                let miny = docs.length ? docs[0].GetNumber(KeyStore.Y, 0) : 0; -                let maxy = docs.length ? docs[0].Height() + miny : miny; +                let minx = docs.length ? Cast(docs[0].x, "number", 0) : 0; +                let maxx = docs.length ? Cast(docs[0].width, "number", 0) + minx : minx; +                let miny = docs.length ? Cast(docs[0].y, "number", 0) : 0; +                let maxy = docs.length ? Cast(docs[0].height, "number", 0) + miny : miny;                  let ranges = docs.filter(doc => doc).reduce((range, doc) => { -                    let x = doc.GetNumber(KeyStore.X, 0); -                    let xe = x + doc.GetNumber(KeyStore.Width, 0); -                    let y = doc.GetNumber(KeyStore.Y, 0); -                    let ye = y + doc.GetNumber(KeyStore.Height, 0); +                    let x = Cast(doc.x, "number", 0); +                    let xe = x + Cast(doc.width, "number", 0); +                    let y = Cast(doc.y, "number", 0); +                    let ye = y + Cast(doc.height, "number", 0);                      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]]); @@ -159,7 +165,7 @@ export class CollectionFreeFormView extends CollectionSubView {              this.setPan(x - dx, y - dy);              this._lastX = e.pageX;              this._lastY = e.pageY; -            e.stopPropagation(); +            e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers              e.preventDefault();          }      } @@ -169,10 +175,10 @@ export class CollectionFreeFormView extends CollectionSubView {          // if (!this.props.active()) {          //     return;          // } -        let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => { +        let childSelected = (this.children || []).filter(doc => doc).some(doc => {              var dv = DocumentManager.Instance.getDocumentView(doc); -            return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false); -        }, false); +            return dv && SelectionManager.IsSelected(dv) ? true : false; +        });          if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {              return;          } @@ -181,23 +187,23 @@ export class CollectionFreeFormView extends CollectionSubView {          if (e.ctrlKey) {              let deltaScale = (1 - (e.deltaY / coefficient)); -            this.props.Document.SetNumber(KeyStore.NativeWidth, this.nativeWidth * deltaScale); -            this.props.Document.SetNumber(KeyStore.NativeHeight, this.nativeHeight * deltaScale); +            this.props.Document.nativeWidth = this.nativeWidth * deltaScale; +            this.props.Document.nativeHeight = this.nativeHeight * deltaScale;              e.stopPropagation();              e.preventDefault();          } else {              // if (modes[e.deltaMode] === 'pixels') coefficient = 50;              // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??              let deltaScale = (1 - (e.deltaY / coefficient)); -            if (deltaScale < 0) deltaScale = -deltaScale;              if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {                  deltaScale = 1 / this.zoomScaling();              } +            if (deltaScale < 0) deltaScale = -deltaScale;              let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);              let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);              let safeScale = Math.abs(localTransform.Scale); -            this.props.Document.SetNumber(KeyStore.Scale, Math.abs(safeScale)); +            this.props.Document.scale = Math.abs(safeScale);              this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);              e.stopPropagation();          } @@ -205,12 +211,11 @@ export class CollectionFreeFormView extends CollectionSubView {      @action      setPan(panX: number, panY: number) { -        MainOverlayTextBox.Instance.SetTextDoc();          var scale = this.getLocalTransform().inverse().Scale;          const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));          const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY)); -        this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); -        this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); +        this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; +        this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;      }      @action @@ -222,51 +227,61 @@ export class CollectionFreeFormView extends CollectionSubView {      onDragOver = (): void => {      } -    @action -    bringToFront(doc: Document) { -        this.props.Document.GetList(this.props.fieldKey, [] as Document[]).slice().sort((doc1, doc2) => { +    bringToFront = (doc: Doc) => { +        const docs = (this.children || []); +        docs.slice().sort((doc1, doc2) => {              if (doc1 === doc) return 1;              if (doc2 === doc) return -1; -            return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0); -        }).map((doc, index) => doc.SetNumber(KeyStore.ZIndex, index + 1)); +            return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); +        }).forEach((doc, index) => doc.zIndex = index + 1); +        doc.zIndex = docs.length + 1;          return doc;      } -    focusDocument = (doc: Document) => { +    focusDocument = (doc: Doc) => { +        SelectionManager.DeselectAll(); +        this.props.Document.panTransformType = "Ease";          this.setPan( -            doc.GetNumber(KeyStore.X, 0) + doc.Width() / 2, -            doc.GetNumber(KeyStore.Y, 0) + doc.Height() / 2); +            NumCast(doc.x) + NumCast(doc.width) / 2, +            NumCast(doc.y) + NumCast(doc.height) / 2);          this.props.focus(this.props.Document); +        if (this.props.Document.panTransformType === "Ease") { +            setTimeout(() => this.props.Document.panTransformType = "None", 2000);  // wait 3 seconds, then reset to false +        }      } -    getDocumentViewProps(document: Document): DocumentViewProps { + +    getDocumentViewProps(document: Doc): DocumentViewProps {          return {              Document: document, +            toggleMinimized: emptyFunction,              addDocument: this.props.addDocument,              removeDocument: this.props.removeDocument,              moveDocument: this.props.moveDocument,              ScreenToLocalTransform: this.getTransform,              isTopMost: false, -            selectOnLoad: document.Id === this._selectOnLoaded, -            PanelWidth: document.Width, -            PanelHeight: document.Height, +            selectOnLoad: document[Id] === this._selectOnLoaded, +            PanelWidth: document[WidthSym], +            PanelHeight: document[HeightSym],              ContentScaling: returnOne,              ContainingCollectionView: this.props.CollectionView,              focus: this.focusDocument,              parentActive: this.props.active,              whenActiveChanged: this.props.active, +            bringToFront: this.bringToFront,          };      } -    @computed +    @computed.struct      get views() { -        var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); -        let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => { -            var page = doc.GetNumber(KeyStore.Page, -1); +        let curPage = FieldValue(this.Document.curPage, -1); +        let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => { +            if (!FieldValue(doc)) return prev; +            var page = NumCast(doc.page, -1);              if (page === curPage || page === -1) { -                let minim = doc.GetT(KeyStore.IsMinimized, BooleanField); -                if (minim === undefined || (minim && !minim.Data)){ -                    prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />); +                let minim = Cast(doc.isMinimized, "boolean"); +                if (minim === undefined || !minim) { +                    prev.push(<CollectionFreeFormDocumentView key={doc[Id]} {...this.getDocumentViewProps(doc)} />);                  }              }              return prev; @@ -282,27 +297,30 @@ export class CollectionFreeFormView extends CollectionSubView {          super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));      } +    private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />];      render() {          const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`; +        const easing = () => this.props.Document.panTransformType === "Ease";          return (              <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel} +                style={{ borderRadius: "inherit" }}                  onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} > -                <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} +                <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}>                      <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} -                        zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> -                        <CollectionFreeFormBackgroundView {...this.getDocumentViewProps(this.props.Document)} /> +                        easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> +                          <CollectionFreeFormLinksView {...this.props} key="freeformLinks">                              <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >                                  {this.childViews}                              </InkingCanvas>                          </CollectionFreeFormLinksView> -                        <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> -                    </CollectionFreeFormViewPannableContents>   -                    <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} />      +                        {/* <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> */} +                    </CollectionFreeFormViewPannableContents> +                    <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />                  </MarqueeView> -                <Timeline {...this.props}/> +                <Timeline {...this.props} />              </div>          );      } @@ -311,9 +329,9 @@ export class CollectionFreeFormView extends CollectionSubView {  @observer  class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {      @computed get overlayView() { -        let overlayLayout = this.props.Document.GetText(KeyStore.OverlayLayout, ""); +        let overlayLayout = Cast(this.props.Document.overlayLayout, "string", "");          return !overlayLayout ? (null) : -            (<DocumentContentsView {...this.props} layoutKey={KeyStore.OverlayLayout} +            (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}                  isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);      }      render() { @@ -322,12 +340,12 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {  }  @observer -class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps> { +class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {      @computed get backgroundView() { -        let backgroundLayout = this.props.Document.GetText(KeyStore.BackgroundLayout, ""); +        let backgroundLayout = Cast(this.props.Document.backgroundLayout, "string", "");          return !backgroundLayout ? (null) : -            (<DocumentContentsView {...this.props} layoutKey={KeyStore.BackgroundLayout} -                isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />); +            (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} +                isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);      }      render() {          return this.backgroundView; @@ -340,17 +358,19 @@ interface CollectionFreeFormViewPannableContentsProps {      panX: () => number;      panY: () => number;      zoomScaling: () => number; +    easing: () => boolean;  }  @observer  class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{      render() { +        let freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none");          const cenx = this.props.centeringShiftX();          const ceny = this.props.centeringShiftY();          const panx = -this.props.panX();          const pany = -this.props.panY();          const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire -        return <div className="collectionfreeformview" style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}> +        return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>              {this.props.children}          </div>;      } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index bf918beba..8c81f6990 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,29 +1,31 @@  import { action, computed, observable } from "mobx";  import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { FieldWaiting } from "../../../../fields/Field"; -import { InkField, StrokeData } from "../../../../fields/InkField"; -import { KeyStore } from "../../../../fields/KeyStore"; -import { Documents } from "../../../documents/Documents"; +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 { CollectionFreeFormView } from "./CollectionFreeFormView"; -import { MINIMIZED_ICON_SIZE } from '../../../views/globalCssVariables.scss'  import "./MarqueeView.scss";  import React = require("react"); +import { Utils } from "../../../../Utils"; +import { Doc } from "../../../../new_fields/Doc"; +import { NumCast, Cast } from "../../../../new_fields/Types"; +import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { Templates } from "../../Templates"; +import { List } from "../../../../new_fields/List";  interface MarqueeViewProps {      getContainerTransform: () => Transform;      getTransform: () => Transform;      container: CollectionFreeFormView; -    addDocument: (doc: Document, allowDuplicates: false) => boolean; -    activeDocuments: () => Document[]; -    selectDocuments: (docs: Document[]) => void; -    removeDocument: (doc: Document) => boolean; -    addLiveTextDocument: (doc: Document) => void; +    addDocument: (doc: Doc, allowDuplicates: false) => boolean; +    activeDocuments: () => Doc[]; +    selectDocuments: (docs: Doc[]) => void; +    removeDocument: (doc: Doc) => boolean; +    addLiveTextDocument: (doc: Doc) => void; +    isSelected: () => boolean;  }  @observer @@ -33,18 +35,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>      @observable _lastY: number = 0;      @observable _downX: number = 0;      @observable _downY: number = 0; -    @observable _used: boolean = false;      @observable _visible: boolean = false; -    _showOnUp: boolean = false; -    static DRAG_THRESHOLD = 4; +    _commandExecuted = false;      @action      cleanupInteractions = (all: boolean = false) => {          if (all) { -            document.removeEventListener("pointermove", this.onPointerMove, true);              document.removeEventListener("pointerup", this.onPointerUp, true); -        } else { -            this._used = true; +            document.removeEventListener("pointermove", this.onPointerMove, true);          }          document.removeEventListener("keydown", this.marqueeCommand, true);          this._visible = false; @@ -52,34 +50,27 @@ export class MarqueeView extends React.Component<MarqueeViewProps>      @action      onKeyPress = (e: KeyboardEvent) => { -        // Mixing events between React and Native is finicky.  In FormattedTextBox, we set the -        // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore -        // the keyPress here. -        //if not these keys, make a textbox if preview cursor is active! -        if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { -            //make textbox and add it to this collection -            let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); -            let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); -            this.props.addLiveTextDocument(newBox); -            PreviewCursor.Visible = false; -            e.stopPropagation(); -        } -    } -    hideCursor = () => { -        document.removeEventListener("keypress", this.onKeyPress, false); +        //make textbox and add it to this collection +        let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); +        let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); +        this.props.addLiveTextDocument(newBox); +        e.stopPropagation();      }      @action      onPointerDown = (e: React.PointerEvent): void => { -        if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) { -            this._downX = this._lastX = e.pageX; -            this._downY = this._lastY = e.pageY; -            this._used = false; -            this._showOnUp = true; -            document.removeEventListener("keypress", this.onKeyPress, false); +        this._downX = this._lastX = e.pageX; +        this._downY = this._lastY = e.pageY; +        this._commandExecuted = false; +        PreviewCursor.Visible = false; +        if ((CollectionFreeFormView.RIGHT_BTN_DRAG && e.button === 0 && !e.altKey && !e.metaKey && this.props.container.props.active()) || +            (!CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && this.props.container.props.active())) {              document.addEventListener("pointermove", this.onPointerMove, true);              document.addEventListener("pointerup", this.onPointerUp, true);              document.addEventListener("keydown", this.marqueeCommand, true);          } +        if (e.altKey) { +            e.preventDefault(); +        }      }      @action @@ -87,33 +78,45 @@ export class MarqueeView extends React.Component<MarqueeViewProps>          this._lastX = e.pageX;          this._lastY = e.pageY;          if (!e.cancelBubble) { -            if (Math.abs(this._downX - e.clientX) > 4 || Math.abs(this._downY - e.clientY) > 4) { -                this._showOnUp = false; -                PreviewCursor.Visible = false; -            } -            if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey && -                (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) { -                this._visible = true; +            if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD || +                Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) { +                if (!this._commandExecuted) { +                    this._visible = true; +                } +                e.stopPropagation(); +                e.preventDefault();              } -            e.stopPropagation(); +        } +        if (e.altKey) {              e.preventDefault();          }      }      @action      onPointerUp = (e: PointerEvent): void => { -        this.cleanupInteractions(true); -        this._visible = false; -        if (this._showOnUp) { -            PreviewCursor.Show(this.hideCursor, this._downX, this._downY); -            document.addEventListener("keypress", this.onKeyPress, false); -        } else { +        if (this._visible) {              let mselect = this.marqueeSelect();              if (!e.shiftKey) {                  SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);              }              this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);          } +        this.cleanupInteractions(true); +        if (e.altKey) { +            e.preventDefault(); +        } +    } + +    @action +    onClick = (e: React.MouseEvent): void => { +        if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && +            Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { +            PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress); +            // let the DocumentView stopPropagation of this event when it selects this document +        } else {  // why do we get a click event when the cursor have moved a big distance? +            // let's cut it off here so no one else has to deal with it. +            e.stopPropagation(); +        }      }      intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -133,41 +136,84 @@ export class MarqueeView extends React.Component<MarqueeViewProps>      @undoBatch      @action      marqueeCommand = (e: KeyboardEvent) => { -        if (e.key === "Backspace" || e.key === "Delete") { +        if (this._commandExecuted) { +            return; +        } +        if (e.key === "Backspace" || e.key === "Delete" || e.key == "d") { +            this._commandExecuted = true;              this.marqueeSelect().map(d => this.props.removeDocument(d)); -            let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); -            if (ink && ink !== FieldWaiting) { -                this.marqueeInkDelete(ink.Data); +            let ink = Cast(this.props.container.props.Document.ink, InkField); +            if (ink) { +                this.marqueeInkDelete(ink.inkData);              } -            this.cleanupInteractions(); +            this.cleanupInteractions(false); +            e.stopPropagation();          } -        if (e.key === "c") { +        if (e.key === "c" || e.key === "r" || e.key === "e") { +            this._commandExecuted = true; +            e.stopPropagation();              let bounds = this.Bounds;              let selected = this.marqueeSelect().map(d => { -                this.props.removeDocument(d); -                d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2); -                d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2); -                d.SetNumber(KeyStore.Page, -1); +                if (e.key !== "r") +                    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;              }); -            let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField); -            let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined; -            //setTimeout(() => { -            let newCollection = Documents.FreeformDocument(selected, { +            let ink = Cast(this.props.container.props.Document.ink, InkField); +            let inkData = ink ? ink.inkData : undefined; +            let zoomBasis = NumCast(this.props.container.props.Document.scale, 1); +            let newCollection = Docs.FreeformDocument(selected, {                  x: bounds.left,                  y: bounds.top, -                panx: 0, -                pany: 0, -                width: bounds.width, -                height: bounds.height, -                ink: inkData ? this.marqueeInkSelect(inkData) : undefined, +                panX: 0, +                panY: 0, +                borderRounding: e.key === "e" ? -1 : undefined, +                scale: zoomBasis, +                width: bounds.width * zoomBasis, +                height: bounds.height * zoomBasis, +                ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,                  title: "a nested collection"              }); -            this.props.addDocument(newCollection, false); +              this.marqueeInkDelete(inkData); -            // }, 100); -            this.cleanupInteractions(); +            // SelectionManager.DeselectAll(); +            if (e.key === "r") { +                let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" }); +                summary.maximizedDocs = new List<Doc>(selected); +                // summary.doc1 = selected[0]; +                // if (selected.length > 1) +                //     summary.doc2 = selected[1]; +                // summary.templates = new List<string>([Templates.Summary.Layout]); +                this.props.addLiveTextDocument(summary); +                e.preventDefault(); +                let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top); +                selected.map(maximizedDoc => { +                    let maxx = NumCast(maximizedDoc.x, undefined); +                    let maxy = NumCast(maximizedDoc.y, undefined); +                    let maxw = NumCast(maximizedDoc.width, undefined); +                    let maxh = NumCast(maximizedDoc.height, undefined); +                    maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0]) +                }); +            } +            else { +                this.props.addDocument(newCollection, false); +            } +            this.cleanupInteractions(false); +        } +        if (e.key === "s") { +            this._commandExecuted = true; +            e.stopPropagation(); +            e.preventDefault(); +            let bounds = this.Bounds; +            let selected = this.marqueeSelect();              SelectionManager.DeselectAll(); +            let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" }); +            this.props.addLiveTextDocument(summary); +            selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!)); + +            this.cleanupInteractions(false);          }      }      @action @@ -200,19 +246,19 @@ export class MarqueeView extends React.Component<MarqueeViewProps>              let idata = new Map();              ink.forEach((value: StrokeData, key: string, map: any) =>                  !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); -            this.props.container.props.Document.SetDataOnPrototype(KeyStore.Ink, idata, InkField); +            Doc.SetOnPrototype(this.props.container.props.Document, "ink", new InkField(idata));          }      }      marqueeSelect() {          let selRect = this.Bounds; -        let selection: Document[] = []; +        let selection: Doc[] = [];          this.props.activeDocuments().map(doc => { -            var z = doc.GetNumber(KeyStore.Zoom, 1); -            var x = doc.GetNumber(KeyStore.X, 0); -            var y = doc.GetNumber(KeyStore.Y, 0); -            var w = doc.Width() / z; -            var h = doc.Height() / z; +            var z = NumCast(doc.zoomBasis, 1); +            var x = NumCast(doc.x); +            var y = NumCast(doc.y); +            var w = NumCast(doc.width) / z; +            var h = NumCast(doc.height) / z;              if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {                  selection.push(doc);              } @@ -230,7 +276,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>      }      render() { -        return <div className="marqueeView" onPointerDown={this.onPointerDown}> +        return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>              {this.props.children}              {!this._visible ? (null) : this.marqueeDiv}          </div>; | 
