diff options
Diffstat (limited to 'src/client/views')
-rw-r--r-- | src/client/views/EditableView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 9 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 44 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 15 | ||||
-rw-r--r-- | src/client/views/collections/CollectionViewChromes.scss | 113 | ||||
-rw-r--r-- | src/client/views/collections/CollectionViewChromes.tsx | 261 | ||||
-rw-r--r-- | src/client/views/collections/KeyRestrictionRow.tsx | 45 |
8 files changed, 427 insertions, 66 deletions
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index a5bb40243..bd00d47b9 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -116,7 +116,7 @@ export class EditableView extends React.Component<EditableProps> { return ( <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }} - onClick={this.onClick} > + onClick={this.onClick}> <span style={{ fontStyle: this.props.fontStyle }}>{this.props.contents}</span> </div> ); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 94a4835a1..69602deed 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDown, faCaretUp, faLongArrowAltRight, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; @@ -127,7 +127,9 @@ export class MainView extends React.Component { library.add(faCut); library.add(faCommentAlt); library.add(faThumbtack); + library.add(faLongArrowAltRight); library.add(faCheck); + library.add(faCaretUp); library.add(faArrowDown); library.add(faArrowUp); library.add(faCloudUploadAlt); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e4e6f92e3..a78a47ffb 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -249,6 +249,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return false; } + sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => { + let descending = BoolCast(this.props.Document.stackingHeadersSortDescending); + let firstEntry = descending ? b : a; + let secondEntry = descending ? a : b; + return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1; + } + render() { let headings = Array.from(this.Sections.keys()); let editableViewProps = { @@ -264,7 +271,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())], ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)], ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */} - {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort((a, b) => a[0].toString() > b[0].toString() ? 1 : -1). + {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). map(section => this.section(section[0], section[1] as Doc[])) : this.section(undefined, this.filteredChildren)} {this.props.Document.sectionFilter ? diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index fe24c63c7..582adc418 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -16,6 +16,8 @@ import "./CollectionStackingView.scss"; import { Docs } from "../../documents/Documents"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { CompileScript } from "../../util/Scripting"; interface CSVFieldColumnProps { @@ -35,6 +37,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC private _dropRef: HTMLDivElement | null = null; private dropDisposer?: DragManager.DragDropDisposer; + private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; @@ -182,6 +185,43 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC } } + startDrag = (e: PointerEvent) => { + let alias = Doc.MakeAlias(this.props.parent.props.Document); + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let value = this.getValue(this._heading); + value = typeof value === "string" ? `"${value}"` : value; + let script = `return doc.${key} === ${value}`; + let compiled = CompileScript(script, { params: { doc: Doc.name } }); + if (compiled.compiled) { + let scriptField = new ScriptField(compiled); + alias.viewSpecScript = scriptField; + let dragData = new DragManager.DocumentDragData([alias], [alias.proto]); + DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY); + } + + e.stopPropagation(); + document.removeEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + } + + pointerUp = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + + document.removeEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + } + + headerDown = (e: React.PointerEvent<HTMLDivElement>) => { + e.stopPropagation(); + e.preventDefault(); + + document.removeEventListener("pointermove", this.startDrag); + document.addEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + document.addEventListener("pointerup", this.pointerUp); + } + render() { let cols = this.props.cols(); let key = StrCast(this.props.parent.props.Document.sectionFilter); @@ -199,11 +239,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC oneLine: true } let headingView = this.props.headingObject ? - <div key={heading} className="collectionStackingView-sectionHeader" + <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef} style={{ width: (style.columnWidth) / (uniqueHeadings.length + 1) }}> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */} - <div className="collectionStackingView-sectionHeader-subCont" + <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown} title={evContents === `No ${key} value` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""} style={{ diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 2ddefb3c0..24c8adb35 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -21,6 +21,8 @@ import { CollectionView } from "./CollectionView"; import React = require("react"); import { MainView } from "../MainView"; import { Utils } from "../../../Utils"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { CompileScript } from "../../util/Scripting"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; @@ -54,7 +56,18 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { let self = this; //TODO tfs: This might not be what we want? //This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue) - return DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]); + let docs = DocListCast(this.extensionDoc[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]); + let viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); + if (viewSpecScript) { + let script = viewSpecScript.script; + docs = docs.filter(d => { + let res = script.run({ doc: d }); + if (res.success) { + return res.result; + } + }); + } + return docs; } get childDocList() { //TODO tfs: This might not be what we want? diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index e5cb1b546..3103cd309 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -1,12 +1,120 @@ @import "../globalCssVariables"; +@import '~js-datepicker/dist/datepicker.min.css'; + +.collectionViewBaseChrome { + display: flex; + + .collectionViewBaseChrome-viewPicker { + font-size: 75%; + text-transform: uppercase; + letter-spacing: 2px; + background: rgb(238, 238, 238); + color: grey; + outline-color: black; + border: none; + padding: 12px 10px 11px 10px; + } + + .collectionViewBaseChrome-viewPicker:active { + outline-color: black; + } + + .collectionViewBaseChrome-collapse { + margin-right: 10px; + transition: all .5s; + } + + .collectionViewBaseChrome-viewSpecs { + margin-left: 10px; + display: grid; + + .collectionViewBaseChrome-viewSpecsInput { + padding: 12px 10px 11px 10px; + border: 0px; + color: grey; + text-align: center; + text-transform: uppercase; + letter-spacing: 2px; + outline-color: black; + font-size: 75%; + background: rgb(238, 238, 238); + height: 100%; + width: 150px; + } + + .collectionViewBaseChrome-viewSpecsMenu { + overflow: hidden; + transition: height .5s, display .5s; + position: absolute; + top: 60px; + z-index: 100; + display: flex; + flex-direction: column; + background: rgb(238, 238, 238); + box-shadow: grey 2px 2px 4px; + + .qs-datepicker { + left: unset; + right: 0; + } + + .collectionViewBaseChrome-viewSpecsMenu-row { + display: grid; + grid-template-columns: 150px 200px 150px; + margin-top: 10px; + margin-right: 10px; + + .collectionViewBaseChrome-viewSpecsMenu-rowLeft, + .collectionViewBaseChrome-viewSpecsMenu-rowMiddle, + .collectionViewBaseChrome-viewSpecsMenu-rowRight { + font-size: 75%; + letter-spacing: 2px; + color: grey; + margin-left: 10px; + padding: 5px; + border: none; + outline-color: black; + } + } + + .collectionViewBaseChrome-viewSpecsMenu-lastRow { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 10px; + margin: 10px; + } + } + } +} .collectionStackingViewChrome { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr auto; padding-bottom: 10px; border-bottom: .5px solid lightgrey; margin: 10px; + .collectionStackingViewChrome-cont { + display: flex; + justify-content: space-between; + } + + .collectionStackingViewChrome-sort { + display: flex; + align-items: center; + justify-content: space-between; + + .collectionStackingViewChrome-sortIcon { + transition: transform .5s; + margin-left: 10px; + } + } + + button:hover { + transform: scale(1); + } + + .collectionStackingViewChrome-sectionFilter-cont { justify-self: right; display: flex; @@ -21,10 +129,9 @@ .collectionStackingViewChrome-sectionFilter { color: white; - width: fit-content; + width: 100px; text-align: center; background: rgb(238, 238, 238); - height: 37px; .editable-view-input, input, diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 52fee26bf..2edac384d 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -3,55 +3,189 @@ import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; import { CollectionViewType } from "./CollectionBaseView"; import { undoBatch } from "../../util/UndoManager"; -import { action, observable, runInAction, computed } from "mobx"; +import { action, observable, runInAction, computed, IObservable, IObservableValue } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { DocLike } from "../MetadataEntryMenu"; -const higflyout = require("@hig/flyout"); -export const Flyout = higflyout.default; import * as Autosuggest from 'react-autosuggest'; import { EditableView } from "../EditableView"; -import { StrCast } from "../../../new_fields/Types"; +import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Utils } from "../../../Utils"; +import KeyRestrictionRow from "./KeyRestrictionRow"; +import { CompileScript } from "../../util/Scripting"; +import { ScriptField } from "../../../new_fields/ScriptField"; +const datepicker = require('js-datepicker'); interface CollectionViewChromeProps { CollectionView: CollectionView; } +let stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation(); + +@observer class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> { + @observable private _viewSpecsOpen: boolean = false; + @observable private _dateWithinValue: string = ""; + @observable private _dateValue: Date = new Date(); + @observable private _keyRestrictions: [JSX.Element, string][] = []; + @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); } + + private _picker: any; + private _datePickerElGuid = Utils.GenerateGuid(); + + componentDidMount = () => { + this._picker = datepicker("#" + this._datePickerElGuid, { + disabler: (date: Date) => date > new Date(), + onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), + dateSelected: new Date() + }); + + runInAction(() => { + this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[0][1] = value)} />, ""]); + this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={false} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]); + }); + } + @undoBatch viewChanged = (e: React.ChangeEvent) => { //@ts-ignore - switch (e.target.selectedOptions[0].value) { - case "freeform": - this.props.CollectionView.props.Document.viewType = CollectionViewType.Freeform; - break; - case "schema": - this.props.CollectionView.props.Document.viewType = CollectionViewType.Schema; - break; - case "treeview": - this.props.CollectionView.props.Document.viewType = CollectionViewType.Tree; - break; - case "stacking": - this.props.CollectionView.props.Document.viewType = CollectionViewType.Stacking; - break; - case "masonry": - this.props.CollectionView.props.Document.viewType = CollectionViewType.Masonry; - break; - default: - break; + this.props.CollectionView.props.Document.viewType = parseInt(e.target.selectedOptions[0].value); + } + + @action + openViewSpecs = (e: React.SyntheticEvent) => { + this._viewSpecsOpen = true; + + //@ts-ignore + if (!e.target.classList[0].startsWith("qs")) { + this.closeDatePicker(); + } + + e.stopPropagation(); + document.removeEventListener("pointerdown", this.closeViewSpecs); + document.addEventListener("pointerdown", this.closeViewSpecs); + } + + @action closeViewSpecs = () => { this._viewSpecsOpen = false; document.removeEventListener("pointerdown", this.closeViewSpecs); }; + + @action + openDatePicker = (e: React.PointerEvent) => { + this.openViewSpecs(e); + if (this._picker) { + this._picker.alwaysShow = true; + this._picker.show(); + // TODO: calendar is offset when zoomed in/out + // this._picker.calendar.style.position = "absolute"; + // let transform = this.props.CollectionView.props.ScreenToLocalTransform(); + // let x = parseInt(this._picker.calendar.style.left) / transform.Scale; + // let y = parseInt(this._picker.calendar.style.top) / transform.Scale; + // this._picker.calendar.style.left = x; + // this._picker.calendar.style.top = y; + + e.stopPropagation(); } } + @action + addKeyRestriction = (e: React.MouseEvent) => { + let index = this._keyRestrictions.length; + this._keyRestrictions.push([<KeyRestrictionRow key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[index][1] = value)} />, ""]); + + this.openViewSpecs(e); + } + + @action + applyFilter = (e: React.MouseEvent) => { + this.openViewSpecs(e); + + let keyRestrictionScript = `${this._keyRestrictions.map(i => i[1]) + .reduce((acc: string, value: string, i: number) => value ? `${acc} && ${value}` : acc)}`; + let yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0; + let monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0; + let weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0; + let dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7; + let lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset); + let upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1); + let dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`; + let fullScript = `return ${dateRestrictionScript} && ${keyRestrictionScript}`; + let compiled = CompileScript(fullScript, { params: { doc: Doc.name } }); + if (compiled.compiled) { + this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); + } + } + + @action + closeDatePicker = () => { + if (this._picker) { + this._picker.alwaysShow = false; + this._picker.hide(); + } + document.removeEventListener("pointerdown", this.closeDatePicker); + } + render() { return ( <div className="collectionViewBaseChrome"> - <select onChange={this.viewChanged}> - <option value="freeform" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Freeform}>Freeform View</option> - <option value="schema" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Schema}>Schema View</option> - <option value="treeview" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Tree}>Tree View</option> - <option value="stacking" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Stacking}>Stacking View</option> - <option value="masonry" selected={this.props.CollectionView.props.Document.viewType === CollectionViewType.Masonry}>Masonry View</option> + <button className="collectionViewBaseChrome-collapse" title="Collapse collection chrome"> + <FontAwesomeIcon icon="caret-up" size="2x" /> + </button> + <select + className="collectionViewBaseChrome-viewPicker" + onPointerDown={stopPropagation} + onChange={this.viewChanged} + value={NumCast(this.props.CollectionView.props.Document.viewType)}> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="1">Freeform View</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="2">Schema View</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="4">Tree View</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View></option> </select> + <div className="collectionViewBaseChrome-viewSpecs"> + <input className="collectionViewBaseChrome-viewSpecsInput" + placeholder="Filter Documents" + value={this.filterValue ? this.filterValue.script.originalScript : ""} + onPointerDown={this.openViewSpecs} /> + <div className="collectionViewBaseChrome-viewSpecsMenu" + onPointerDown={this.openViewSpecs} + style={{ + height: this._viewSpecsOpen ? "fit-content" : "0px", + overflow: this._viewSpecsOpen ? "initial" : "hidden" + }}> + {this._keyRestrictions.map(i => i[0])} + <div className="collectionViewBaseChrome-viewSpecsMenu-row"> + <div className="collectionViewBaseChrome-viewSpecsMenu-rowLeft"> + CREATED WITHIN: + </div> + <select className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" + style={{ textTransform: "uppercase", textAlign: "center" }} + value={this._dateWithinValue} + onChange={(e) => runInAction(() => this._dateWithinValue = e.target.value)}> + <option value="1d">1 day of</option> + <option value="3d">3 days of</option> + <option value="1w">1 week of</option> + <option value="2w">2 weeks of</option> + <option value="1m">1 month of</option> + <option value="2m">2 months of</option> + <option value="6m">6 months of</option> + <option value="1y">1 year of</option> + </select> + <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" + id={this._datePickerElGuid} + value={this._dateValue.toLocaleDateString()} + onPointerDown={this.openDatePicker} + placeholder="Value" /> + </div> + <div className="collectionViewBaseChrome-viewSpecsMenu-lastRow"> + <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.addKeyRestriction}> + ADD KEY RESTRICTION + </button> + <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}> + APPLY FILTER + </button> + </div> + </div> + </div> </div> ) } @@ -62,6 +196,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView @observable private _currentKey: string = ""; @observable private suggestions: string[] = []; + @computed private get descending() { return BoolCast(this.props.CollectionView.props.Document.stackingHeadersSortDescending); } @computed get sectionFilter() { return StrCast(this.props.CollectionView.props.Document.sectionFilter); } getKeySuggestions = async (value: string): Promise<string[]> => { @@ -109,41 +244,53 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView return true; } + @action toggleSort = () => { this.props.CollectionView.props.Document.stackingHeadersSortDescending = !this.props.CollectionView.props.Document.stackingHeadersSortDescending; } @action resetValue = () => { this._currentKey = this.sectionFilter; }; render() { return ( <div className="collectionStackingViewChrome"> <CollectionViewBaseChrome CollectionView={this.props.CollectionView} /> - <div className="collectionStackingViewChrome-sectionFilter-cont"> - <div className="collectionStackingViewChrome-sectionFilter-label"> - Group items by: - </div> - <div className="collectionStackingViewChrome-sectionFilter"> - <EditableView - GetValue={() => this.sectionFilter} - autosuggestProps={ - { - resetValue: this.resetValue, - value: this._currentKey, - onChange: this.onKeyChange, - autosuggestProps: { - inputProps: - { - value: this._currentKey, - onChange: this.onKeyChange - }, - getSuggestionValue: this.getSuggestionValue, - suggestions: this.suggestions, - alwaysRenderSuggestions: true, - renderSuggestion: this.renderSuggestion, - onSuggestionsFetchRequested: this.onSuggestionFetch, - onSuggestionsClearRequested: this.onSuggestionClear - } - }} - SetValue={this.setValue} - contents={this.sectionFilter ? this.sectionFilter : "N/A"} - /> + <div className="collectionStackingViewChrome-cont"> + <button className="collectionStackingViewChrome-sort" onClick={this.toggleSort}> + <div className="collectionStackingViewChrome-sortLabel"> + Sort + </div> + <div className="collectionStackingViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}> + <FontAwesomeIcon icon="caret-up" size="2x" color="white" /> + </div> + </button> + <div className="collectionStackingViewChrome-sectionFilter-cont"> + <div className="collectionStackingViewChrome-sectionFilter-label"> + Group items by: + </div> + <div className="collectionStackingViewChrome-sectionFilter"> + <EditableView + GetValue={() => this.sectionFilter} + autosuggestProps={ + { + resetValue: this.resetValue, + value: this._currentKey, + onChange: this.onKeyChange, + autosuggestProps: { + inputProps: + { + value: this._currentKey, + onChange: this.onKeyChange + }, + getSuggestionValue: this.getSuggestionValue, + suggestions: this.suggestions, + alwaysRenderSuggestions: true, + renderSuggestion: this.renderSuggestion, + onSuggestionsFetchRequested: this.onSuggestionFetch, + onSuggestionsClearRequested: this.onSuggestionClear + } + }} + oneLine + SetValue={this.setValue} + contents={this.sectionFilter ? this.sectionFilter : "N/A"} + /> + </div> </div> </div> </div> diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx new file mode 100644 index 000000000..8051a8359 --- /dev/null +++ b/src/client/views/collections/KeyRestrictionRow.tsx @@ -0,0 +1,45 @@ +import * as React from "react"; +import { observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField"; + +interface IKeyRestrictionProps { + contains: boolean; + script: (value: string) => void; +} + +@observer +export default class KeyRestrictionRow extends React.Component<IKeyRestrictionProps> { + @observable private _key = ""; + @observable private _value = ""; + @observable private _contains = this.props.contains; + + render() { + if (this._key && this._value) { + let parsedValue: string | number = `"${this._value}"`; + let parsed = parseInt(this._value); + if (!isNaN(parsed)) { + parsedValue = parsed; + } + let scriptText = `(doc.${this._key} ${this._contains ? "===" : "!=="} ${parsedValue})`; + this.props.script(scriptText); + } + return ( + <div className="collectionViewBaseChrome-viewSpecsMenu-row"> + <input className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" + value={this._key} + onChange={(e) => runInAction(() => this._key = e.target.value)} + placeholder="KEY" /> + <button className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" + style={{ background: PastelSchemaPalette.get(this._contains ? "green" : "red") }} + onClick={() => runInAction(() => this._contains = !this._contains)}> + {this._contains ? "CONTAINS" : "DOES NOT CONTAIN"} + </button> + <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" + value={this._value} + onChange={(e) => runInAction(() => this._value = e.target.value)} + placeholder="VALUE" /> + </div> + ) + } +}
\ No newline at end of file |