diff options
author | bobzel <zzzman@gmail.com> | 2023-09-18 14:27:05 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2023-09-18 14:27:05 -0400 |
commit | 2b96f355ea7f4aa0e1fcf0dbee8ce6bf6e8f09d4 (patch) | |
tree | 8c719c26b90a1fd5862028cdd050741a252d2344 /src | |
parent | d5d6298c6b2890a40060ba6ecca7417f387a76fb (diff) |
revised how to do filtering by links.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 28 | ||||
-rw-r--r-- | src/client/views/FilterPanel.tsx | 15 | ||||
-rw-r--r-- | src/client/views/InkTangentHandles.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/MapBox/MapBox.tsx | 6 | ||||
-rw-r--r-- | src/fields/Doc.ts | 25 |
5 files changed, 37 insertions, 38 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 055950e6f..254be67b9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; import { inheritParentAcls, SharingPermissions } from '../../fields/util'; import { Upload } from '../../server/SharedMediaTypes'; -import { aggregateBounds, OmitKeys, Utils } from '../../Utils'; +import { OmitKeys, Utils } from '../../Utils'; import { YoutubeBox } from '../apis/youtube/YoutubeBox'; import { DocServer } from '../DocServer'; import { Networking } from '../Network'; @@ -1296,20 +1296,6 @@ export namespace DocUtils { for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(Doc.FilterSep)[0])) { const facet = filterFacets[facetKey]; - let links = true; - const linkedTo = filterFacets['-linkedTo'] && Array.from(Object.keys(filterFacets['-linkedTo']))?.[0]; - const linkedToField = filterFacets['-linkedTo']?.[linkedTo]; - const allLinks = linkedTo && linkedToField ? LinkManager.Instance.getAllRelatedLinks(d) : []; - // prettier-ignore - if (linkedTo) { - if (allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_1)?.[linkedToField]))) || // - allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_2)?.[linkedToField])))) - { - links = true; - } - else links = false - } - // facets that match some value in the field of the document (e.g. some text field) const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match'); @@ -1319,16 +1305,16 @@ export namespace DocUtils { // metadata facets that exist const exists = Object.keys(facet).filter(value => facet[value] === 'exists'); - // metadata facets that exist + // facets that unset metadata (a hack for making cookies work) const unsets = Object.keys(facet).filter(value => facet[value] === 'unset'); - // facets that have an x next to them + // facets that specify that a field must not match a specific value const xs = Object.keys(facet).filter(value => facet[value] === 'x'); - if (!linkedTo && !unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; + if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value)); const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value)); - const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined); + const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== '-linkedTo' ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length)); const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined); const satisfiesMatchFacets = !matches.length ? true @@ -1344,11 +1330,11 @@ export namespace DocUtils { }); // 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?.childFilters_boolean === 'OR') { - if (links && satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; + 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 else { - if (!links || !satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; + if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; } } return parentCollection?.childFilters_boolean === 'OR' ? false : true; diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 4af29c70e..312dc3a70 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -56,7 +56,7 @@ export class FilterPanel extends React.Component<filterProps> { @computed get _allFacets() { // trace(); - const noviceReqFields = ['author', 'tags', 'text', 'type']; + const noviceReqFields = ['author', 'tags', 'text', 'type', '-linkedTo']; const noviceLayoutFields: string[] = []; //["_layout_curPage"]; const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; @@ -118,7 +118,7 @@ export class FilterPanel extends React.Component<filterProps> { // } gatherFieldValues(childDocs: Doc[], facetKey: string) { - const valueSet = new Set<string>(); + const valueSet = new Set<string>(StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1])); let rtFields = 0; let subDocs = childDocs; if (subDocs.length > 0) { @@ -127,7 +127,6 @@ export class FilterPanel extends React.Component<filterProps> { newarray = []; subDocs.forEach(t => { const facetVal = t[facetKey]; - // console.log("facetVal " + facetVal) if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++; facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field)); (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal)); @@ -182,14 +181,14 @@ export class FilterPanel extends React.Component<filterProps> { }); if (facetHeader === 'text') { - return { facetHeader: facetHeader, renderType: 'text' }; + return { facetHeader, renderType: 'text' }; } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter - return { facetHeader: facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; + return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; } else { - return { facetHeader: facetHeader, renderType: 'checkbox' }; + return { facetHeader, renderType: 'checkbox' }; } }) ); @@ -236,7 +235,7 @@ export class FilterPanel extends React.Component<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>([String.fromCharCode(127) + '--undefined--', Doc.FilterAny]); + const set = new Set<string>([...StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]); if (facetHeader === 'tags') allCollectionDocs.forEach(child => StrListCast(child[facetHeader]) @@ -404,7 +403,7 @@ export class FilterPanel extends React.Component<filterProps> { ?.split(Doc.FilterSep)[2] ?? '' )} type={type} - onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? (fval === Doc.FilterAny ? 'exists' : 'check') : 'remove'), 'set filter')} + onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} /> {facetValue} </div> diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 71e0ff63c..65f6a6dfa 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -12,7 +12,6 @@ import { UndoManager } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; -import { DocumentView } from './nodes/DocumentView'; export interface InkHandlesProps { inkDoc: Doc; inkView: InkingStroke; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 3e22c0d0e..d7469e530 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -350,7 +350,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // Removes filter Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove'); Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove'); - Doc.setDocFilter(this.rootDoc, '-linkedTo', Field.toString(DocCast(this.selectedPin.mapPin)), 'removeAll'); + Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); const temp = this.selectedPin; if (!this._unmounting) { @@ -382,7 +382,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'match'); // Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'match'); - Doc.setDocFilter(this.rootDoc, '-linkedTo', Field.toScriptString(this.selectedPin), 'mapPin' as any); + Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toScriptString(this.selectedPin)}`, 'check'); this.recolorPin(this.selectedPin, 'green'); @@ -516,7 +516,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps // Removes filter Doc.setDocFilter(this.rootDoc, 'latitude', this.selectedPin.latitude, 'remove'); Doc.setDocFilter(this.rootDoc, 'longitude', this.selectedPin.longitude, 'remove'); - Doc.setDocFilter(this.rootDoc, '-linkedTo', Field.toString(DocCast(this.selectedPin.mapPin)), 'removeAll'); + Doc.setDocFilter(this.rootDoc, '-linkedTo', `mapPin=${Field.toString(DocCast(this.selectedPin.mapPin))}`, 'remove'); this.removePushpin(this.selectedPin); } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 5af49629d..78a03d782 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1463,18 +1463,32 @@ export namespace Doc { const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1; return isTransparent(StrCast(doc[key])); } + if (key === '-linkedTo') { + // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) + const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); + const matchLink = (value: string, anchor: Doc) => { + const linkedToExp = value?.split('='); + if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value; + return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; + }; + // prettier-ignore + return (value === Doc.FilterNone && !allLinks.length) || + (value === Doc.FilterAny && !!allLinks.length) || + (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || + matchLink(value,DocCast(link.link_anchor_2)) )); + } if (typeof value === 'string') { value = value.replace(`,${Utils.noRecursionHack}`, ''); } const fieldVal = doc[key]; if (Cast(fieldVal, listSpec('string'), []).length) { - const vals = Cast(fieldVal, listSpec('string'), []); + const vals = StrListCast(fieldVal); const docs = vals.some(v => (v as any) instanceof Doc); if (docs) return value === Field.toString(fieldVal as Field); return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } const fieldStr = Field.toString(fieldVal as Field); - return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return fieldStr.includes(value) || (value === Doc.FilterAny && fieldVal !== undefined) || (value === Doc.FilterNone && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { @@ -1520,18 +1534,19 @@ export namespace Doc { export const FilterSep = '::'; export const FilterAny = '--any--'; + export const FilterNone = '--undefined--'; // 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: 'removeAll' | 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: 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 = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters'; const childFilters = StrListCast(container[filterField]); runInAction(() => { for (let i = 0; i < childFilters.length; i++) { const fields = childFilters[i].split(FilterSep); // split key:value:modifier - if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || modifiers === 'removeAll' || (fields[2] === 'match' && modifiers === 'remove'))) { + if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) { if (toggle) modifiers = 'remove'; else return; @@ -1543,7 +1558,7 @@ export namespace Doc { } if (!childFilters.length && modifiers === 'match' && value === undefined) { container[filterField] = undefined; - } else if (modifiers !== 'remove' && modifiers !== 'removeAll') { + } else if (modifiers !== 'remove') { !append && (childFilters.length = 0); childFilters.push(key + FilterSep + value + FilterSep + modifiers); container[filterField] = new List<string>(childFilters); |