aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts10
-rw-r--r--src/client/views/FilterPanel.scss189
-rw-r--r--src/client/views/FilterPanel.tsx232
-rw-r--r--src/client/views/PropertiesView.tsx72
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/FilterBox.scss12
-rw-r--r--src/client/views/nodes/FilterBox.tsx588
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx21
-rw-r--r--src/fields/Doc.ts6
9 files changed, 448 insertions, 684 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 98469a2f9..a7378c431 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -39,7 +39,6 @@ import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
import { DocFocusOptions, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
import { EquationBox } from '../views/nodes/EquationBox';
import { FieldViewProps } from '../views/nodes/FieldView';
-import { FilterBox } from '../views/nodes/FilterBox';
import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox';
import { ImageBox } from '../views/nodes/ImageBox';
@@ -412,13 +411,6 @@ export namespace Docs {
},
],
[
- DocumentType.FILTER,
- {
- layout: { view: FilterBox, dataField: defaultDataKey },
- options: { _width: 400, links: '@links(self)' },
- },
- ],
- [
DocumentType.COLOR,
{
layout: { view: ColorBox, dataField: defaultDataKey },
@@ -1260,7 +1252,7 @@ export namespace DocUtils {
return Field.toString(d[facetKey] as Field).includes(value);
});
// if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria
- if ((parentCollection?.currentFilter as Doc)?.filterBoolean === 'OR') {
+ if (parentCollection?.filterBoolean === 'OR') {
if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true;
}
// if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
new file mode 100644
index 000000000..7f907c8d4
--- /dev/null
+++ b/src/client/views/FilterPanel.scss
@@ -0,0 +1,189 @@
+.filterBox-flyout {
+ display: block;
+ text-align: left;
+ font-weight: 100;
+
+ .filterBox-flyout-facet {
+ background-color: white;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+
+ .filterBox-flyout-facet-check {
+ margin-right: 6px;
+ }
+ }
+}
+
+.filter-bookmark {
+ //display: flex;
+
+ .filter-bookmark-icon {
+ float: right;
+ margin-right: 10px;
+ margin-top: 7px;
+ }
+}
+
+// .filterBox-bottom {
+// // position: fixed;
+// // bottom: 0;
+// // width: 100%;
+// }
+
+.filterBox-select {
+ // width: 90%;
+ margin-top: 5px;
+ // margin-bottom: 15px;
+}
+
+.filterBox-saveBookmark {
+ background-color: #e9e9e9;
+ border-radius: 11px;
+ padding-left: 8px;
+ padding-right: 8px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin: 8px;
+ display: flex;
+ font-size: 11px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: white;
+ }
+
+ .filterBox-saveBookmark-icon {
+ margin-right: 6px;
+ margin-top: 4px;
+ margin-left: 2px;
+ }
+}
+
+.filterBox-select-scope,
+.filterBox-select-bool,
+.filterBox-addWrapper,
+.filterBox-select-matched,
+.filterBox-saveWrapper {
+ font-size: 10px;
+ justify-content: center;
+ justify-items: center;
+ padding-bottom: 10px;
+ display: flex;
+}
+
+.filterBox-addWrapper {
+ font-size: 11px;
+ width: 100%;
+}
+
+.filterBox-saveWrapper {
+ width: 100%;
+}
+
+// .filterBox-top {
+// padding-bottom: 20px;
+// border-bottom: 2px solid black;
+// position: fixed;
+// top: 0;
+// width: 100%;
+// }
+
+.filterBox-select-scope {
+ padding-bottom: 20px;
+ border-bottom: 2px solid black;
+}
+
+.filterBox-title {
+ font-size: 15;
+ // border: 2px solid black;
+ width: 100%;
+ align-self: center;
+ text-align: center;
+ background-color: #d3d3d3;
+}
+
+.filterBox-select-bool {
+ margin-top: 6px;
+}
+
+.filterBox-select-text {
+ margin-right: 8px;
+ margin-left: 8px;
+ margin-top: 3px;
+}
+
+.filterBox-select-box {
+ margin-right: 2px;
+ font-size: 30px;
+ border: 0;
+ background: transparent;
+}
+
+.filterBox-selection {
+ border-radius: 6px;
+ border: none;
+ background-color: #e9e9e9;
+ padding: 2px;
+
+ &:hover {
+ background-color: white;
+ }
+}
+
+.filterBox-addFilter {
+ width: 120px;
+ background-color: #e9e9e9;
+ border-radius: 12px;
+ padding: 5px;
+ margin: 5px;
+ display: flex;
+ text-align: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: white;
+ }
+}
+
+.filterBox-treeView {
+ display: flex;
+ flex-direction: column;
+ width: 200px;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 1;
+ background-color: #9f9f9f;
+
+ .filterBox-tree {
+ z-index: 0;
+ }
+
+ .filterBox-addfacet {
+ display: inline-block;
+ width: 200px;
+ height: 30px;
+ text-align: left;
+
+ .filterBox-addFacetButton {
+ display: flex;
+ margin: auto;
+ cursor: pointer;
+ }
+
+ > div,
+ > div > div {
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .filterBox-tree {
+ display: inline-block;
+ width: 100%;
+ margin-bottom: 10px;
+ //height: calc(100% - 30px);
+ }
+}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
new file mode 100644
index 000000000..d35494f26
--- /dev/null
+++ b/src/client/views/FilterPanel.tsx
@@ -0,0 +1,232 @@
+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';
+
+interface filterProps {
+ rootDoc: Doc;
+}
+@observer
+export class FilterPanel extends React.Component<filterProps> {
+ 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);
+ }
+
+ @computed get allDocs() {
+ const allDocs = new Set<Doc>();
+ 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[] = []; //["_curPage"];
+ const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
+
+ const keys = new Set<string>(noviceFields);
+ this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
+ return Array.from(keys.keys())
+ .filter(key => key[0])
+ .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
+ .sort();
+ }
+
+ /**
+ * The current attributes selected to filter based on
+ */
+ @computed get activeFilters() {
+ return StrListCast(this.targetDoc?._docFilters);
+ }
+
+ /**
+ * @returns a string array of the current attributes
+ */
+ @computed get currentFacets() {
+ return this.activeFilters.map(filter => filter.split(':')[0]);
+ }
+
+ gatherFieldValues(childDocs: Doc[], facetKey: string) {
+ const valueSet = new Set<string>();
+ 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));
+ });
+ 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<string, 'text' | 'checkbox' | 'slider' | 'range'>();
+ @computed get activeFacets() {
+ const facets = new Map<string, 'text' | 'checkbox' | 'slider' | 'range'>(this._chosenFacets);
+ StrListCast(this.targetDoc?._docFilters).map(filter => facets.set(filter.split(':')[0], filter.split(':')[2] === 'match' ? 'text' : 'checkbox'));
+ setTimeout(() => StrListCast(this.targetDoc?._docFilters).map(action(filter => this._chosenFacets.set(filter.split(':')[0], filter.split(':')[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.rtFields > 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<Doc>();
+ SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
+ const set = new Set<string>();
+ if (facetHeader === 'tags')
+ allCollectionDocs.forEach(child =>
+ Field.toString(child[facetHeader] as Field)
+ .split(':')
+ .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++);
+ const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
+ return facetValue;
+ });
+ return facetValueDocSet;
+ };
+
+ render() {
+ const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
+ return (
+ <div className="filterBox-treeView" style={{ position: 'relative', width: '100%' }}>
+ <div className="filterBox-select-bool">
+ <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._filterBoolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.filterBoolean)}>
+ {['AND', 'OR'].map(bool => (
+ <option value={bool} key={bool}>
+ {bool}
+ </option>
+ ))}
+ </select>
+ <div className="filterBox-select-text">filters together</div>
+ </div>
+
+ <div className="filterBox-select">
+ <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
+ </div>
+
+ <div className="filterBox-tree" key="tree" style={{ overflow: 'auto' }}>
+ {Array.from(this.activeFacets.keys()).map(facetHeader => (
+ <div>
+ {facetHeader}
+ {this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader)}
+ </div>
+ ))}
+ </div>
+ </div>
+ );
+ }
+
+ private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string): React.ReactNode {
+ switch (type) {
+ case 'text':
+ return (
+ <input
+ placeholder={
+ StrListCast(this.targetDoc._docFilters)
+ .find(filter => filter.split(':')[0] === facetHeader)
+ ?.split(':')[1] ?? '-empty-'
+ }
+ onKeyDown={e => e.key === 'Enter' && Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match')}
+ />
+ );
+ case 'checkbox':
+ return this.facetValues(facetHeader).map(fval => {
+ const facetValue = fval;
+ return (
+ <div>
+ <input
+ style={{ width: 20, marginLeft: 20 }}
+ checked={
+ StrListCast(this.targetDoc._docFilters)
+ .find(filter => filter.split(':')[0] === facetHeader && filter.split(':')[1] == facetValue)
+ ?.split(':')[2] === 'check'
+ }
+ type={type}
+ onChange={e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove')}
+ />
+ {facetValue}
+ </div>
+ );
+ });
+ }
+ }
+}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 1ec2b76d6..4a63930bd 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -26,7 +26,7 @@ import { EditableView } from './EditableView';
import { Colors } from './global/globalEnums';
import { InkStrokeProperties } from './InkStrokeProperties';
import { DocumentView, OpenWhere, StyleProviderFunc } from './nodes/DocumentView';
-import { FilterBox } from './nodes/FilterBox';
+import { FilterPanel } from './FilterPanel';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
import { PropertiesButtons } from './PropertiesButtons';
@@ -76,13 +76,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openOptions: boolean = true;
@observable openSharing: boolean = true;
- @observable openFields: boolean = true;
+ @observable openFields: boolean = false;
@observable openLayout: boolean = false;
@observable openContexts: boolean = true;
@observable openLinks: boolean = true;
@observable openAppearance: boolean = true;
@observable openTransform: boolean = true;
- @observable openFilters: boolean = true; // should be false
+ @observable openFilters: boolean = false;
/**
* autorun to set up the filter doc of a collection if that collection has been selected and the filters panel is open
@@ -104,7 +104,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
componentDidMount() {
this.selectedDocListenerDisposer?.();
- this.selectedDocListenerDisposer = autorun(() => this.openFilters && this.selectedDoc && this.checkFilterDoc());
+ // this.selectedDocListenerDisposer = autorun(() => this.openFilters && this.selectedDoc && this.checkFilterDoc());
}
componentWillUnmount() {
@@ -1136,37 +1136,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
/**
- * Checks if a currentFilter (FilterDoc) exists on the current collection (if the Properties Panel + Filters submenu are open).
- * If it doesn't exist, it creates it.
- */
- checkFilterDoc() {
- if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = FilterBox.createFilterDoc();
- }
-
- /**
- * Creates a new currentFilter for this.filterDoc,
- */
- createNewFilterDoc = () => {
- if (this.selectedDoc) {
- const curFilterDoc = DocCast(this.selectedDoc.currentFilter);
- const currentDocFilters = this.selectedDoc._docFilters;
- const currentDocRangeFilters = this.selectedDoc._docRangeFilters;
- this.selectedDoc._docFilters = new List<string>();
- this.selectedDoc._docRangeFilters = new List<string>();
- if (DocListCast(Doc.UserDoc().savedFilters).includes(curFilterDoc)) {
- curFilterDoc._docFiltersList = currentDocFilters;
- curFilterDoc._docRangeFiltersList = currentDocRangeFilters;
- this.selectedDoc.currentFilter = FilterBox.createFilterDoc();
- } else {
- Doc.GetProto(curFilterDoc).data = undefined;
- Doc.GetProto(curFilterDoc).title = 'Unnamed Filter';
- curFilterDoc._docFiltersList = undefined;
- curFilterDoc._docRangeFiltersList = undefined;
- }
- }
- };
-
- /**
* Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter
*/
updateFilterDoc = (doc: Doc) => {
@@ -1191,7 +1160,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
};
@computed get filtersSubMenu() {
- return !(this.selectedDoc?.currentFilter instanceof Doc) ? null : (
+ return (
<div className="propertiesView-filters">
<div className="propertiesView-filters-title" onPointerDown={action(() => (this.openFilters = !this.openFilters))} style={{ backgroundColor: this.openFilters ? 'black' : '' }}>
Filters
@@ -1200,35 +1169,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>
</div>
{!this.openFilters ? null : (
- <div className="propertiesView-filters-content" style={{ height: this.selectedDoc.currentFilter[HeightSym]() + 15 }}>
- <DocumentView
- Document={this.selectedDoc.currentFilter}
- DataDoc={undefined}
- addDocument={undefined}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- rootSelected={returnTrue}
- removeDocument={returnFalse}
- ScreenToLocalTransform={this.getTransform}
- PanelWidth={() => this.props.width}
- PanelHeight={this.selectedDoc.currentFilter[HeightSym]}
- renderDepth={0}
- scriptContext={this.selectedDoc.currentFilter}
- focus={emptyFunction}
- styleProvider={DefaultStyleProvider}
- isContentActive={returnTrue}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined}
- createNewFilterDoc={this.createNewFilterDoc}
- updateFilterDoc={this.updateFilterDoc}
- docViewPath={returnEmptyDoclist}
- dontCenter="y"
- />
+ <div className="propertiesView-filters-content" style={{ position: 'relative', height: 'auto' }}>
+ <FilterPanel rootDoc={this.selectedDoc ?? Doc.ActiveDashboard!} />
</div>
)}
</div>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 569579996..2d1839dd8 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -24,7 +24,6 @@ import { DocumentViewProps } from './DocumentView';
import './DocumentView.scss';
import { EquationBox } from './EquationBox';
import { FieldView, FieldViewProps } from './FieldView';
-import { FilterBox } from './FilterBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { FunctionPlotBox } from './FunctionPlotBox';
import { ImageBox } from './ImageBox';
@@ -254,7 +253,6 @@ export class DocumentContentsView extends React.Component<
YoutubeBox,
PresElementBox,
SearchBox,
- FilterBox,
FunctionPlotBox,
ColorBox,
DashWebRTCVideo,
diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss
index 107ad2e36..7f907c8d4 100644
--- a/src/client/views/nodes/FilterBox.scss
+++ b/src/client/views/nodes/FilterBox.scss
@@ -16,7 +16,6 @@
}
}
-
.filter-bookmark {
//display: flex;
@@ -39,7 +38,6 @@
// margin-bottom: 15px;
}
-
.filterBox-saveBookmark {
background-color: #e9e9e9;
border-radius: 11px;
@@ -61,7 +59,6 @@
margin-top: 4px;
margin-left: 2px;
}
-
}
.filterBox-select-scope,
@@ -154,12 +151,11 @@
display: flex;
flex-direction: column;
width: 200px;
- height: 100%;
position: absolute;
right: 0;
top: 0;
z-index: 1;
- background-color: #9F9F9F;
+ background-color: #9f9f9f;
.filterBox-tree {
z-index: 0;
@@ -177,8 +173,8 @@
cursor: pointer;
}
- >div,
- >div>div {
+ > div,
+ > div > div {
width: 100%;
height: 100%;
}
@@ -190,4 +186,4 @@
margin-bottom: 10px;
//height: calc(100% - 30px);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index d41a4bb82..e69de29bb 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -1,588 +0,0 @@
-import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import Select from 'react-select';
-import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt } from '../../../fields/Doc';
-import { List } from '../../../fields/List';
-import { RichTextField } from '../../../fields/RichTextField';
-import { listSpec } from '../../../fields/Schema';
-import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
-import { Docs, DocumentOptions } from '../../documents/Documents';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { UserOptions } from '../../util/GroupManager';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { SelectionManager } from '../../util/SelectionManager';
-import { CollectionTreeView } from '../collections/CollectionTreeView';
-import { CollectionView } from '../collections/CollectionView';
-import { ViewBoxBaseComponent } from '../DocComponent';
-import { EditableView } from '../EditableView';
-import { SearchBox } from '../search/SearchBox';
-import { DashboardToggleButton, DefaultStyleProvider, StyleProp } from '../StyleProvider';
-import { DocumentViewProps } from './DocumentView';
-import { FieldView, FieldViewProps } from './FieldView';
-import './FilterBox.scss';
-const higflyout = require('@hig/flyout');
-export const { anchorPoints } = higflyout;
-export const Flyout = higflyout.default;
-
-@observer
-export class FilterBox extends ViewBoxBaseComponent<FieldViewProps>() {
- constructor(props: Readonly<FieldViewProps>) {
- super(props);
- const targetDoc = FilterBox.targetDoc;
- if (targetDoc && !targetDoc.currentFilter) runInAction(() => (targetDoc.currentFilter = FilterBox.createFilterDoc()));
- }
-
- /// creates a new, empty filter doc
- static createFilterDoc() {
- const clearAll = `getProto(self).data = new List([])`;
- const reqdOpts: DocumentOptions = {
- _lockedPosition: true,
- _autoHeight: true,
- _fitWidth: true,
- _height: 150,
- _xPadding: 5,
- _yPadding: 5,
- _gridGap: 5,
- _forceActive: true,
- title: 'Unnamed Filter',
- filterBoolean: 'AND',
- boxShadow: '0 0',
- childDontRegisterViews: true,
- targetDropAction: 'same',
- ignoreClick: true,
- system: true,
- childDropAction: 'none',
- treeViewHideTitle: true,
- treeViewTruncateTitleWidth: 150,
- childContextMenuLabels: new List<string>(['Clear All']),
- childContextMenuScripts: new List<ScriptField>([ScriptField.MakeFunction(clearAll)!]),
- };
- return Docs.Create.FilterDocument(reqdOpts);
- }
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(FilterBox, fieldKey);
- }
-
- public _filterSelected = false;
- public _filterMatch = 'matched';
-
- @computed static get currentContainingCollectionDoc() {
- let docView: any = SelectionManager.Views()[0];
- let doc = docView.Document;
-
- while (docView && doc && doc.type !== 'collection') {
- doc = docView.props.ContainingCollectionDoc;
- docView = docView.props.ContainingCollectionView;
- }
-
- return doc;
- }
-
- // /**
- // * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
- // */
-
- // trying to get this to work rather than the version below this
-
- // @computed static get targetDoc() {
- // console.log(FilterBox.currentContainingCollectionDoc.type);
- // if (FilterBox._filterScope === "Current Collection") {
- // return FilterBox.currentContainingCollectionDoc;
- // }
- // else return CollectionDockingView.Instance.props.Document;
- // // return FilterBox._filterScope === "Current Collection" ? SelectionManager.Views()[0].Document || CollectionDockingView.Instance.props.Document : CollectionDockingView.Instance.props.Document;
- // }
-
- /**
- * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
- */
- @computed static get targetDoc() {
- return SelectionManager.Docs().length ? SelectionManager.Docs()[0] : Doc.ActiveDashboard;
- }
- @computed static get targetDocChildKey() {
- if (SelectionManager.Views().length) {
- return SelectionManager.Views()[0].ComponentView?.annotationKey || SelectionManager.Views()[0].ComponentView?.fieldKey || 'data';
- }
- return 'data';
- }
- @computed static get targetDocChildren() {
- return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || Doc.ActiveDashboard?.data);
- }
-
- @observable _loaded = false;
- componentDidMount() {
- reaction(
- () => DocListCastAsync(this.layoutDoc[this.fieldKey]),
- async activeTabsAsync => {
- const activeTabs = await activeTabsAsync;
- activeTabs && (await SearchBox.foreachRecursiveDocAsync(activeTabs, emptyFunction));
- runInAction(() => (this._loaded = true));
- },
- { fireImmediately: true }
- );
- }
-
- @computed get allDocs() {
- // trace();
- const allDocs = new Set<Doc>();
- const targetDoc = FilterBox.targetDoc;
- if (this._loaded && targetDoc) {
- SearchBox.foreachRecursiveDoc(FilterBox.targetDocChildren, (depth, doc) => allDocs.add(doc));
- }
- return Array.from(allDocs);
- }
-
- @computed get _allFacets() {
- // trace();
- const noviceReqFields = ['author', 'tags', 'text', 'type'];
- const noviceLayoutFields: string[] = []; //["_curPage"];
- const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
-
- const keys = new Set<string>(noviceFields);
- this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
- return Array.from(keys.keys())
- .filter(key => key[0])
- .filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
- .sort();
- }
-
- /**
- * The current attributes selected to filter based on
- */
- @computed get activeAttributes() {
- return DocListCast(this.dataDoc[this.props.fieldKey]);
- }
-
- /**
- * @returns a string array of the current attributes
- */
- @computed get currentFacets() {
- return this.activeAttributes.map(attribute => StrCast(attribute.title));
- }
-
- gatherFieldValues(childDocs: Doc[], facetKey: string) {
- const valueSet = new Set<string>();
- let rtFields = 0;
- childDocs.forEach(d => {
- const facetVal = d[facetKey];
- if (facetVal instanceof RichTextField) rtFields++;
- valueSet.add(Field.toString(facetVal as Field));
- const fieldKey = Doc.LayoutFieldKey(d);
- const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView');
- const data = d[annos ? fieldKey + '-annotations' : fieldKey];
- if (data !== undefined) {
- let subDocs = DocListCast(data);
- if (subDocs.length > 0) {
- let newarray: Doc[] = [];
- while (subDocs.length > 0) {
- newarray = [];
- subDocs.forEach(t => {
- const facetVal = t[facetKey];
- if (facetVal instanceof RichTextField) rtFields++;
- facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
- 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));
- });
- subDocs = newarray;
- }
- }
- }
- });
- return { strings: Array.from(valueSet.keys()), rtFields };
- }
- removeFilterDoc = (doc: Doc | Doc[]) => ((doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false);
- public removeFilter = (filterName: string) => {
- const targetDoc = FilterBox.targetDoc;
- if (targetDoc) {
- const filterDoc = targetDoc.currentFilter as Doc;
- const attributes = DocListCast(filterDoc.data);
- const found = attributes.findIndex(doc => doc.title === filterName);
- if (found !== -1) {
- (filterDoc.data as List<Doc>).splice(found, 1);
- const docFilter = Cast(targetDoc._docFilters, listSpec('string'));
- if (docFilter) {
- let index: number;
- while ((index = docFilter.findIndex(item => item.split(':')[0] === filterName)) !== -1) {
- docFilter.splice(index, 1);
- }
- }
- const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec('string'));
- if (docRangeFilters) {
- let index: number;
- while ((index = docRangeFilters.findIndex(item => item.split(':')[0] === filterName)) !== -1) {
- docRangeFilters.splice(index, 3);
- }
- }
- }
- }
- return true;
- };
- /**
- * Responds to clicking the check box in the flyout menu
- */
- facetClick = (facetHeader: string) => {
- const { targetDoc, targetDocChildren } = FilterBox;
- if (!targetDoc) return;
- const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader);
- if (found !== -1) {
- this.removeFilter(facetHeader);
- } else {
- const allCollectionDocs = targetDocChildren;
- const facetValues = this.gatherFieldValues(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);
- }
- });
- let newFacet: Opt<Doc>;
- if (facetHeader === 'text' || facetValues.rtFields / allCollectionDocs.length > 0.1) {
- newFacet = Docs.Create.TextDocument('', {
- title: facetHeader,
- system: true,
- target: targetDoc,
- _width: 100,
- _height: 25,
- _stayInCollection: true,
- treeViewExpandedView: 'layout',
- _treeViewOpen: true,
- _forceActive: true,
- ignoreClick: true,
- });
- Doc.GetProto(newFacet).forceActive = true; // required for FormattedTextBox to be able to gain focus since it will never be 'selected'
- Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- newFacet._textBoxPaddingX = newFacet._textBoxPaddingY = 4;
- const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
- newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: 'string' });
- } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
- newFacet = Docs.Create.SliderDocument({
- title: facetHeader,
- system: true,
- target: targetDoc,
- _fitWidth: true,
- _height: 40,
- _stayInCollection: true,
- treeViewExpandedView: 'layout',
- _treeViewOpen: true,
- _forceActive: true,
- _overflow: 'visible',
- });
- const newFacetField = Doc.LayoutFieldKey(newFacet);
- const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader);
- Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
- 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)));
- newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0];
- newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1];
- Doc.GetProto(newFacet)[newFacetField + '-minThumb'] = extendedMinVal;
- Doc.GetProto(newFacet)[newFacetField + '-maxThumb'] = extendedMaxVal;
- const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`;
- newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: 'number' });
- newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
- } else {
- newFacet = new Doc();
- newFacet.system = true;
- newFacet.title = facetHeader;
- newFacet.treeViewOpen = true;
- newFacet.layout = CollectionView.LayoutString('data');
- newFacet.layoutKey = 'layout';
- newFacet.type = DocumentType.COL;
- newFacet.target = targetDoc;
- newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${FilterBox.targetDocChildKey}", "${facetHeader}")`);
- }
- newFacet.hideContextMenu = true;
- Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
- }
- };
-
- @computed get scriptField() {
- const scriptText = 'setDocFilter(this?.target, heading, this.title, checked)';
- const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: 'string', checked: 'string', containingTreeView: Doc.name });
- return script ? () => script : undefined;
- }
-
- /**
- * Sets whether filters are ANDed or ORed together
- */
- @action
- changeBool = (e: any) => {
- FilterBox.targetDoc && (DocCast(FilterBox.targetDoc.currentFilter).filterBoolean = e.currentTarget.value);
- };
-
- /**
- * Changes whether to select matched or unmatched documents
- */
- @action
- changeMatch = (e: any) => {
- this._filterMatch = e.currentTarget.value;
- };
-
- @action
- changeSelected = () => {
- if (this._filterSelected) {
- this._filterSelected = false;
- SelectionManager.DeselectAll();
- } else {
- this._filterSelected = true;
- // helper method to select specified docs
- }
- };
-
- FilteringStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
- switch (property.split(':')[0]) {
- case StyleProp.Decorations:
- if (doc && !doc.treeViewHideHeaderFields) {
- return (
- <>
- <div style={{ marginRight: '5px', fontSize: '10px' }}>
- <select className="filterBox-selection">
- <option value="Is" key="Is">
- Is
- </option>
- <option value="Is Not" key="Is Not">
- Is Not
- </option>
- </select>
- </div>
- <div className="filterBox-treeView-close" onClick={e => this.removeFilter(StrCast(doc.title))}>
- <FontAwesomeIcon icon={'times'} size="sm" />
- </div>
- </>
- );
- }
- }
- return DefaultStyleProvider(doc, props, property);
- }
-
- suppressChildClick = () => ScriptField.MakeScript('')!;
-
- /**
- * Adds a filterDoc to the list of saved filters
- */
- saveFilter = () => {
- Doc.AddDocToList(Doc.UserDoc(), 'savedFilters', this.props.Document);
- };
-
- /**
- * Changes the title of the filterDoc
- */
- onTitleValueChange = (val: string) => {
- Doc.GetProto(this.props.Document).title = val || `FilterDoc for ${FilterBox.targetDoc?.title}`;
- return true;
- };
-
- /**
- * The flyout from which you can select a saved filter to apply
- */
- @computed get flyoutPanel() {
- return DocListCast(Doc.UserDoc().savedFilters).map(doc => {
- return (
- <div className="filterBox-tempFlyout" onWheel={e => e.stopPropagation()} style={{ height: 20, border: '2px' }} onPointerDown={() => this.props.updateFilterDoc?.(doc)}>
- {StrCast(doc.title)}
- </div>
- );
- });
- }
- setTreeHeight = (hgt: number) => {
- this.layoutDoc._height = NumCast(this.layoutDoc._autoHeightMargins) + 150; // need to add all the border sizes together.
- };
-
- /**
- * add lock and hide button decorations for the "Dashboards" flyout TreeView
- */
- FilterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
- if (property.split(':')[0] === StyleProp.Decorations) {
- return !doc || doc.treeViewHideHeaderFields ? null : DashboardToggleButton(doc, 'hidden', 'trash', 'trash', () => this.removeFilter(StrCast(doc.title)));
- }
- return this.props.styleProvider?.(doc, props, property);
- };
-
- layoutHeight = () => this.layoutDoc[HeightSym]();
- render() {
- const facetCollection = this.props.Document;
-
- const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
- return this.props.dontRegisterView ? null : (
- <div className="filterBox-treeView" style={{ width: '100%' }}>
- <div className="filterBox-title">
- <EditableView key="editableView" contents={this.props.Document.title} height={24} fontSize={15} GetValue={() => StrCast(this.props.Document.title)} SetValue={this.onTitleValueChange} />
- </div>
-
- <div className="filterBox-select-bool">
- <select className="filterBox-selection" onChange={this.changeBool} defaultValue={StrCast((FilterBox.targetDoc?.currentFilter as Doc)?.filterBoolean)}>
- <option value="AND" key="AND">
- AND
- </option>
- <option value="OR" key="OR">
- OR
- </option>
- </select>
- <div className="filterBox-select-text">filters together</div>
- </div>
-
- <div className="filterBox-select">
- <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
- </div>
-
- <div className="filterBox-tree" key="tree">
- <CollectionTreeView
- Document={facetCollection}
- DataDoc={Doc.GetProto(facetCollection)}
- fieldKey={this.props.fieldKey}
- CollectionView={undefined}
- disableDocBrushing={true}
- setHeight={this.setTreeHeight} // if the tree view can trigger the height of the filter box to change, then this needs to be filled in.
- docFilters={returnEmptyFilter}
- docRangeFilters={returnEmptyFilter}
- searchFilterDocs={returnEmptyDoclist}
- childDocumentsActive={returnTrue}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- ContainingCollectionView={this.props.ContainingCollectionView}
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.layoutHeight}
- rootSelected={this.props.rootSelected}
- renderDepth={1}
- dropAction={this.props.dropAction}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- isAnyChildContentActive={returnFalse}
- addDocTab={returnFalse}
- pinToPres={returnFalse}
- isSelected={returnFalse}
- select={returnFalse}
- bringToFront={emptyFunction}
- isContentActive={returnTrue}
- whenChildContentsActiveChanged={returnFalse}
- treeViewHideTitle={true}
- focus={emptyFunction}
- onCheckedClick={this.scriptField}
- treeViewHideHeaderFields={false}
- dontRegisterView={true}
- styleProvider={this.FilterStyleProvider}
- docViewPath={this.props.docViewPath}
- scriptContext={this.props.scriptContext}
- moveDocument={returnFalse}
- removeDocument={this.removeFilterDoc}
- addDocument={returnFalse}
- />
- </div>
- <div className="filterBox-bottom">
- {/* <div className="filterBox-select-matched">
- <input className="filterBox-select-box" type="checkbox"
- onChange={this.changeSelected} />
- <div className="filterBox-select-text">select</div>
- <select className="filterBox-selection" onChange={e => this.changeMatch(e)}>
- <option value="matched" key="matched">matched</option>
- <option value="unmatched" key="unmatched">unmatched</option>
- </select>
- <div className="filterBox-select-text">documents</div>
- </div> */}
-
- <div style={{ display: 'flex' }}>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark" onPointerDown={this.saveFilter}>
- <div>SAVE</div>
- </div>
- </div>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark">
- <Flyout className="myFilters-flyout" anchorPoint={anchorPoints.TOP} content={this.flyoutPanel}>
- <div>FILTERS</div>
- </Flyout>
- </div>
- </div>
- <div className="filterBox-saveWrapper">
- <div className="filterBox-saveBookmark" onPointerDown={this.props.createNewFilterDoc}>
- <div>NEW</div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-ScriptingGlobals.add(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) {
- const docFilters = Cast(layoutDoc._docFilters, listSpec('string'), []);
- for (const filter of docFilters) {
- const fields = filter.split(':'); // split into key:value:modifiers
- if (fields[0] === facetHeader && fields[1] === facetValue) {
- return fields[2];
- }
- }
- return undefined;
-});
-ScriptingGlobals.add(function readNumFacetData(layoutDoc: Doc, facetDoc: Doc, childKey: string, facetHeader: string) {
- const allCollectionDocs = new Set<Doc>();
- const activeTabs = DocListCast(layoutDoc[childKey]);
- SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>();
- if (facetHeader === 'tags')
- allCollectionDocs.forEach(child =>
- Field.toString(child[facetHeader] as Field)
- .split(':')
- .forEach(key => set.add(key))
- );
- else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
- const facetValues = Array.from(set).filter(v => v);
-
- let minVal = Number.MAX_VALUE,
- maxVal = -Number.MAX_VALUE;
- facetValues.map(val => {
- const num = val ? Number(val) : Number.NaN;
- if (!Number.isNaN(num)) {
- minVal = Math.min(num, minVal);
- maxVal = Math.max(num, maxVal);
- }
- });
- const newFacetField = Doc.LayoutFieldKey(facetDoc);
- const ranged = Doc.readDocRangeFilter(layoutDoc, facetHeader);
- 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)));
- facetDoc[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0];
- facetDoc[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1];
- Doc.GetProto(facetDoc)[newFacetField + '-minThumb'] = extendedMinVal;
- Doc.GetProto(facetDoc)[newFacetField + '-maxThumb'] = extendedMaxVal;
-});
-ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) {
- const allCollectionDocs = new Set<Doc>();
- const activeTabs = DocListCast(layoutDoc[childKey]);
- SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>();
- if (facetHeader === 'tags')
- allCollectionDocs.forEach(child =>
- Field.toString(child[facetHeader] as Field)
- .split(':')
- .forEach(key => set.add(key))
- );
- else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field)));
- const facetValues = Array.from(set).filter(v => v);
-
- let nonNumbers = 0;
-
- facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
- const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
- const doc = new Doc();
- doc.system = true;
- doc.title = facetValue.toString();
- doc.target = layoutDoc;
- doc.facetHeader = facetHeader;
- doc.facetValue = facetValue;
- doc.treeViewHideHeaderFields = true;
- doc.treeViewChecked = ComputedField.MakeFunction('determineCheckedState(self.target, self.facetHeader, self.facetValue)');
- return doc;
- });
- return new List<Doc>(facetValueDocSet);
-});
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index b33529aeb..a89e8b4ed 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -1,5 +1,8 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable } from 'mobx';
import { observer } from 'mobx-react';
+import { NodeSelection } from 'prosemirror-state';
import * as ReactDOM from 'react-dom/client';
import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
@@ -7,18 +10,14 @@ import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
import { ComputedField } from '../../../../fields/ScriptField';
import { Cast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
+import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
+import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
import React = require('react');
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils';
-import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
-import { Tooltip } from '@material-ui/core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { CollectionViewType } from '../../../documents/DocumentTypes';
-import { NodeSelection } from 'prosemirror-state';
-import { OpenWhere } from '../DocumentView';
-import { FormattedTextBoxComment } from './FormattedTextBoxComment';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -102,7 +101,11 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna
this._textBoxDoc = this.props.tbox.props.Document;
if (this.props.docid) {
- DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
+ DocServer.GetRefField(this.props.docid).then(
+ action(async dashDoc => {
+ dashDoc instanceof Doc && (this._dashDoc = dashDoc);
+ })
+ );
} else {
this._dashDoc = this.props.tbox.rootDoc;
}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 7713d884e..ee164ab31 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1424,14 +1424,14 @@ export namespace Doc {
// filters document in a container collection:
// all documents with the specified value for the specified key are included/excluded
// based on the modifiers :"check", "x", undefined
- export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldSuffix?: string, append: boolean = true) {
+ export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) {
if (!container) return;
- const filterField = '_' + (fieldSuffix ? fieldSuffix + '-' : '') + 'docFilters';
+ const filterField = '_' + (fieldPrefix ? fieldPrefix + '-' : '') + 'docFilters';
const docFilters = Cast(container[filterField], listSpec('string'), []);
runInAction(() => {
for (let i = 0; i < docFilters.length; i++) {
const fields = docFilters[i].split(':'); // split key:value:modifier
- if (fields[0] === key && (fields[1] === value || modifiers === 'match')) {
+ if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) {
if (fields[2] === modifiers && modifiers && fields[1] === value) {
if (toggle) modifiers = 'remove';
else return;