diff options
Diffstat (limited to 'src/client/views/FilterPanel.tsx')
-rw-r--r-- | src/client/views/FilterPanel.tsx | 243 |
1 files changed, 72 insertions, 171 deletions
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 818c81c9a..0521c4a4b 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -4,19 +4,16 @@ import * as React from 'react'; import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider'; import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai'; import { CiCircleRemove } from 'react-icons/ci'; -import Select from 'react-select'; import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; -import { DocOptions, FInfo } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; -import { UserOptions } from '../util/GroupManager'; import { SearchUtil } from '../util/SearchUtil'; import { SettingsManager } from '../util/SettingsManager'; import { undoable } from '../util/UndoManager'; +import { FieldsDropdown } from './FieldsDropdown'; import './FilterPanel.scss'; -import { FieldView } from './nodes/FieldView'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -26,9 +23,7 @@ interface filterProps { @observer export class FilterPanel extends ObservableReactComponent<filterProps> { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(FilterPanel, fieldKey); - } + @observable _selectedFacetHeaders = new Set<string>(); constructor(props: any) { super(props); @@ -38,45 +33,19 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { /** * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection */ - get targetDoc() { + get Document() { return this._props.Document; } @computed get targetDocChildKey() { - const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc); + const targetView = DocumentManager.Instance.getFirstDocumentView(this.Document); 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<Doc>(); - const targetDoc = this.targetDoc; - if (targetDoc) { - SearchUtil.foreachRecursiveDoc([this.targetDoc], (depth, doc) => allDocs.add(doc)); - } - return Array.from(allDocs); - } - - @computed get _allFacets() { - const noviceReqFields = ['author', 'tags', 'text', 'type', LinkedTo]; - const noviceLayoutFields: string[] = []; //["_layout_curPage"]; - const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; - - const keys = new Set<string>(noviceFields); - this.allDocs.forEach(doc => SearchUtil.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]; + return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_sidebar'])]; } @computed get rangeFilters() { - return StrListCast(this.targetDoc?._childFiltersByRanges).filter((filter, i) => !(i % 3)); + return StrListCast(this.Document?._childFiltersByRanges).filter((filter, i) => !(i % 3)); } /** @@ -84,7 +53,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { * ["#tags::bob::check", "tags::joe::check", "width", "height"] */ @computed get activeFilters() { - return StrListCast(this.targetDoc?._childFilters).concat(this.rangeFilters); + return StrListCast(this.Document?._childFilters).concat(this.rangeFilters); } @computed get mapActiveFiltersToFacets() { @@ -100,32 +69,27 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { // 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]); - // } static gatherFieldValues(childDocs: Doc[], facetKey: string, childFilters: string[]) { const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1])); let rtFields = 0; let subDocs = childDocs; + let gatheredDocs = [] as Doc[]; if (subDocs.length > 0) { let newarray: Doc[] = []; while (subDocs.length > 0) { newarray = []; subDocs.forEach(t => { + gatheredDocs.push(t); const facetVal = t[facetKey]; if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++; facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field)); @@ -135,7 +99,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); }); - subDocs = newarray; + subDocs = newarray.filter(d => !gatheredDocs.includes(d)); } } // } @@ -145,8 +109,8 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { } public removeFilter = (filterName: string) => { - Doc.setDocFilter(this.targetDoc, filterName, undefined, 'remove'); - Doc.setDocRangeFilter(this.targetDoc, filterName, undefined); + Doc.setDocFilter(this.Document, filterName, undefined, 'remove'); + Doc.setDocRangeFilter(this.Document, filterName, undefined); }; // @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>(); @@ -154,18 +118,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { @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]}) + // "width" => {"range", domain:[1978,1992]}) // - @computed get activeRenderedFacetInfos() { return new Set( Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => { - const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.targetDoc.childFilters)); + const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters)); let nonNumbers = 0; let minVal = Number.MAX_VALUE, @@ -185,7 +147,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { } 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 + const ranged = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; } else { return { facetHeader, renderType: 'checkbox' }; @@ -194,8 +156,6 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { ); } - @observable _selectedFacetHeaders = new Set<string>(); - /** * 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 @@ -229,7 +189,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { facetValues = (facetHeader: string) => { const allCollectionDocs = new Set<Doc>(); SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); - const set = new Set<string>([...StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]); + const set = new Set<string>([...StrListCast(this.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]); if (facetHeader === 'tags') allCollectionDocs.forEach(child => StrListCast(child[facetHeader]) @@ -253,59 +213,13 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; - @computed get fieldsDropdown() { - const filteredOptions = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.filter(facet => facet[0] === facet.charAt(0).toUpperCase())]; - - Object.entries(DocOptions) - .filter(opts => opts[1].filterable) - .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0])); - const options = filteredOptions.map(facet => ({ value: facet, label: facet })); - - return ( - <Select - styles={{ - control: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - placeholder: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - input: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - option: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor, - }), - menuList: (baseStyles, state) => ({ - ...baseStyles, - backgroundColor: SettingsManager.userBackgroundColor, - }), - }} - placeholder={'add a filter'} - options={options} - isMulti={false} - onChange={val => this.facetClick((val as UserOptions).value)} - onKeyDown={e => e.stopPropagation()} - //onMenuClose={onClose} - value={null} - closeMenuOnSelect={true} - /> - ); - } - render() { return ( <div className="filterBox-treeView"> <div className="filterBox-select"> - <div style={{ width: '100%' }}>{this.fieldsDropdown}</div> + <div style={{ width: '100%' }}> + <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder={true} placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} /> + </div> {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */} {/* <div className="filterBox-select-bool"> <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}> @@ -341,11 +255,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { className="filterBox-facetHeader-remove" onClick={action(e => { if (renderInfo.facetHeader === 'text') { - Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, 'match', 'remove'); + Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove'); } else { for (var key of this.facetValues(renderInfo.facetHeader)) { if (this.mapActiveFiltersToFacets.get(key)) { - Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove'); + Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove'); } } } @@ -353,7 +267,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { this._chosenFacetsCollapse.delete(renderInfo.facetHeader); if (renderInfo.domain) { - Doc.setDocRangeFilter(this.targetDoc, renderInfo.facetHeader, renderInfo.domain, 'remove'); + Doc.setDocRangeFilter(this.Document, renderInfo.facetHeader, renderInfo.domain, 'remove'); } })}> <CiCircleRemove />{' '} @@ -377,16 +291,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { case 'text': return ( <input - key={this.targetDoc[Id]} + key={this.Document[Id]} placeholder={'enter text to match'} defaultValue={ - StrListCast(this.targetDoc._childFilters) + StrListCast(this.Document._childFilters) .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader) ?.split(Doc.FilterSep)[1] } style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} - 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)} + onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} + onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)} /> ); case 'checkbox': @@ -397,12 +311,12 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { <input style={{ width: 20, marginLeft: 20 }} checked={['check', 'exists'].includes( - StrListCast(this.targetDoc._childFilters) + StrListCast(this.Document._childFilters) .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) ?.split(Doc.FilterSep)[2] ?? '' )} type={type} - onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} + onChange={undoable(e => Doc.setDocFilter(this.Document, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} /> {facetValue} </div> @@ -412,64 +326,51 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { 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> - </> + <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.Document, 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> ); } |