diff options
Diffstat (limited to 'src/client/views/collections/CollectionSchemaView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionSchemaView.tsx | 319 | 
1 files changed, 143 insertions, 176 deletions
| diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 587f60b3d..90077b053 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -2,31 +2,29 @@ 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, trace, untracked } from "mobx"; +import { action, computed, observable, untracked } from "mobx";  import { observer } from "mobx-react"; -import Measure from "react-measure";  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, FieldWaiting } from "../../../fields/Field"; +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 { setupDrag } from "../../util/DragManager"; +import { SetupDrag } from "../../util/DragManager";  import { CompileScript, ToField } from "../../util/Scripting";  import { Transform } from "../../util/Transform"; +import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";  import { anchorPoints, Flyout } from "../DocumentDecorations";  import '../DocumentDecorations.scss';  import { EditableView } from "../EditableView";  import { DocumentView } from "../nodes/DocumentView";  import { FieldView, FieldViewProps } from "../nodes/FieldView";  import "./CollectionSchemaView.scss"; -import { CollectionView } from "./CollectionView";  import { CollectionSubView } from "./CollectionSubView"; -import { TextField } from "../../../fields/TextField"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; -import { emptyFunction, returnFalse } from "../../../Utils";  // bcz: need to add drag and drop of rows and columns.  This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 @@ -36,61 +34,55 @@ import { emptyFunction, returnFalse } from "../../../Utils";  class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggle: (key: Key) => void }> {      @observable key: Key | undefined; -    componentWillReceiveProps() { -        Server.GetField(this.props.keyId, action((field: Opt<Field>) => { -            if (field instanceof Key) { -                this.key = field; -            } -        })); +    constructor(props: any) { +        super(props); +        Server.GetField(this.props.keyId, action((field: Opt<Field>) => field instanceof Key && (this.key = field)));      }      render() { -        if (this.key) { -            return (<div key={this.key.Id}> +        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 (null);      }  }  @observer  export class CollectionSchemaView extends CollectionSubView { -    private _mainCont = React.createRef<HTMLDivElement>(); +    private _mainCont?: HTMLDivElement;      private _startSplitPercent = 0;      private DIVIDER_WIDTH = 4;      @observable _columns: Array<Key> = [KeyStore.Title, KeyStore.Data, KeyStore.Author]; -    @observable _contentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ContentScaling prop of the DocumentView -    @observable _dividerX = 0; -    @observable _panelWidth = 0; -    @observable _panelHeight = 0;      @observable _selectedIndex = 0;      @observable _columnsPercentage = 0;      @observable _keys: Key[] = []; +    @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; }      renderCell = (rowProps: CellInfo) => {          let props: FieldViewProps = {              Document: rowProps.value[0],              fieldKey: rowProps.value[1], -            isSelected: () => false, -            select: () => { }, +            ContainingCollectionView: this.props.CollectionView, +            isSelected: returnFalse, +            select: emptyFunction,              isTopMost: false,              selectOnLoad: false,              ScreenToLocalTransform: Transform.Identity, -            focus: emptyFunction, +            focus: emptyDocFunction,              active: returnFalse, -            onActiveChanged: emptyFunction, +            whenActiveChanged: emptyFunction,          };          let contents = (              <FieldView {...props} />          );          let reference = React.createRef<HTMLDivElement>(); -        let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument); +        let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument);          let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => {              const res = run({ this: doc });              if (!res.success) return false; @@ -108,11 +100,11 @@ export class CollectionSchemaView extends CollectionSubView {              return false;          };          return ( -            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}> +            <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document.Id} ref={reference}>                  <EditableView                      display={"inline"}                      contents={contents} -                    height={56} +                    height={Number(MAX_ROW_HEIGHT)}                      GetValue={() => {                          let field = props.Document.Get(props.fieldKey);                          if (field && field instanceof Field) { @@ -166,9 +158,9 @@ export class CollectionSchemaView extends CollectionSubView {          };      } -    @computed -    get columns() { -        return this.props.Document.GetList<Key>(KeyStore.ColumnsKey, []); +    private createTarget = (ele: HTMLDivElement) => { +        this._mainCont = ele; +        super.CreateDropTarget(ele);      }      @action @@ -188,31 +180,12 @@ export class CollectionSchemaView extends CollectionSubView {      //toggles preview side-panel of schema      @action      toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => { -        this._startSplitPercent = this.splitPercentage; -        if (this._startSplitPercent === this.splitPercentage) { -            this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); -        } -    } - -    @computed -    get findAllDocumentKeys(): { [id: string]: boolean } { -        const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []); -        let keys: { [id: string]: boolean } = {}; -        if (this._optionsActivated > -1) { -            // 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)))); -        } -        this.columns.forEach(key => keys[key.Id] = true); -        return keys; +        this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);      }      @action      onDividerMove = (e: PointerEvent): void => { -        let nativeWidth = this._mainCont.current!.getBoundingClientRect(); +        let nativeWidth = this._mainCont!.getBoundingClientRect();          this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));      }      @action @@ -231,155 +204,149 @@ export class CollectionSchemaView extends CollectionSubView {          document.addEventListener('pointerup', this.onDividerUp);      } -    @observable _tableWidth = 0; -    @action -    setTableDimensions = (r: any) => { -        this._tableWidth = r.entry.width; -    } -    @action -    setScaling = (r: any) => { -        const children = this.props.Document.GetList<Document>(this.props.fieldKey, []); -        const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; -        this._panelWidth = r.entry.width; -        this._panelHeight = r.entry.height ? r.entry.height : this._panelHeight; -        this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width); +    onPointerDown = (e: React.PointerEvent): void => { +        if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { +            if (this.props.isSelected()) +                e.stopPropagation(); +            else e.preventDefault(); +        }      } -    getContentScaling = (): number => this._contentScaling; -    getPanelWidth = (): number => this._panelWidth; -    getPanelHeight = (): number => this._panelHeight; -    getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); -    getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - -    focusDocument = (doc: Document) => { }; - -    onPointerDown = (e: React.PointerEvent): void => { -        if (this.props.isSelected()) { +    onWheel = (e: React.WheelEvent): void => { +        if (this.props.active()) {              e.stopPropagation();          }      }      @action      addColumn = () => { -        this.columns.push(new Key(this.newKeyName)); -        this.newKeyName = ""; +        this.columns.push(new Key(this._newKeyName)); +        this._newKeyName = "";      } -    @observable -    newKeyName: string = ""; -      @action      newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => { -        this.newKeyName = e.currentTarget.value; -    } -    onWheel = (e: React.WheelEvent): void => { -        if (this.props.active()) { -            e.stopPropagation(); -        } -    } - -    @observable _optionsActivated: number = 0; -    @action -    OptionsMenuDown = (e: React.PointerEvent) => { -        this._optionsActivated++; +        this._newKeyName = e.currentTarget.value;      } -    @observable previewScript: string = "this"; +    @observable previewScript: string = "";      @action      onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {          this.previewScript = e.currentTarget.value;      } -    render() { -        library.add(faCog); -        library.add(faPlus); -        const columns = this.columns; -        const children = this.props.Document.GetList<Document>(this.props.fieldKey, []); +    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; -        //all the keys/columns that will be displayed in the schema -        const allKeys = this.findAllDocumentKeys; -        let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined; +        return selected ? (this.previewScript ? selected.Get(new Key(this.previewScript)) as Document : 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 previewContentScaling = () => { +        let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth); +        if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) +            return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight); +        return wscale; +    } +    private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling(); +    private previewPanelHeight = () => this.previewDocNativeHeight() * this.previewContentScaling(); +    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()); +    @computed +    get previewPanel() {          // let doc = CompileScript(this.previewScript, { this: selected }, true)(); -        let content = this._selectedIndex === -1 || !selected ? (null) : ( -            <Measure onResize={this.setScaling}> -                {({ measureRef }) => -                    <div className="collectionSchemaView-content" ref={measureRef}> -                        {doc instanceof Document ? -                            <DocumentView Document={doc} -                                addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} -                                isTopMost={false} -                                selectOnLoad={false} -                                ScreenToLocalTransform={this.getPreviewTransform} -                                ContentScaling={this.getContentScaling} -                                PanelWidth={this.getPanelWidth} -                                PanelHeight={this.getPanelHeight} -                                ContainingCollectionView={undefined} -                                focus={this.focusDocument} -                                parentActive={this.props.active} -                                onActiveChanged={this.props.onActiveChanged} /> : null} -                        <input value={this.previewScript} onChange={this.onPreviewScriptChange} -                            style={{ position: 'absolute', bottom: '0px' }} /> -                    </div> -                } -            </Measure> -        ); -        let dividerDragger = this.splitPercentage === 0 ? (null) : -            <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />; - -        //options button and menu -        let optionsMenu = !this.props.active() ? (null) : (<Flyout -            anchorPoint={anchorPoints.LEFT_TOP} -            content={<div> -                <div id="schema-options-header"><h5><b>Options</b></h5></div> -                <div id="options-flyout-div"> -                    <h6 className="schema-options-subHeader">Preview Window</h6> -                    <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} />  Show Preview </div> -                    <h6 className="schema-options-subHeader" >Displayed Columns</h6> -                    <ul id="schema-col-checklist" > -                        {Array.from(Object.keys(allKeys)).map(item => -                            (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />))} -                    </ul> -                    <input value={this.newKeyName} onChange={this.newKeyChange} /> -                    <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button> +        return !this.previewDocument ? (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} +                        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} +                        parentActive={this.props.active} +                        whenActiveChanged={this.props.whenActiveChanged} +                    />                  </div> +                <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange} +                    style={{ left: `calc(50% - ${Math.min(75, this.previewPanelWidth() / 2)}px)` }} />              </div> -            }> -            <button id="schemaOptionsMenuBtn" onPointerDown={this.OptionsMenuDown}><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button> -        </Flyout>); +        ); +    } -        return ( -            <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} > -                <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> -                    <Measure onResize={this.setTableDimensions}> -                        {({ measureRef }) => -                            <div className="collectionSchemaView-tableContainer" ref={measureRef} style={{ width: `calc(100% - ${this.splitPercentage}%)` }}> -                                <ReactTable -                                    data={children} -                                    pageSize={children.length} -                                    page={0} -                                    showPagination={false} -                                    columns={columns.map(col => ({ -                                        Header: col.Name, -                                        accessor: (doc: Document) => [doc, col], -                                        id: col.Id -                                    }))} -                                    column={{ -                                        ...ReactTableDefaults.column, -                                        Cell: this.renderCell, +    get documentKeysCheckList() { +        const docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); +        let keys: { [id: 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)))); + +        this.columns.forEach(key => keys[key.Id] = true); +        return Array.from(Object.keys(keys)).map(item => +            (<KeyToggle checked={keys[item]} key={item} keyId={item} toggle={this.toggleKey} />)); +    } -                                    }} -                                    getTrProps={this.getTrProps} -                                /> -                            </div>} -                    </Measure> -                    {dividerDragger} -                    <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}> -                        {content} +    get tableOptionsPanel() { +        return !this.props.active() ? (null) : +            (<Flyout +                anchorPoint={anchorPoints.LEFT_TOP} +                content={<div> +                    <div id="schema-options-header"><h5><b>Options</b></h5></div> +                    <div id="options-flyout-div"> +                        <h6 className="schema-options-subHeader">Preview Window</h6> +                        <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} />  Show Preview </div> +                        <h6 className="schema-options-subHeader" >Displayed Columns</h6> +                        <ul id="schema-col-checklist" > +                            {this.documentKeysCheckList} +                        </ul> +                        <input value={this._newKeyName} onChange={this.newKeyChange} /> +                        <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>                      </div> -                    {optionsMenu}                  </div> -            </div > +                }> +                <button id="schemaOptionsMenuBtn" ><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button> +            </Flyout>); +    } + +    @computed +    get dividerDragger() { +        return this.splitPercentage === 0 ? (null) : +            <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />; +    } + +    render() { +        library.add(faCog); +        library.add(faPlus); +        const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); +        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 +                        }))} +                        column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }} +                        getTrProps={this.getTrProps} +                    /> +                </div> +                {this.dividerDragger} +                {this.previewPanel} +                {this.tableOptionsPanel} +            </div>          );      }  }
\ No newline at end of file | 
