aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/FieldsDropdown.tsx
blob: 0bdf92bbcdefc13a5739ef95eccca0f92b270553 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
 * This creates a dropdown menu that's populated with possible field key names (e.g., author, tags)
 *
 * The set of field names actually displayed is based on searching the prop 'Document' and its descendants :
 *     The field list will contain all of the fields within the prop Document and all of its children;
 *     this list is then pruned down to only include fields that are not marked in Documents.ts to be non-filterable
 */

import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import { Doc } from '../../fields/Doc';
import { DocOptions, FInfo } from '../documents/Documents';
import { SearchUtil } from '../util/SearchUtil';
import { SnappingManager } from '../util/SnappingManager';
import './FilterPanel.scss';
import { ObservableReactComponent } from './ObservableReactComponent';

interface fieldsDropdownProps {
    Doc: Doc; // show fields for this Doc if set, otherwise for all docs in dashboard
    selectFunc: (value: string) => void;
    menuClose?: () => void;
    placeholder?: string | (() => string);
    showPlaceholder?: true; // if true, then input field always shows the placeholder value; otherwise, it shows the current selection
    addedFields?: string[];
    isInactive?: boolean;
}

@observer
export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps> {
    @observable _newField = '';
    constructor(props: fieldsDropdownProps) {
        super(props);
        makeObservable(this);
    }

    @computed get allDescendantDocs() {
        const allDocs = new Set<Doc>();
        SearchUtil.foreachRecursiveDoc([this._props.Doc], (depth, doc) => allDocs.add(doc));
        return Array.from(allDocs);
    }

    @computed get fieldsOfDocuments() {
        const keys = new Set<string>();
        this.allDescendantDocs.forEach(doc => SearchUtil.documentKeys(doc).filter(key => keys.add(key)));
        const sortedKeys = Array.from(keys.keys())
            .filter(key => key[0])
            .filter(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || !Doc.noviceMode)
            .sort();

        Array.from(keys).forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1));

        return [...Array.from(keys), ...sortedKeys];
    }

    render() {
        const filteredOptions = ['author', ...(this._newField ? [this._newField] : []), ...(this._props.addedFields ?? []), ...this.fieldsOfDocuments.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];

        Object.entries(DocOptions)
            .filter(opts => opts[1] instanceof FInfo && opts[1].filterable)
            .forEach((pair: [string, unknown]) => filteredOptions.push(pair[0]));
        const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));

        return (
            <Select
                styles={{
                    control: (baseStyles /* , state */) => ({
                        ...baseStyles,
                        minHeight: '5px',
                        maxHeight: '30px',
                        color: SnappingManager.userColor,
                        backgroundColor: SnappingManager.userBackgroundColor,
                        padding: 0,
                        margin: 0,
                    }),
                    singleValue: (baseStyles /* , state */) => ({
                        ...baseStyles,
                        color: SnappingManager.userColor,
                        background: SnappingManager.userBackgroundColor,
                        display: this._props.isInactive ? 'none' : undefined,
                    }),
                    placeholder: (baseStyles /* , state */) => ({
                        ...baseStyles,
                        color: SnappingManager.userColor,
                        background: SnappingManager.userBackgroundColor,
                        display: this._props.isInactive ? 'none' : undefined,
                    }),
                    input: (baseStyles /* , state */) => ({
                        ...baseStyles,
                        padding: 0,
                        margin: 0,
                        color: SnappingManager.userColor,
                        background: 'transparent',
                    }),
                    option: (baseStyles, state) => ({
                        ...baseStyles,
                        color: SnappingManager.userColor,
                        background: !state.isFocused ? SnappingManager.userBackgroundColor : SnappingManager.userVariantColor,
                    }),
                    menuList: (baseStyles /* , state */) => ({
                        ...baseStyles,
                        backgroundColor: SnappingManager.userBackgroundColor,
                    }),
                }}
                placeholder={typeof this._props.placeholder === 'string' ? this._props.placeholder : this._props.placeholder?.()}
                options={options}
                isMulti={false}
                onChange={val => this._props.selectFunc((val as { value: string; label: string }).value)}
                onKeyDown={action(e => {
                    if (e.key === 'Enter') {
                        this._props.selectFunc((this._newField = (e.nativeEvent.target as HTMLSelectElement)?.value));
                    }
                    e.stopPropagation();
                })}
                onMenuClose={this._props.menuClose}
                closeMenuOnSelect
                value={this._props.showPlaceholder ? null : undefined}
            />
        );
    }
}