import React = require('react'); import { action, computed, observable, ObservableMap } from 'mobx'; import { observer } from 'mobx-react'; import Select from 'react-select'; import { Doc, DocListCast, Field, StrListCast } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; import { StrCast } from '../../fields/Types'; import { DocumentManager } from '../util/DocumentManager'; import { UserOptions } from '../util/GroupManager'; import './FilterPanel.scss'; import { FieldView } from './nodes/FieldView'; import { SearchBox } from './search/SearchBox'; import { undoable } from '../util/UndoManager'; import { AiOutlineMinusSquare } from 'react-icons/ai'; import { CiCircleRemove } from 'react-icons/ci'; interface filterProps { rootDoc: Doc; } @observer export class FilterPanel extends React.Component { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterPanel, fieldKey); } /** * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection */ @computed get targetDoc() { return this.props.rootDoc; } @computed get targetDocChildKey() { const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc); return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data'; } @computed get targetDocChildren() { return [...DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.targetDoc[Doc.LayoutFieldKey(this.targetDoc) + '_sidebar'])]; } @computed get allDocs() { const allDocs = new Set(); const targetDoc = this.targetDoc; if (targetDoc) { SearchBox.foreachRecursiveDoc([this.targetDoc], (depth, doc) => allDocs.add(doc)); } return Array.from(allDocs); } @computed get _allFacets() { // trace(); const noviceReqFields = ['author', 'tags', 'text', 'type']; const noviceLayoutFields: string[] = []; //["_layout_curPage"]; const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; const keys = new Set(noviceFields); this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); const sortedKeys = Array.from(keys.keys()) .filter(key => key[0]) .filter(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode) .sort(); noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1)); return [...noviceFields, ...sortedKeys]; } /** * The current attributes selected to filter based on */ @computed get activeFilters() { return StrListCast(this.targetDoc?._childFilters); } /** * @returns a string array of the current attributes */ @computed get currentFacets() { return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); } gatherFieldValues(childDocs: Doc[], facetKey: string) { const valueSet = new Set(); let rtFields = 0; let subDocs = childDocs; if (subDocs.length > 0) { let newarray: Doc[] = []; while (subDocs.length > 0) { newarray = []; subDocs.forEach(t => { const facetVal = t[facetKey]; if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++; facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field)); (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal)); const fieldKey = Doc.LayoutFieldKey(t); const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView'); DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); }); subDocs = newarray; } } // } // }); return { strings: Array.from(valueSet.keys()), rtFields }; } public removeFilter = (filterName: string) => { Doc.setDocFilter(this.targetDoc, filterName, undefined, 'remove'); Doc.setDocRangeFilter(this.targetDoc, filterName, undefined); }; @observable _chosenFacets = new ObservableMap(); @computed get activeFacets() { const facets = new Map(this._chosenFacets); StrListCast(this.targetDoc?._childFilters).map(filter => facets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')); setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')))); return facets; } /** * Responds to clicking the check box in the flyout menu */ @action facetClick = (facetHeader: string) => { if (!this.targetDoc) return; const allCollectionDocs = this.targetDocChildren; const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); let nonNumbers = 0; let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; facetValues.strings.map(val => { const num = val ? Number(val) : Number.NaN; if (Number.isNaN(num)) { val && nonNumbers++; } else { minVal = Math.min(num, minVal); maxVal = Math.max(num, maxVal); } }); if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) { this._chosenFacets.set(facetHeader, 'text'); } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { } else { this._chosenFacets.set(facetHeader, 'checkbox'); } }; facetValues = (facetHeader: string) => { const allCollectionDocs = new Set(); SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); const set = new Set([String.fromCharCode(127) + '--undefined--']); if (facetHeader === 'tags') allCollectionDocs.forEach(child => StrListCast(child[facetHeader]) .filter(h => h) .forEach(key => set.add(key)) ); else allCollectionDocs.forEach(child => { const fieldVal = child[facetHeader] as Field; set.add(Field.toString(fieldVal)); (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString()); }); const facetValues = Array.from(set).filter(v => v); let nonNumbers = 0; facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++); return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; render() { const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); console.log("this is option " + options) console.log("this is alll facets " + this._allFacets) return (
this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}> {['AND', 'OR'].map(bool => ( ))}
{' '} */}
{Array.from(this.activeFacets.keys()).map(facetHeader => (
{facetHeader.charAt(0).toUpperCase() + facetHeader.slice(1)}
{this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader)}
))}
); } private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string): React.ReactNode { switch (type) { case 'text': return ( filter.split(Doc.FilterSep)[0] === facetHeader) ?.split(Doc.FilterSep)[1] ?? '-empty-' } onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)} /> ); case 'checkbox': return this.facetValues(facetHeader).map(fval => { const facetValue = fval; return (
filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) ?.split(Doc.FilterSep)[2] === 'check' } type={type} onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} /> {facetValue}
); }); } } }