diff options
| author | Sam Wilkins <samwilkins333@gmail.com> | 2019-08-20 20:41:41 -0400 |
|---|---|---|
| committer | Sam Wilkins <samwilkins333@gmail.com> | 2019-08-20 20:41:41 -0400 |
| commit | 3b99cb39a4a860258b93f6af5ff8f238cf78b2e8 (patch) | |
| tree | b533ee5fd791da9be8064f3afaafdee317e877ac /src/client/views/collections/CollectionViewChromes.tsx | |
| parent | dc587b0fb493d869e6cd38fa94b81105da4fbaab (diff) | |
| parent | 700dfc5add1ecd9c2b1ecafcdc593ff821b7a6a6 (diff) | |
merged with master
Diffstat (limited to 'src/client/views/collections/CollectionViewChromes.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionViewChromes.tsx | 295 |
1 files changed, 248 insertions, 47 deletions
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 52c47e7e8..25b152d4e 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -1,26 +1,24 @@ -import * as React from "react"; -import { CollectionView } from "./CollectionView"; -import "./CollectionViewChromes.scss"; -import { CollectionViewType } from "./CollectionBaseView"; -import { undoBatch } from "../../util/UndoManager"; -import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; +import * as React from "react"; import { Doc, DocListCast } from "../../../new_fields/Doc"; -import { DocLike } from "../MetadataEntryMenu"; -import * as Autosuggest from 'react-autosuggest'; -import { EditableView } from "../EditableView"; -import { StrCast, NumCast, BoolCast, Cast } from "../../../new_fields/Types"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; -import KeyRestrictionRow from "./KeyRestrictionRow"; +import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { CollectionSchemaView } from "./CollectionSchemaView"; +import { undoBatch } from "../../util/UndoManager"; +import { EditableView } from "../EditableView"; import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss"; -import { listSpec } from "../../../new_fields/Schema"; -import { List } from "../../../new_fields/List"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { threadId } from "worker_threads"; +import { DocLike } from "../MetadataEntryMenu"; +import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionView } from "./CollectionView"; +import "./CollectionViewChromes.scss"; +import KeyRestrictionRow from "./KeyRestrictionRow"; const datepicker = require('js-datepicker'); interface CollectionViewChromeProps { @@ -29,31 +27,73 @@ interface CollectionViewChromeProps { collapse?: (value: boolean) => any; } +interface Filter { + key: string; + value: string; + contains: boolean; +} + let stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation(); @observer export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> { + //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) + @observable private _viewSpecsOpen: boolean = false; @observable private _dateWithinValue: string = ""; @observable private _dateValue: Date | string = ""; @observable private _keyRestrictions: [JSX.Element, string][] = []; - @observable private _collapsed: boolean = false; @computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); } private _picker: any; - private _datePickerElGuid = Utils.GenerateGuid(); + + getFilters = (script: string) => { + let re: any = /(!)?\(\(\(doc\.(\w+)\s+&&\s+\(doc\.\w+\s+as\s+\w+\)\.includes\(\"(\w+)\"\)/g; + let arr: any[] = re.exec(script); + let toReturn: Filter[] = []; + if (arr !== null) { + let filter: Filter = { + key: arr[2], + value: arr[3], + contains: (arr[1] === "!") ? false : true, + }; + toReturn.push(filter); + script = script.replace(arr[0], ""); + if (re.exec(script) !== null) { + toReturn.push(...this.getFilters(script)); + } + else { return toReturn; } + } + return toReturn; + } + + addKeyRestrictions = (fields: Filter[]) => { + + if (fields.length !== 0) { + for (let i = 0; i < fields.length; i++) { + this._keyRestrictions.push([<KeyRestrictionRow field={fields[i].key} value={fields[i].value} key={Utils.GenerateGuid()} contains={fields[i].contains} script={(value: string) => runInAction(() => this._keyRestrictions[i][1] = value)} />, ""]); + + } + if (this._keyRestrictions.length === 1) { + this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]); + } + } + else { + this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[0][1] = value)} />, ""]); + this._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={false} script={(value: string) => runInAction(() => this._keyRestrictions[1][1] = value)} />, ""]); + } + } componentDidMount = () => { - setTimeout(() => this._picker = datepicker("#" + this._datePickerElGuid, { - disabler: (date: Date) => date > new Date(), - onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), - dateSelected: new Date() - }), 1000); - 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)} />, ""]); + let fields: Filter[] = []; + if (this.filterValue) { + let string = this.filterValue.script.originalScript; + fields = this.getFilters(string); + } + runInAction(() => { + this.addKeyRestrictions(fields); // chrome status is one of disabled, collapsed, or visible. this determines initial state from document let chromeStatus = this.props.CollectionView.props.Document.chromeStatus; if (chromeStatus) { @@ -61,7 +101,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!"); } else if (chromeStatus === "collapsed") { - this._collapsed = true; if (this.props.collapse) { this.props.collapse(true); } @@ -113,17 +152,17 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro @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._keyRestrictions.push([<KeyRestrictionRow field="" value="" key={Utils.GenerateGuid()} contains={true} script={(value: string) => runInAction(() => this._keyRestrictions[index][1] = value)} />, ""]); this.openViewSpecs(e); } - @action + @action.bound 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 keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")"; 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; @@ -143,9 +182,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro } } let fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ? - `return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} ${keyRestrictionScript}` : - `return ${keyRestrictionScript} ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : + `return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` : + `return (${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` : "return true"; + let compiled = CompileScript(fullScript, { params: { doc: Doc.name }, typecheck: false }); if (compiled.compiled) { this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); @@ -163,9 +203,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro @action toggleCollapse = () => { - this._collapsed = !this._collapsed; + this.props.CollectionView.props.Document.chromeStatus = this.props.CollectionView.props.Document.chromeStatus === "enabled" ? "collapsed" : "enabled"; if (this.props.collapse) { - this.props.collapse(this._collapsed); + this.props.collapse(this.props.CollectionView.props.Document.chromeStatus !== "enabled"); } } @@ -182,6 +222,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Tree: return ( + <CollectionTreeViewChrome + key="collchrome" + CollectionView={this.props.CollectionView} + type={this.props.type} + />); default: return null; } @@ -217,17 +263,59 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro })} />); } + @action.bound + clearFilter = () => { + let compiled = CompileScript("return true", { params: { doc: Doc.name }, typecheck: false }); + if (compiled.compiled) { + this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled); + } + + this._keyRestrictions = []; + this.addKeyRestrictions([]); + } + + private dropDisposer?: DragManager.DragDropDisposer; + protected createDropTarget = (ele: HTMLDivElement) => { + this.dropDisposer && this.dropDisposer(); + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + + @undoBatch + @action + protected drop(e: Event, de: DragManager.DropEvent): boolean { + if (de.data instanceof DragManager.DocumentDragData) { + if (de.data.draggedDocuments.length) { + this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0]; + e.stopPropagation(); + return true; + } + } + return true; + } + + datePickerRef = (node: HTMLInputElement) => { + if (node) { + this._picker = datepicker("#" + node.id, { + disabler: (date: Date) => date > new Date(), + onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date), + dateSelected: new Date() + }); + } + } render() { + let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled"; return ( - <div className="collectionViewChrome-cont" style={{ top: this._collapsed ? -70 : 0 }}> + <div className="collectionViewChrome-cont" style={{ top: collapsed ? -70 : 0 }}> <div className="collectionViewChrome"> <div className="collectionViewBaseChrome"> <button className="collectionViewBaseChrome-collapse" style={{ - top: this._collapsed ? 70 : 10, - transform: `rotate(${this._collapsed ? 180 : 0}deg) scale(${this._collapsed ? 0.5 : 1}) translate(${this._collapsed ? "-100%, -100%" : "0, 0"})`, - opacity: (this._collapsed && !this.props.CollectionView.props.isSelected()) ? 0 : 0.9, - left: (this._collapsed ? 0 : "unset"), + top: collapsed ? 70 : 10, + transform: `rotate(${collapsed ? 180 : 0}deg) scale(${collapsed ? 0.5 : 1}) translate(${collapsed ? "-100%, -100%" : "0, 0"})`, + opacity: (collapsed && !this.props.CollectionView.props.isSelected()) ? 0 : 0.9, + left: (collapsed ? 0 : "unset"), }} title="Collapse collection chrome" onClick={this.toggleCollapse}> <FontAwesomeIcon icon="caret-up" size="2x" /> @@ -243,12 +331,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro <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" style={{ display: this._collapsed ? "none" : "grid" }}> + <div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}> <input className="collectionViewBaseChrome-viewSpecsInput" placeholder="FILTER DOCUMENTS" - value={this.filterValue ? this.filterValue.script.originalScript : ""} + value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""} onChange={(e) => { }} - onPointerDown={this.openViewSpecs} /> + onPointerDown={this.openViewSpecs} + id="viewSpecsInput" /> {this.getPivotInput()} <div className="collectionViewBaseChrome-viewSpecsMenu" onPointerDown={this.openViewSpecs} @@ -275,7 +364,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro <option value="1y">1 year of</option> </select> <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight" - id={this._datePickerElGuid} + id={Utils.GenerateGuid()} + ref={this.datePickerRef} value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue} onChange={(e) => runInAction(() => this._dateValue = e.target.value)} onPointerDown={this.openDatePicker} @@ -288,9 +378,15 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.applyFilter}> APPLY FILTER </button> + <button className="collectonViewBaseChrome-viewSpecsMenu-lastRowButton" onClick={this.clearFilter}> + CLEAR + </button> </div> </div> </div> + <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{}}> + TEMPLATE + </div> </div> {this.subChrome()} </div> @@ -468,4 +564,109 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh </div > ); } -}
\ No newline at end of file +} + +@observer +export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> { + @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[]> => { + value = value.toLowerCase(); + let docs: Doc | Doc[] | Promise<Doc> | Promise<Doc[]> | (() => DocLike) + = () => DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldExt ? this.props.CollectionView.props.fieldExt : this.props.CollectionView.props.fieldKey]); + if (typeof docs === "function") { + docs = docs(); + } + docs = await docs; + if (docs instanceof Doc) { + return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value)); + } else { + const keys = new Set<string>(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + return Array.from(keys).filter(key => key.toLowerCase().startsWith(value)); + } + } + + @action + onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { + this._currentKey = newValue; + } + + getSuggestionValue = (suggestion: string) => suggestion; + + renderSuggestion = (suggestion: string) => { + return <p>{suggestion}</p>; + } + + onSuggestionFetch = async ({ value }: { value: string }) => { + const sugg = await this.getKeySuggestions(value); + runInAction(() => { + this.suggestions = sugg; + }); + } + + @action + onSuggestionClear = () => { + this.suggestions = []; + } + + setValue = (value: string) => { + this.props.CollectionView.props.Document.sectionFilter = value; + 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="collectionTreeViewChrome-cont"> + <button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}> + <div className="collectionTreeViewChrome-sortLabel"> + Sort + </div> + <div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}> + <FontAwesomeIcon icon="caret-up" size="2x" color="white" /> + </div> + </button> + <div className="collectionTreeViewChrome-sectionFilter-cont"> + <div className="collectionTreeViewChrome-sectionFilter-label"> + GROUP ITEMS BY: + </div> + <div className="collectionTreeViewChrome-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> + ); + } +} + |
