diff options
Diffstat (limited to 'src/client/views/collections')
5 files changed, 422 insertions, 445 deletions
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 87b767c93..4807dc40a 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -1,13 +1,12 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Document } from '../../../fields/Document'; -import { 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, Id } from '../../../new_fields/Doc'; +import { listSpec } from '../../../new_fields/Schema'; +import { List } from '../../../new_fields/List'; export enum CollectionViewType { Invalid, @@ -18,9 +17,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 +36,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 +57,74 @@ 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 { - var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); - doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); + addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { + let props = this.props; + var curPage = Cast(props.Document.curPage, "number", -1); + Doc.SetOnPrototype(doc, "page", curPage); if (curPage >= 0) { - doc.SetOnPrototype(KeyStore.AnnotationOn, this.props.Document); + Doc.SetOnPrototype(doc, "annotationOn", props.Document); } - if (!this.createsCycle(doc, this.props.Document)) { - let value = this.props.Document.Get(this.props.fieldKey) as ListField<Document>; - if (value) { - if (!value.Data.some(v => v.Id === doc.Id) || allowDuplicates) { - value.Data.push(doc); + if (!this.createsCycle(doc, props.Document)) { + //TODO This won't create the field if it doesn't already exist + const value = Cast(props.Document[props.fieldKey], listSpec(Doc)); + if (value !== undefined) { + if (allowDuplicates || !value.some(v => v.Id === doc.Id)) { + value.push(doc); } } else { - this.props.Document.Set(this.props.fieldKey, new ListField([doc])); + this.props.Document[this.props.fieldKey] = new List([doc]); } // 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 = this.props.Document.GetNumber(KeyStore.Scale, 1); + let zoom = NumCast(this.props.Document.scale, 1); let screen = this.props.ScreenToLocalTransform().inverse().Scale / (this.props as any).ContentScaling() * zoom; - doc.SetNumber(KeyStore.ZoomBasis, screen); + doc.zoomBasis = screen; } } return true; - // bcz: What is this code trying to do? - // 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); - // return true; - // } - // } - return false; } @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,7 +139,7 @@ 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; } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 3267f2455..ec5f823b0 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -4,11 +4,8 @@ import 'golden-layout/src/css/goldenlayout-dark-theme.css'; import { action, observable, reaction, trace } 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, returnZero } from "../../../Utils"; +import { Utils, returnTrue, emptyFunction, returnOne, returnZero } from "../../../Utils"; import { Server } from "../../Server"; import { undoBatch } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; @@ -17,20 +14,23 @@ 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, Id, Opt, Field, FieldId } from "../../../new_fields/Doc"; +import { Cast, NumCast } from "../../../new_fields/Types"; +import { List } from "../../../new_fields/List"; +import { DocServer } from "../../DocServer"; +import { listSpec } from "../../../new_fields/Schema"; @observer export class CollectionDockingView extends React.Component<SubCollectionViewProps> { public static Instance: CollectionDockingView; - public static makeDocumentConfig(document: Document) { + public static makeDocumentConfig(document: Doc) { return { type: 'react-component', component: 'DocumentFrameRenderer', - title: document.Title, + title: document.title, props: { - documentId: document.Id, + documentId: document[Id], //collectionDockingView: CollectionDockingView.Instance } }; @@ -48,14 +48,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp (window as any).React = React; (window as any).ReactDOM = ReactDOM; } - public StartOtherDrag(dragDocs: Document[], e: any) { + public StartOtherDrag(dragDocs: Doc[], e: any) { 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)] @@ -83,7 +83,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // @action - public AddRightSplit(document: Document, minimize: boolean = false) { + public AddRightSplit(document: Doc, minimize: boolean = false) { let newItemStackConfig = { type: 'stack', content: [CollectionDockingView.makeDocumentConfig(document)] @@ -119,7 +119,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } setupGoldenLayout() { - var config = this.props.Document.GetText(KeyStore.Data, ""); + var config = Cast(this.props.Document.data, "string", ""); if (config) { if (!this._goldenLayout) { this._goldenLayout = new GoldenLayout(JSON.parse(config)); @@ -154,7 +154,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp componentDidMount: () => void = () => { if (this._containerRef.current) { reaction( - () => this.props.Document.GetText(KeyStore.Data, ""), + () => Cast(this.props.Document.data, "string", ""), () => { if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) { setTimeout(() => this.setupGoldenLayout(), 1); @@ -203,7 +203,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp 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))); + (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc))); } else if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) { e.stopPropagation(); @@ -213,11 +213,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp 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) { + if (f instanceof Doc) { DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y, { handlers: { - dragComplete: action(emptyFunction), + dragComplete: emptyFunction, }, hideSource: false }); @@ -235,7 +235,7 @@ 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.data = json; } itemDropped = () => { @@ -251,29 +251,26 @@ 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 tfield = await Cast(f.title, "string"); + if (tfield !== undefined) { + tab.titleElement[0].textContent = f.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 () { @@ -319,32 +316,29 @@ 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 = () => { - let pw = this._document!.GetNumber(KeyStore.NativeWidth, 0); - return pw ? pw : this._panelWidth; - } - nativeHeight = () => { - let pw = this._document!.GetNumber(KeyStore.NativeHeight, 0); - return pw ? pw : 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); + const nativeH = this.nativeHeight(); + const nativeW = this.nativeWidth(); + let wscale = this._panelWidth / nativeW; + if (wscale * nativeH > this._panelHeight) { + return this._panelHeight / nativeH; + } return 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(); @@ -355,7 +349,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { 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} @@ -367,9 +361,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { selectOnLoad={false} parentActive={returnTrue} whenActiveChanged={emptyFunction} - focus={emptyDocFunction} + focus={emptyFunction} ContainingCollectionView={undefined} /> - </div>); + </div >); } render() { diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index b6d5f1bfa..2e1175f28 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -7,15 +7,9 @@ import { observer } from "mobx-react"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; 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, returnZero } 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,43 +19,45 @@ import { DocumentView } from "../nodes/DocumentView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; +import { Opt, Field, Doc, Id } from "../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { listSpec } from "../../../new_fields/Schema"; +import { List } from "../../../new_fields/List"; // 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 splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); } + @computed get columns() { return Cast(this.props.Document.columns, listSpec("string"), []); } @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } renderCell = (rowProps: CellInfo) => { @@ -74,7 +70,7 @@ export class CollectionSchemaView extends CollectionSubView { isTopMost: false, selectOnLoad: false, ScreenToLocalTransform: Transform.Identity, - focus: emptyDocFunction, + focus: emptyFunction, active: returnFalse, whenActiveChanged: emptyFunction, PanelHeight: returnZero, @@ -85,34 +81,27 @@ export class CollectionSchemaView extends CollectionSubView { ); 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 } }); @@ -128,14 +117,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 > ); } @@ -166,36 +154,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.columns, listSpec("string")); + if (list === undefined) { + this.props.Document.columns = 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) => { @@ -208,8 +197,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(); } } @@ -222,7 +210,7 @@ export class CollectionSchemaView extends CollectionSubView { @action addColumn = () => { - this.columns.push(new Key(this._newKeyName)); + this.columns.push(this._newKeyName); this._newKeyName = ""; } @@ -237,21 +225,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[]); + get previewDocument(): Doc | undefined { + const children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; - return selected ? (this.previewScript ? selected.Get(new Key(this.previewScript)) as Document : selected) : 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(); @@ -259,7 +248,7 @@ 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() { @@ -274,7 +263,7 @@ export class CollectionSchemaView extends CollectionSubView { 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} /> @@ -286,18 +275,19 @@ 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 => proto._proxies.forEach((val: any, key: string) => 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() { @@ -331,16 +321,16 @@ export class CollectionSchemaView extends CollectionSubView { render() { library.add(faCog); library.add(faPlus); - const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); + const children = Cast(this.props.Document[this.props.fieldKey], listSpec(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 f9fc7be5a..558a8728f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,28 +1,26 @@ 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, ObjectField, Opt } from "../../../new_fields/Doc"; +import { DocComponent } from "../DocComponent"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, PromiseValue } from "../../../new_fields/Types"; +import { List } from "../../../new_fields/List"; +import { DocServer } from "../../DocServer"; 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,176 +31,193 @@ 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() { + return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)); + } + + @action + protected async 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]; + 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.aliasOnDrop || de.data.copyOnDrop) { + ["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.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); + } + 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; } - ctor = Documents.WebDocument; - options = { height: options.width, ...options, title: path, nativeWidth: undefined }; + if (type.indexOf("pdf") !== -1) { + ctor = Docs.PdfDocument; + options.nativeWidth = 1200; + } + if (type.indexOf("excel") !== -1) { + ctor = Docs.DBDocument; + options.copyDraggedItems = true; + } + 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 (text && text.startsWith("<div")) { + return; + } + e.stopPropagation(); + e.preventDefault(); - if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) { - this.props.addDocument(Documents.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text }), false); - return; - } + 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 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["content-type"]; - if (type) { - this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }) - .then(doc => doc && this.props.addDocument(doc, false)); - } + 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.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); + } + 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); - } - let type = item.type; - if (item.kind === "file") { - let file = item.getAsFile(); - let dropFileName = file ? file.name : "-empty-"; - let formData = new FormData(); - if (file) formData.append('file', file); - - promises.push(fetch(upload, { - method: 'POST', - body: formData - }).then(async (res: Response) => - (await res.json()).map(action((file: any) => - this.getDocumentFromType(type, window.location.origin + file, { ...options, nativeWidth: 600, width: 300, title: dropFileName }). - then(doc => doc && this.props.addDocument(doc, false)))))); + 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/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 56342c84c..80900c450 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,5 @@ import { action, computed } 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"; @@ -12,7 +10,7 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { InkingCanvas } from "../../InkingCanvas"; 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"; @@ -20,10 +18,23 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); -import { BooleanField } from "../../../../fields/BooleanField"; +import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema"; +import { Doc, Id } from "../../../../new_fields/Doc"; +import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types"; +import { pageSchema } from "../../nodes/ImageBox"; +import { List } from "../../../../new_fields/List"; + +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; @@ -31,42 +42,38 @@ export class CollectionFreeFormView extends CollectionSubView { 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 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); } - @action - private addDocument = (newBox: Document, allowDuplicates: boolean) => { + 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 = Cast(doc.page, "number", -1); + return page === curPage || page === -1; + }); } @undoBatch @@ -75,22 +82,22 @@ export class CollectionFreeFormView extends CollectionSubView { if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { let dragDoc = de.data.droppedDocuments[0]; - let zoom = dragDoc.GetNumber(KeyStore.ZoomBasis, 1); + 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 = de.data.droppedDocuments[0].GetNumber(KeyStore.X, 0); - let dropY = de.data.droppedDocuments[0].GetNumber(KeyStore.Y, 0); + 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.GetNumber(KeyStore.Width, 0)) { - d.SetNumber(KeyStore.Width, 300); + d.x = x + NumCast(d.x) - dropX; + d.y = y + NumCast(d.y) - dropY; + if (!NumCast(d.width)) { + d.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); + if (!NumCast(d.height)) { + let nw = NumCast(d.nativeWidth); + let nh = NumCast(d.nativeHeight); + d.height = nw && nh ? nh / nw * d.Width() : 300; } this.bringToFront(d); }); @@ -109,7 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView { @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); @@ -133,20 +140,20 @@ export class CollectionFreeFormView extends CollectionSubView { @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]]); @@ -170,10 +177,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; } @@ -182,8 +189,8 @@ 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 { @@ -198,7 +205,7 @@ export class CollectionFreeFormView extends CollectionSubView { 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(); } @@ -209,8 +216,8 @@ export class CollectionFreeFormView extends CollectionSubView { 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,25 +229,25 @@ export class CollectionFreeFormView extends CollectionSubView { onDragOver = (): void => { } - @action - bringToFront(doc: Document) { - let docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).slice(); - docs.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)); - doc.SetNumber(KeyStore.ZIndex, docs.length + 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) => { this.setPan( - doc.GetNumber(KeyStore.X, 0) + doc.Width() / 2, - doc.GetNumber(KeyStore.Y, 0) + doc.Height() / 2); + Cast(doc.x, "number", 0) + Cast(doc.width, "number", 0) / 2, + Cast(doc.y, "number", 0) + Cast(doc.height, "number", 0) / 2); this.props.focus(this.props.Document); } - getDocumentViewProps(document: Document): DocumentViewProps { + getDocumentViewProps(document: Doc): DocumentViewProps { return { Document: document, toggleMinimized: emptyFunction, @@ -250,8 +257,8 @@ export class CollectionFreeFormView extends CollectionSubView { ScreenToLocalTransform: this.getTransform, isTopMost: false, selectOnLoad: document.Id === this._selectOnLoaded, - PanelWidth: document.Width, - PanelHeight: document.Height, + PanelWidth: () => Cast(document.width, "number", 0),//TODO Types These are inline functions + PanelHeight: () => Cast(document.height, "number", 0), ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, @@ -262,13 +269,14 @@ export class CollectionFreeFormView extends CollectionSubView { @computed 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) => { + var page = Cast(doc.page, "number", -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; }, [] as JSX.Element[]); @@ -317,9 +325,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() { @@ -330,9 +338,9 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> { @observer class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps> { @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} + (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />); } render() { |
