From 2e3b83df9b0cb69db6f5febae70a2e36a0a90822 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 2 Sep 2020 10:46:29 -0400
Subject: made highlight colors more obvious.  fixed tags to update when #key
 is set in textdoc.

---
 src/client/views/search/SearchBox.tsx | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

(limited to 'src/client/views/search/SearchBox.tsx')

diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3d564f073..6c5278892 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -192,8 +192,11 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
 
             while (docs.length > 0) {
                 newarray = [];
-                docs.forEach((d) => {
-                    d.data && newarray.push(...DocListCast(d.data));
+                docs.forEach(d => {
+                    const fieldKey = Doc.LayoutFieldKey(d);
+                    const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
+                    const data = d[annos ? fieldKey + "-annotations" : fieldKey];
+                    data && newarray.push(...DocListCast(data));
                     const hlights = new Set<string>();
                     this.documentKeys(d).forEach(key =>
                         Field.toString(d[key] as Field).toLowerCase().includes(query) && hlights.add(key));
-- 
cgit v1.2.3-70-g09d2


From 3f3ba063bb6e3bde20dafc8a9e6d7fe9254e22fe Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 2 Sep 2020 15:30:45 -0400
Subject: fixed behavior of menuButtons to toggle contents.  added start of a
 filter panel.

---
 src/client/documents/DocumentTypes.ts           |   7 +-
 src/client/documents/Documents.ts               |   8 +
 src/client/util/CurrentUserUtils.ts             |  17 ++
 src/client/views/DocumentDecorations.tsx        |  32 ++--
 src/client/views/MainView.tsx                   |   9 +-
 src/client/views/collections/TabDocView.tsx     |   2 +-
 src/client/views/nodes/DocumentContentsView.tsx |   3 +-
 src/client/views/nodes/DocumentView.tsx         |   2 +-
 src/client/views/nodes/FilterBox.scss           |  44 ++++++
 src/client/views/nodes/FilterBox.tsx            | 197 ++++++++++++++++++++++++
 src/client/views/search/SearchBox.tsx           |   2 +-
 src/fields/Doc.ts                               |   9 +-
 12 files changed, 304 insertions(+), 28 deletions(-)
 create mode 100644 src/client/views/nodes/FilterBox.scss
 create mode 100644 src/client/views/nodes/FilterBox.tsx

(limited to 'src/client/views/search/SearchBox.tsx')

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 1bef6fa08..37a148e55 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -13,7 +13,8 @@ export enum DocumentType {
     INK = "inks",        // ink stroke
     SCREENSHOT = "screenshot",  // view of a desktop application
     FONTICON = "fonticonbox",   // font icon
-    SEARCH = "search",            // search query
+    FILTER = "filter",
+    SEARCH = "search",          // search query
     LABEL = "label",            // simple text label
     BUTTON = "button",          // onClick button
     WEBCAM = "webcam",          // webcam
@@ -32,10 +33,10 @@ export enum DocumentType {
     YOUTUBE = "youtube",        // youtube directory (view of you tube search results)
     DOCHOLDER = "docholder",    // nested document (view of a document)
     SEARCHITEM = "searchitem",
-    COMPARISON = "comparison",   // before/after view with slider (view of 2 images)
+    COMPARISON = "comparison",  // before/after view with slider (view of 2 images)
     GROUP = "group",            // group of users
 
     LINKDB = "linkdb",          // database of links  ??? why do we have this
-    SCRIPTDB = "scriptdb",          // database of scripts
+    SCRIPTDB = "scriptdb",      // database of scripts
     GROUPDB = "groupdb"         // database of groups
 }
\ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index feb84843d..fbcecbec6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -50,6 +50,7 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { SearchBox } from "../views/search/SearchBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { DocumentType } from "./DocumentTypes";
+import { FilterBox } from "../views/nodes/FilterBox";
 const path = require('path');
 
 const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -246,6 +247,10 @@ export namespace Docs {
                 layout: { view: SearchBox, dataField: defaultDataKey },
                 options: { _width: 400 }
             }],
+            [DocumentType.FILTER, {
+                layout: { view: FilterBox, dataField: defaultDataKey },
+                options: { _width: 400 }
+            }],
             [DocumentType.COLOR, {
                 layout: { view: ColorBox, dataField: defaultDataKey },
                 options: { _nativeWidth: 220, _nativeHeight: 300 }
@@ -821,6 +826,9 @@ export namespace Docs {
         export function FontIconDocument(options?: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { hideLinkButton: true, ...(options || {}) });
         }
+        export function FilterDocument(options?: DocumentOptions) {
+            return InstanceFromProto(Prototypes.get(DocumentType.FILTER), undefined, { ...(options || {}) });
+        }
 
         export function PresElementBoxDocument(options?: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) });
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 734050b05..164c3ab67 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -516,6 +516,7 @@ export class CurrentUserUtils {
             { title: "Import", target: Cast(doc.myImportPanel, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
             { title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
             { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)' },
+            { title: "Filter", target: Cast(doc.myFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' },
             { title: "Pres. Trails", target: Cast(doc.myPresentations, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
             { title: "Catalog", target: undefined as any, icon: "file", click: 'selectMainMenu(self)' },
             { title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' },
@@ -775,6 +776,21 @@ export class CurrentUserUtils {
             (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
         }
     }
+    static setupFilterDocs(doc: Doc) {
+        // setup Recently Closed library item
+        doc.myFilter === undefined;
+        if (doc.myFilter === undefined) {
+            doc.myFilter = new PrefetchProxy(Docs.Create.FilterDocument({
+                title: "FilterDoc", _height: 500,
+                treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias",
+                treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false,
+                lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true
+            }));
+            const clearAll = ScriptField.MakeScript(`self.data = new List([])`);
+            (doc.myFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
+            (doc.myFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
+        }
+    }
 
 
     static setupUserDoc(doc: Doc) {
@@ -806,6 +822,7 @@ export class CurrentUserUtils {
         CurrentUserUtils.setupDashboards(doc);
         CurrentUserUtils.setupPresentations(doc);
         CurrentUserUtils.setupRecentlyClosedDocs(doc);
+        CurrentUserUtils.setupFilterDocs(doc);
         CurrentUserUtils.setupUserDoc(doc);
     }
 
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6951cb592..7781d6069 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { Tooltip } from '@material-ui/core';
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { AclAdmin, AclEdit, DataSym, Doc, Field } from "../../fields/Doc";
+import { AclAdmin, AclEdit, DataSym, Doc, Field, WidthSym, HeightSym } from "../../fields/Doc";
 import { Document } from '../../fields/documentSchemas';
 import { HtmlField } from '../../fields/HtmlField';
 import { InkField } from "../../fields/InkField";
@@ -11,7 +11,7 @@ import { ScriptField } from '../../fields/ScriptField';
 import { Cast, NumCast } from "../../fields/Types";
 import { GetEffectiveAcl } from '../../fields/util';
 import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from "../../Utils";
-import { DocUtils } from "../documents/Documents";
+import { DocUtils, Docs } from "../documents/Documents";
 import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from "../util/DragManager";
 import { SelectionManager } from "../util/SelectionManager";
@@ -178,17 +178,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
     }
     @action
     onMaximizeDown = (e: React.PointerEvent): void => {
-        if (e.ctrlKey) {
-            const selectedDocs = SelectionManager.SelectedDocuments();
-            const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
-            alias.context = undefined;
-            //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
-            CollectionDockingView.AddSplit(alias, "right");
-            e.stopPropagation();
-            e.preventDefault();
-        } else {
-            setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onMaximizeClick);
-        }
+        setupMoveUpEvents(this, e, (e, d) => false, (e) => { }, this.onMaximizeClick);
     }
     @undoBatch
     @action
@@ -196,7 +186,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (e.button === 0) {
             const selectedDocs = SelectionManager.SelectedDocuments();
             if (selectedDocs.length) {
-                CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
+                if (e.ctrlKey) {
+                    const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
+                    alias.context = undefined;
+                    //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
+                    CollectionDockingView.AddSplit(alias, "right");
+                } else if (e.shiftKey) {
+                    const alias = Doc.MakeAlias(selectedDocs[0].props.Document);
+                    alias.context = undefined;
+                    alias.x = -alias[WidthSym]() / 2;
+                    alias.y = -alias[HeightSym]() / 2;
+                    //CollectionDockingView.Instance?.OpenFullScreen(selectedDocs[0]);
+                    CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([alias], { title: "Tab for " + alias.title }), "right");
+                } else {
+                    CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right");
+                }
             }
         }
         SelectionManager.DeselectAll();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7c7c5b72b..e6e80d9fb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -372,9 +372,9 @@ export class MainView extends React.Component {
     @action
     selectMenu = (button: Doc) => {
         const title = StrCast(Doc.GetProto(button).title);
-        const closed = !this._flyoutWidth;
+        const willOpen = !this._flyoutWidth || this._panelContent !== title;
         this.closeFlyout();
-        if (this._panelContent !== title || !this._flyoutWidth) {
+        if (willOpen) {
             switch (this._panelContent = title) {
                 case "Settings":
                     SettingsManager.Instance.open();
@@ -384,7 +384,7 @@ export class MainView extends React.Component {
                     SearchBox.Instance.enter(undefined);
                     break;
                 default:
-                    closed && this.expandFlyout(button);
+                    this.expandFlyout(button);
             }
         }
         return true;
@@ -434,6 +434,7 @@ export class MainView extends React.Component {
         this._lastButton && (this._lastButton.color = "white");
         this._lastButton && (this._lastButton._backgroundColor = "");
         this._panelContent = "none";
+        this._sidebarContent.proto = undefined;
         this._flyoutWidth = 0;
     });
 
@@ -520,7 +521,7 @@ export class MainView extends React.Component {
             </defs>
         </svg>;
     }
-    select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
+    select = (ctrlPressed: boolean) => { };
 
     @computed get search() {
         TraceMobx();
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 3c230537c..5cdf337a9 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -191,7 +191,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
         if (this.props.glContainer.tab && this._isActive !== this.props.glContainer.tab.isActive) {
             this._isActive = this.props.glContainer.tab.isActive;
             this._isActive && setTimeout(() => this.view && SelectionManager.SelectDoc(this.view, false), 0);
-            (CollectionDockingView.Instance as any)._goldenLayout.isInitialised && CollectionDockingView.Instance.stateChanged();
+            (CollectionDockingView.Instance as any)._goldenLayout?.isInitialised && CollectionDockingView.Instance.stateChanged();
             !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
         }
     }
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 5d5a1f7f3..1b2070c0f 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -25,6 +25,7 @@ import { KeyValueBox } from "./KeyValueBox";
 import { PDFBox } from "./PDFBox";
 import { PresBox } from "./PresBox";
 import { SearchBox } from "../search/SearchBox";
+import { FilterBox } from "./FilterBox";
 import { ColorBox } from "./ColorBox";
 import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo";
 import { LinkAnchorBox } from "./LinkAnchorBox";
@@ -191,7 +192,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo
                 components={{
                     FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, LabelBox, SliderBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                    PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox,
+                    PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, FilterBox,
                     ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox,
                     ScreenshotBox, HTMLtag, ComparisonBox
                 }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0182e652f..41e6d9603 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -300,7 +300,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let stopPropagate = true;
             let preventDefault = true;
             !this.props.Document._isBackground && this.props.bringToFront(this.props.Document);
-            if (this._doubleTap && this.props.renderDepth && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
+            if (this._doubleTap && ((this.props.renderDepth && this.props.Document.type !== DocumentType.FONTICON) || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click
                 if (this._timeout) {
                     clearTimeout(this._timeout);
                     this._timeout = undefined;
diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss
new file mode 100644
index 000000000..c5d6e2505
--- /dev/null
+++ b/src/client/views/nodes/FilterBox.scss
@@ -0,0 +1,44 @@
+
+
+    .collectionTimeView-treeView {
+        display: flex;
+        flex-direction: column;
+        width: 200px;
+        height: 100%;
+        position: absolute;
+        right: 0;
+        top: 0;
+        border-left: solid 1px;
+        z-index: 1;
+
+        .collectionTimeView-addfacet {
+            display: inline-block;
+            width: 200px;
+            height: 30px;
+            background: darkGray;
+            text-align: left;
+
+            .collectionTimeView-button {
+                align-items: center;
+                display: flex;
+                width: 100%;
+                height: 100%;
+
+                .collectionTimeView-span {
+                    margin: auto;
+                }
+            }
+
+            >div,
+            >div>div {
+                width: 100%;
+                height: 100%;
+            }
+        }
+
+        .collectionTimeView-tree {
+            display: inline-block;
+            width: 100%;
+            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
new file mode 100644
index 000000000..7bffc9184
--- /dev/null
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -0,0 +1,197 @@
+import React = require("react");
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import { ColorState, SketchPicker } from 'react-color';
+import { Doc, Opt, DocListCast, Field, DataSym } from "../../../fields/Doc";
+import { Utils, returnEmptyFilter, returnEmptyDoclist, returnZero, emptyPath, returnFalse, emptyFunction, returnOne } from "../../../Utils";
+import { documentSchema } from "../../../fields/documentSchemas";
+import { InkTool } from "../../../fields/InkField";
+import { makeInterface, listSpec } from "../../../fields/Schema";
+import { StrCast, Cast } from "../../../fields/Types";
+import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
+import { ViewBoxBaseComponent } from "../DocComponent";
+import "./ColorBox.scss";
+import { FieldView, FieldViewProps } from './FieldView';
+import { DocumentType } from "../../documents/DocumentTypes";
+import { CollectionTreeView } from "../collections/CollectionTreeView";
+import { ScriptField, ComputedField } from "../../../fields/ScriptField";
+import { Docs } from "../../documents/Documents";
+import { RichTextField } from "../../../fields/RichTextField";
+import { List } from "../../../fields/List";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import './FilterBox.scss';
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+type FilterBoxDocument = makeInterface<[typeof documentSchema]>;
+const FilterBoxDocument = makeInterface(documentSchema);
+
+@observer
+export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDocument>(FilterBoxDocument) {
+    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); }
+
+    @computed get _allFacets() {
+        return ["author", "creationDate", "type", "text", "context"];
+        // const noviceReqFields = ["author", "creationDate", "type", "text", "context"];
+        // const noviceLayoutFields: string[] = [];//["_curPage"];
+        // const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
+
+        // const facets = new Set<string>([...noviceReqFields, ...noviceLayoutFields]);
+        // this.childDocs.filter(child => child).forEach(child => child && Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
+        // Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key)));
+
+        // return Array.from(facets).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort();
+    }
+    /**
+     * Responds to clicking the check box in the flyout menu
+     */
+    facetClick = (facetHeader: string) => {
+        const targetDoc = CollectionDockingView.Instance.props.Document;
+        const found = DocListCast(this.dataDoc[this.props.fieldKey]).findIndex(doc => doc.title === facetHeader);
+        if (found !== -1) {
+            (this.dataDoc[this.props.fieldKey] as List<Doc>).splice(found, 1);
+            const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
+            if (docFilter) {
+                let index: number;
+                while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+                    docFilter.splice(index, 3);
+                }
+            }
+            const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string"));
+            if (docRangeFilters) {
+                let index: number;
+                while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) {
+                    docRangeFilters.splice(index, 3);
+                }
+            }
+        } else {
+            const allCollectionDocs = DocListCast((targetDoc.data as any)[0].data);
+            var rtfields = 0;
+            const facetValues = Array.from(allCollectionDocs.reduce((set, child) => {
+                const field = child[facetHeader] as Field;
+                const fieldStr = Field.toString(field);
+                if (field instanceof RichTextField || (typeof (field) === "string" && fieldStr.split(" ").length > 2)) rtfields++;
+                return set.add(fieldStr);
+            }, new Set<string>()));
+
+            let nonNumbers = 0;
+            let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE;
+            facetValues.map(val => {
+                const num = Number(val);
+                if (Number.isNaN(num)) {
+                    nonNumbers++;
+                } else {
+                    minVal = Math.min(num, minVal);
+                    maxVal = Math.max(num, maxVal);
+                }
+            });
+            let newFacet: Opt<Doc>;
+            if (facetHeader === "text" || rtfields / allCollectionDocs.length > 0.1) {
+                newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true });
+                Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox
+                newFacet._textBoxPadding = 4;
+                const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
+                newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
+            } else if (nonNumbers / facetValues.length < .1) {
+                newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true });
+                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.abs(maxVal - minVal) * .05);
+                const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .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 = `setDocFilterRange(this?.target, "${facetHeader}", range)`;
+                newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" });
+            } else {
+                newFacet = new Doc();
+                newFacet.sytem = true;
+                newFacet.title = facetHeader;
+                newFacet.treeViewOpen = true;
+                newFacet.type = DocumentType.COL;
+                const capturedVariables = { layoutDoc: targetDoc, dataDoc: (targetDoc.data as any)[0][DataSym] };
+                newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables);
+            }
+            newFacet && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet);
+        }
+    }
+    filterBackground = () => "rgba(105, 105, 105, 0.432)";
+    get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
+    @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;
+    }
+
+    @computed get filterView() {
+        const facetCollection = this.props.Document.proto as Doc;
+        const flyout = (
+            <div className="collectionTimeView-flyout" style={{ width: `100%`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
+                {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}>
+                    <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey]).some(d => d.title === facet)} />
+                    <span className="checkmark" />
+                    {facet}
+                </label>)}
+            </div>
+        );
+
+        return this.props.dontRegisterView ? (null) : <div className="collectionTimeView-treeView" style={{ width: "100%" }}>
+            <div className="collectionTimeView-addFacet" style={{ width: "100%" }} onPointerDown={e => e.stopPropagation()}>
+                <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}>
+                    <div className="collectionTimeView-button">
+                        <FontAwesomeIcon icon={"edit"} size={"lg"} />
+                        <span className="collectionTimeView-span">Facet Filters</span>
+                    </div>
+                </Flyout>
+            </div>
+            <div className="collectionTimeView-tree" key="tree">
+                <CollectionTreeView
+                    PanelPosition={""}
+                    Document={facetCollection}
+                    DataDoc={Doc.GetProto(facetCollection)}
+                    fieldKey={`${this.props.fieldKey}`}
+                    CollectionView={undefined}
+                    docFilters={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+                    ContainingCollectionView={this.props.ContainingCollectionView}
+                    PanelWidth={this.props.PanelWidth}
+                    PanelHeight={this.props.PanelHeight}
+                    NativeHeight={returnZero}
+                    NativeWidth={returnZero}
+                    LibraryPath={emptyPath}
+                    rootSelected={this.props.rootSelected}
+                    renderDepth={1}
+                    dropAction={this.props.dropAction}
+                    ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+                    addDocTab={returnFalse}
+                    pinToPres={returnFalse}
+                    isSelected={returnFalse}
+                    select={returnFalse}
+                    bringToFront={emptyFunction}
+                    active={this.props.active}
+                    whenActiveChanged={returnFalse}
+                    treeViewHideTitle={true}
+                    ContentScaling={returnOne}
+                    focus={returnFalse}
+                    treeViewHideHeaderFields={true}
+                    onCheckedClick={this.scriptField}
+                    ignoreFields={this.ignoreFields}
+                    annotationsKey={""}
+                    dontRegisterView={true}
+                    backgroundColor={this.filterBackground}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    addDocument={returnFalse} />
+            </div>
+        </div>;
+    }
+    render() {
+        return this.filterView;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 6c5278892..3907d2d5c 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -45,7 +45,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc
     private _maxSearchIndex: number = 0;
     private _curRequest?: Promise<any> = undefined;
     private _disposers: { [name: string]: IReactionDisposer } = {};
-    private _blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.DOCHOLDER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+    private _blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.DOCHOLDER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
 
     private docsforfilter: Doc[] | undefined = [];
     private realTotalResults: number = 0;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index bc31c1a21..08d949b5e 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -23,6 +23,7 @@ import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, u
 import { LinkManager } from "../client/util/LinkManager";
 import JSZip = require("jszip");
 import { saveAs } from "file-saver";
+import { CollectionDockingView } from "../client/views/collections/CollectionDockingView";
 
 export namespace Field {
     export function toKeyValueString(doc: Doc, key: string): string {
@@ -1048,7 +1049,8 @@ export namespace Doc {
         doc.layoutKey = deiconify || "layout";
     }
     export function setDocFilterRange(target: Doc, key: string, range?: number[]) {
-        const docRangeFilters = Cast(target._docRangeFilters, listSpec("string"), []);
+        const container = target ?? CollectionDockingView.Instance.props.Document;
+        const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []);
         for (let i = 0; i < docRangeFilters.length; i += 3) {
             if (docRangeFilters[i] === key) {
                 docRangeFilters.splice(i, 3);
@@ -1059,14 +1061,15 @@ export namespace Doc {
             docRangeFilters.push(key);
             docRangeFilters.push(range[0].toString());
             docRangeFilters.push(range[1].toString());
-            target._docRangeFilters = new List<string>(docRangeFilters);
+            container._docRangeFilters = new List<string>(docRangeFilters);
         }
     }
 
     // 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: Doc, key: string, value: any, modifiers?: "remove" | "match" | "check" | "x" | undefined) {
+    export function setDocFilter(target: Opt<Doc>, key: string, value: any, modifiers?: "remove" | "match" | "check" | "x" | undefined) {
+        const container = target ?? CollectionDockingView.Instance.props.Document;
         const docFilters = Cast(container._docFilters, listSpec("string"), []);
         runInAction(() => {
             for (let i = 0; i < docFilters.length; i += 3) {
-- 
cgit v1.2.3-70-g09d2

