diff options
Diffstat (limited to 'src/client/views/collections/CollectionSchemaView.tsx')
-rw-r--r-- | src/client/views/collections/CollectionSchemaView.tsx | 294 |
1 files changed, 130 insertions, 164 deletions
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 1defdba7e..90077b053 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -4,15 +4,15 @@ import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 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 } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { emptyDocFunction, emptyFunction, returnFalse, returnOne } from "../../../Utils"; +import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils"; import { Server } from "../../Server"; import { SetupDrag } from "../../util/DragManager"; import { CompileScript, ToField } from "../../util/Scripting"; @@ -34,42 +34,35 @@ import { CollectionSubView } from "./CollectionSubView"; 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 = { @@ -83,7 +76,7 @@ export class CollectionSchemaView extends CollectionSubView { ScreenToLocalTransform: Transform.Identity, focus: emptyDocFunction, active: returnFalse, - onActiveChanged: emptyFunction, + whenActiveChanged: emptyFunction, }; let contents = ( <FieldView {...props} /> @@ -107,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) { @@ -165,9 +158,9 @@ export class CollectionSchemaView extends CollectionSubView { }; } - @computed - get columns() { - return this.props.Document.GetList(KeyStore.ColumnsKey, [] as Key[]); + private createTarget = (ele: HTMLDivElement) => { + this._mainCont = ele; + super.CreateDropTarget(ele); } @action @@ -187,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(this.props.fieldKey, [] as Document[]); - 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 @@ -230,28 +204,6 @@ 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(this.props.fieldKey, [] as Document[]); - 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); - } - - @computed - get borderWidth() { return COLLECTION_BORDER_WIDTH; } - getContentScaling = (): number => this._contentScaling; - getPanelWidth = (): number => this._panelWidth; - getPanelHeight = (): number => this._panelHeight; - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling); - getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling); - onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { if (this.props.isSelected()) @@ -260,127 +212,141 @@ export class CollectionSchemaView extends CollectionSubView { } } - @action - addColumn = () => { - 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++; + addColumn = () => { + this.columns.push(new Key(this._newKeyName)); + this._newKeyName = ""; } - @observable previewScript: string = "this"; + @action + newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._newKeyName = e.currentTarget.value; + } + + @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; + 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={this.props.CollectionView} - focus={emptyDocFunction} - 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}> - <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)))); - }} - 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} + 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} />)); + } + + 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 |