aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/FieldsDropdown.tsx
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2024-04-09 12:17:03 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2024-04-09 12:17:03 -0400
commitb158b7ffb564db8dc60da9b80b01b10f1ed8b7cf (patch)
treeaf35e320eb876c12c617fda2eb70ce19ef376b67 /src/client/views/FieldsDropdown.tsx
parenteecc7ee1d14719d510ec2975826022c565a35e5f (diff)
parent3b90916af8ffcbeaf5b8a3336009b84e19c22fa9 (diff)
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/FieldsDropdown.tsx')
-rw-r--r--src/client/views/FieldsDropdown.tsx119
1 files changed, 119 insertions, 0 deletions
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
new file mode 100644
index 000000000..6a5c2cb4c
--- /dev/null
+++ b/src/client/views/FieldsDropdown.tsx
@@ -0,0 +1,119 @@
+/**
+ * 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 { computed, makeObservable, observable, runInAction } 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 { SettingsManager } from '../util/SettingsManager';
+import './FilterPanel.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+
+interface fieldsDropdownProps {
+ Document: 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[];
+}
+
+@observer
+export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps> {
+ @observable _newField = '';
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ @computed get allDescendantDocs() {
+ const allDocs = new Set<Doc>();
+ SearchUtil.foreachRecursiveDoc([this._props.Document], (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].filterable)
+ .forEach((pair: [string, FInfo]) => 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: SettingsManager.userColor,
+ backgroundColor: SettingsManager.userBackgroundColor,
+ padding: 0,
+ margin: 0,
+ }),
+ singleValue: (baseStyles, state) => ({
+ ...baseStyles,
+ color: SettingsManager.userColor,
+ background: SettingsManager.userBackgroundColor,
+ }),
+ placeholder: (baseStyles, state) => ({
+ ...baseStyles,
+ color: SettingsManager.userColor,
+ background: SettingsManager.userBackgroundColor,
+ }),
+ input: (baseStyles, state) => ({
+ ...baseStyles,
+ padding: 0,
+ margin: 0,
+ color: SettingsManager.userColor,
+ background: 'transparent',
+ }),
+ option: (baseStyles, state) => ({
+ ...baseStyles,
+ color: SettingsManager.userColor,
+ background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
+ }),
+ menuList: (baseStyles, state) => ({
+ ...baseStyles,
+ backgroundColor: SettingsManager.userBackgroundColor,
+ }),
+ }}
+ placeholder={typeof this._props.placeholder === 'string' ? this._props.placeholder : this._props.placeholder?.()}
+ options={options as any}
+ isMulti={false}
+ onChange={val => this._props.selectFunc((val as any as { value: string; label: string }).value)}
+ onKeyDown={e => {
+ if (e.key === 'Enter') {
+ runInAction(() => this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value)));
+ }
+ e.stopPropagation();
+ }}
+ onMenuClose={this._props.menuClose}
+ closeMenuOnSelect={true}
+ value={this._props.showPlaceholder ? null : undefined}
+ />
+ );
+ }
+}