diff options
author | bobzel <zzzman@gmail.com> | 2023-08-27 13:37:45 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2023-08-27 13:37:45 -0400 |
commit | e566cdb001da320b5ee3f7fa60d96b7a09a195c3 (patch) | |
tree | 3d4373ede28da858ae964edffa8539961f148471 /src/client/views/FilterPanel.tsx | |
parent | 0332a80329328e39c244623d5090331b83df339c (diff) | |
parent | 6d60b14e109e527ddc7a096af858cbc05dab3480 (diff) |
Merge branch 'master' into data-visualization-sarah
Diffstat (limited to 'src/client/views/FilterPanel.tsx')
-rw-r--r-- | src/client/views/FilterPanel.tsx | 307 |
1 files changed, 260 insertions, 47 deletions
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 63bd01b19..11c2fc86c 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -2,6 +2,7 @@ import React = require('react'); import { action, computed, observable, ObservableMap } from 'mobx'; import { observer } from 'mobx-react'; import Select from 'react-select'; +import { Checkbox, Tooltip } from '@material-ui/core'; import { Doc, DocListCast, Field, StrListCast } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; import { StrCast } from '../../fields/Types'; @@ -11,14 +12,21 @@ 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 { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai'; import { CiCircleRemove } from 'react-icons/ci'; +import { Slider, Rail, Handles, Tracks, Ticks } from 'react-compound-slider'; +import { TooltipRail, Handle, Tick, Track } from './nodes/SliderBox-components'; +import { DocumentOptions, FInfo } from '../documents/Documents'; +import { string32 } from 'pdfjs-dist/types/src/shared/util'; interface filterProps { rootDoc: Doc; } + @observer export class FilterPanel extends React.Component<filterProps> { + private _documentOptions: DocumentOptions = new DocumentOptions(); + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterPanel, fieldKey); } @@ -43,6 +51,7 @@ export class FilterPanel extends React.Component<filterProps> { if (targetDoc) { SearchBox.foreachRecursiveDoc([this.targetDoc], (depth, doc) => allDocs.add(doc)); } + console.log('this is all Docs' + Array.from(allDocs)); return Array.from(allDocs); } @@ -58,23 +67,56 @@ export class FilterPanel extends React.Component<filterProps> { .filter(key => key[0]) .filter(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode) .sort(); + + // console.log('THIS IS HERE ' + Doc.UserDoc().color + 'space ' + Doc.UserDoc().color); noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1)); + + console.log('this is novice fields ' + noviceFields + 'and this is sorted Keys ' + sortedKeys); + return [...noviceFields, ...sortedKeys]; } + @computed get rangeFilters() { + return StrListCast(this.targetDoc?._childFiltersByRanges).filter((filter, i) => !(i % 3)); + } + /** - * The current attributes selected to filter based on + * activeFilters( ) -- all filters that currently have a filter set on them in this document (ranges, and others) + * ["#tags::bob::check", "tags::joe::check", "width", "height"] */ @computed get activeFilters() { - return StrListCast(this.targetDoc?._childFilters); + return StrListCast(this.targetDoc?._childFilters).concat(this.rangeFilters); } + @computed get mapActiveFiltersToFacets() { + const filters = new Map<string, string>(); + //this.targetDoc.docFilters + this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1], filter.split(Doc.FilterSep)[0])); + return filters; + } + + // + // activeFacetHeaders() - just the facet names, not the rest of the filter + // + // this wants to return all the filter facets that have an existing filter set on them in order to show them in the rendered panel + // this set may overlap the selectedFilters + // if the components reloads, these will still exist and be shown + + // ["#tags", "width", "height"] + // + + @computed get activeFacetHeaders() { + const activeHeaders = new Array(); + this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0])); + + return activeHeaders; + } /** * @returns a string array of the current attributes */ - @computed get currentFacets() { - return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); - } + // @computed get currentFacets() { + // return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); + // } gatherFieldValues(childDocs: Doc[], facetKey: string) { const valueSet = new Set<string>(); @@ -86,6 +128,7 @@ export class FilterPanel extends React.Component<filterProps> { newarray = []; subDocs.forEach(t => { const facetVal = t[facetKey]; + // console.log("facetVal " + facetVal) 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)); @@ -99,6 +142,7 @@ export class FilterPanel extends React.Component<filterProps> { } // } // }); + return { strings: Array.from(valueSet.keys()), rtFields }; } @@ -107,40 +151,87 @@ export class FilterPanel extends React.Component<filterProps> { Doc.setDocRangeFilter(this.targetDoc, filterName, undefined); }; - @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>(); - @computed get activeFacets() { - const facets = new Map<string, 'text' | 'checkbox' | 'slider' | 'range'>(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; + // @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>(); + @observable _chosenFacetsCollapse = new ObservableMap<string, boolean>(); + @observable _collapseReturnKeys = new Array(); + + // this computed function gets the active filters and maps them to their headers + + // + // activeRenderedFacetInfos() + // returns renderInfo for all user selected filters and for all existing filters set on the document + // Map("tags" => {"checkbox"}, + // "width" => {"rangs", domain:[1978,1992]}) + // + + @computed get activeRenderedFacetInfos() { + return new Set( + Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => { + 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') { + return { facetHeader: facetHeader, renderType: 'text' }; + } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { + const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); + const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); + const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter + return { facetHeader: facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; + } else { + return { facetHeader: facetHeader, renderType: 'checkbox' }; + } + }) + ); } + + @observable _selectedFacetHeaders = new Set<string>(); + /** - * Responds to clicking the check box in the flyout menu + * user clicks on a filter facet because they want to see it. + * this adds this chosen filter to a set of user selected filters called: selectedFilters + * if this component reloads, then these filters will go away since they haven't been written to any Doc anywhere + * + * // this._selectedFacets.add(facetHeader); .. add to Set() not array */ + @action facetClick = (facetHeader: string) => { - if (!this.targetDoc) return; - const allCollectionDocs = this.targetDocChildren; - const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); + // just when someone chooses a facet - 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); + this._selectedFacetHeaders.add(facetHeader); + + return; + }; + + @action + sortingCurrentFacetValues = (facetHeader: string) => { + this._collapseReturnKeys.splice(0); + + Array.from(this.activeRenderedFacetInfos.keys()).map(renderInfo => { + if (renderInfo.renderType === 'range' && renderInfo.facetHeader === facetHeader && renderInfo.range) { + this._collapseReturnKeys.push(renderInfo.range.map(number => number.toFixed(2))); } }); - 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'); + + for (var key of this.facetValues(facetHeader)) { + if (this.mapActiveFiltersToFacets.get(key)) { + this._collapseReturnKeys.push(key); + } } + + return <div className=" filterbox-collpasedAndActive">{this._collapseReturnKeys.join(', ')}</div>; }; facetValues = (facetHeader: string) => { @@ -168,7 +259,24 @@ export class FilterPanel extends React.Component<filterProps> { }; render() { - const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); + // console.log('this is frist one today ' + this._allFacets); + this._allFacets.forEach(element => console.log(element)); + // const options = Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => pair[1].filterable ).map(facet => value: facet, label: facet) //this._allFacets.filter(facet => this.activeFacetHeaders.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); + // console.log('HEELLLLLL ' + DocumentOptions); + + let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest']; + + Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => { + if (pair[1].filterable) { + filteredOptions.push(pair[0]); + console.log('THIS IS FILTERABLE ALKDJFIIEII' + filteredOptions); + } + }); + + let options = filteredOptions.map(facet => ({ value: facet, label: facet })); + + // Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => console.log('this is first piar ' + pair[0] + ' this is second piar ' + pair[1].filterable)); + return ( <div className="filterBox-treeView"> <div className="filterBox-select"> @@ -188,29 +296,58 @@ export class FilterPanel extends React.Component<filterProps> { </div> <div className="filterBox-tree" key="tree"> - {Array.from(this.activeFacets.keys()).map(facetHeader => ( - <div> - <div className="filterBox-facetHeader"> - <div className="filterBox-facetHeader-Header"> </div> - {facetHeader.charAt(0).toUpperCase() + facetHeader.slice(1)} + {Array.from(this.activeRenderedFacetInfos.keys()).map( + ( + renderInfo // iterato over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader + ) => ( + <div> + <div className="filterBox-facetHeader"> + <div className="filterBox-facetHeader-Header"> </div> + {renderInfo.facetHeader.charAt(0).toUpperCase() + renderInfo.facetHeader.slice(1)} + + <div + className="filterBox-facetHeader-collapse" + onClick={action(e => { + const collapseBoolValue = this._chosenFacetsCollapse.get(renderInfo.facetHeader); + this._chosenFacetsCollapse.set(renderInfo.facetHeader, !collapseBoolValue); + })}> + {this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? <AiOutlinePlusSquare /> : <AiOutlineMinusSquare />} + </div> - <div className="filterBox-facetHeader-collapse"> - <AiOutlineMinusSquare /> - {/* <CiCircleRemove/> */} + <div + className="filterBox-facetHeader-remove" + onClick={action(e => { + for (var key of this.facetValues(renderInfo.facetHeader)) { + if (this.mapActiveFiltersToFacets.get(key)) { + Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove'); + } + } + this._selectedFacetHeaders.delete(renderInfo.facetHeader); + this._chosenFacetsCollapse.delete(renderInfo.facetHeader); + + if (renderInfo.domain) { + Doc.setDocRangeFilter(this.targetDoc, renderInfo.facetHeader, renderInfo.domain, 'remove'); + } + })}> + <CiCircleRemove />{' '} + </div> </div> - </div> - {this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader)} - </div> - ))} + {this._chosenFacetsCollapse.get(renderInfo.facetHeader) + ? this.sortingCurrentFacetValues(renderInfo.facetHeader) + : this.displayFacetValueFilterUIs(renderInfo.renderType, renderInfo.facetHeader, renderInfo.domain, renderInfo.range)} + {/* */} + </div> + ) + )} </div> </div> ); } - private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string): React.ReactNode { - switch (type) { - case 'text': + private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string, renderInfoDomain?: number[] | undefined, renderInfoRange?: number[]): React.ReactNode { + switch (type /* renderInfo.type */) { + case 'text': // if (this.chosenFacets.get(facetHeader) === 'text') return ( <input placeholder={ @@ -241,6 +378,82 @@ export class FilterPanel extends React.Component<filterProps> { </div> ); }); + + case 'range': + const domain = renderInfoDomain; + const range = renderInfoRange; + + if (range) { + console.log('this is info range ' + range[0] + ' , ' + range[1]); + } + + if (domain) { + console.log('this is info domain ' + domain[0] + ', ' + domain[1]); + + return ( + <> + {/* <div className="sliderBox-outerDiv-checkBox" style={{ float: 'left' }}> + <Checkbox color="primary" onChange={action(() => console.log('on change'))} /> + </div> */} + + <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}> + <Slider + mode={2} + step={Math.min(1, 0.1 * (domain[1] - domain[0]))} + domain={[domain[0], domain[1]]} // -1000, 1000 + rootStyle={{ position: 'relative', width: '100%' }} + onChange={values => Doc.setDocRangeFilter(this.targetDoc, facetHeader, values)} + values={renderInfoRange!}> + <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> + <Handles> + {({ handles, activeHandleID, getHandleProps }) => ( + <div className="slider-handles"> + {handles.map((handle, i) => { + // const value = i === 0 ? defaultValues[0] : defaultValues[1]; + return ( + <div> + <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} /> + </div> + ); + })} + </div> + )} + </Handles> + <Tracks left={false} right={false}> + {({ tracks, getTrackProps }) => ( + <div className="slider-tracks"> + {tracks.map(({ id, source, target }) => ( + <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} /> + ))} + </div> + )} + </Tracks> + <Ticks count={5}> + {({ ticks }) => ( + <div className="slider-ticks"> + {ticks.map(tick => ( + <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} /> + ))} + </div> + )} + </Ticks> + </Slider> + </div> + </> + ); + } + + // case 'range' + // return <Slider ... + // return <slider domain={renderInfo.domain}> domain is number[] for min and max + // onChange = { ... Doc.setDocRangeFilter(this.targetDoc, facetHeader, [extendedMinVal, extendedMaxVal] ) } + // + // OR + + // return <div> + // <slider domain={renderInfo.domain}> // domain is number[] for min and max + // <dimain changing handles > + // <?div } } } |