diff options
-rw-r--r-- | src/client/documents/Documents.ts | 18 | ||||
-rw-r--r-- | src/client/views/FieldsDropdown.tsx | 120 | ||||
-rw-r--r-- | src/client/views/FilterPanel.tsx | 239 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTimeView.tsx | 55 | ||||
-rw-r--r-- | src/client/views/global/globalScripts.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.scss | 1 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 67 |
7 files changed, 236 insertions, 266 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9b17901ca..031560886 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -77,6 +77,7 @@ export enum FInfoFieldType { enumeration, date, list, + rtf, } export class FInfo { description: string = ''; @@ -84,7 +85,7 @@ export class FInfo { fieldType?: FInfoFieldType; values?: Field[]; - filterable?: boolean = true; + filterable?: boolean = true; // can be used as a Filter in FilterPanel // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string constructor(d: string, readOnly?: boolean) { @@ -165,9 +166,19 @@ class DTypeInfo extends FInfo { override searchable = () => false; } class DateInfo extends FInfo { + constructor(d: string, filterable?: boolean) { + super(d, true); + this.filterable = filterable; + } fieldType? = FInfoFieldType.date; values?: DateField[] = []; - filterable = true; +} +class RtfInfo extends FInfo { + constructor(d: string, filterable?: boolean) { + super(d, true); + this.filterable = filterable; + } + fieldType? = FInfoFieldType.rtf; } class ListInfo extends FInfo { fieldType? = FInfoFieldType.list; @@ -178,6 +189,7 @@ type NUMt = NumInfo | number; type STRt = StrInfo | string; type LISTt = ListInfo | List<any>; type DOCt = DocInfo | Doc; +type RTFt = RtfInfo | RichTextField; type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; type PEVt = PEInfo | 'none' | 'all'; type COLLt = CTypeInfo | CollectionViewType; @@ -191,6 +203,7 @@ export class DocumentOptions { z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, false, [1, 0]); overlayX?: NUMt = new NumInfo('x coordinate of document in a overlay view', false); overlayY?: NUMt = new NumInfo('y coordinate of document in a overlay view', false); + text?: RTFt = new RtfInfo('rich text of a text doc', true); _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false); _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units"); latitude?: NUMt = new NumInfo('latitude coordinate for map views', false); @@ -470,7 +483,6 @@ export class DocumentOptions { sidebar_type_collection?: string; // collection type of text sidebar data_dashboards?: List<any>; // list of dashboards used in shareddocs; - text?: string; textTransform?: string; letterSpacing?: string; iconTemplate?: string; // name of icon template style diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx new file mode 100644 index 000000000..5638d34c6 --- /dev/null +++ b/src/client/views/FieldsDropdown.tsx @@ -0,0 +1,120 @@ +/** + * 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 })); + + console.log(options); + 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} + /> + ); + } +} diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 455801422..0521c4a4b 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -4,19 +4,16 @@ import * as React from 'react'; import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider'; import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai'; import { CiCircleRemove } from 'react-icons/ci'; -import Select from 'react-select'; import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; -import { DocOptions, FInfo } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; -import { UserOptions } from '../util/GroupManager'; import { SearchUtil } from '../util/SearchUtil'; import { SettingsManager } from '../util/SettingsManager'; import { undoable } from '../util/UndoManager'; +import { FieldsDropdown } from './FieldsDropdown'; import './FilterPanel.scss'; -import { FieldView } from './nodes/FieldView'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -26,9 +23,7 @@ interface filterProps { @observer export class FilterPanel extends ObservableReactComponent<filterProps> { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(FilterPanel, fieldKey); - } + @observable _selectedFacetHeaders = new Set<string>(); constructor(props: any) { super(props); @@ -38,45 +33,19 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { /** * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection */ - get targetDoc() { + get Document() { return this._props.Document; } @computed get targetDocChildKey() { - const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc); + const targetView = DocumentManager.Instance.getFirstDocumentView(this.Document); return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data'; } @computed get targetDocChildren() { - return [...DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.targetDoc[Doc.LayoutFieldKey(this.targetDoc) + '_sidebar'])]; - } - - @computed get allDocs() { - const allDocs = new Set<Doc>(); - const targetDoc = this.targetDoc; - if (targetDoc) { - SearchUtil.foreachRecursiveDoc([this.targetDoc], (depth, doc) => allDocs.add(doc)); - } - return Array.from(allDocs); - } - - @computed get _allFacets() { - const noviceReqFields = ['author', 'tags', 'text', 'type', LinkedTo]; - const noviceLayoutFields: string[] = []; //["_layout_curPage"]; - const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; - - const keys = new Set<string>(noviceFields); - this.allDocs.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('_')) || noviceFields.includes(key) || !Doc.noviceMode) - .sort(); - - noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1)); - - return [...noviceFields, ...sortedKeys]; + return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_sidebar'])]; } @computed get rangeFilters() { - return StrListCast(this.targetDoc?._childFiltersByRanges).filter((filter, i) => !(i % 3)); + return StrListCast(this.Document?._childFiltersByRanges).filter((filter, i) => !(i % 3)); } /** @@ -84,7 +53,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { * ["#tags::bob::check", "tags::joe::check", "width", "height"] */ @computed get activeFilters() { - return StrListCast(this.targetDoc?._childFilters).concat(this.rangeFilters); + return StrListCast(this.Document?._childFilters).concat(this.rangeFilters); } @computed get mapActiveFiltersToFacets() { @@ -100,22 +69,15 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { // this wants to return all the filter facets that have an existing filter set on them in order to show them in the rendered panel // this set may overlap the selectedFilters // if the components reloads, these will still exist and be shown - + // // ["#tags", "width", "height"] // - @computed get activeFacetHeaders() { const activeHeaders = new Array(); this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0])); return activeHeaders; } - /** - * @returns a string array of the current attributes - */ - // @computed get currentFacets() { - // return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); - // } static gatherFieldValues(childDocs: Doc[], facetKey: string, childFilters: string[]) { const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1])); @@ -147,8 +109,8 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { } public removeFilter = (filterName: string) => { - Doc.setDocFilter(this.targetDoc, filterName, undefined, 'remove'); - Doc.setDocRangeFilter(this.targetDoc, filterName, undefined); + Doc.setDocFilter(this.Document, filterName, undefined, 'remove'); + Doc.setDocRangeFilter(this.Document, filterName, undefined); }; // @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>(); @@ -156,18 +118,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { @observable _collapseReturnKeys = new Array(); // this computed function gets the active filters and maps them to their headers - // // activeRenderedFacetInfos() // returns renderInfo for all user selected filters and for all existing filters set on the document // Map("tags" => {"checkbox"}, - // "width" => {"rangs", domain:[1978,1992]}) + // "width" => {"range", domain:[1978,1992]}) // - @computed get activeRenderedFacetInfos() { return new Set( Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => { - const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.targetDoc.childFilters)); + const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters)); let nonNumbers = 0; let minVal = Number.MAX_VALUE, @@ -187,7 +147,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { } 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 + const ranged = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; } else { return { facetHeader, renderType: 'checkbox' }; @@ -196,8 +156,6 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { ); } - @observable _selectedFacetHeaders = new Set<string>(); - /** * user clicks on a filter facet because they want to see it. * this adds this chosen filter to a set of user selected filters called: selectedFilters @@ -231,7 +189,7 @@ export class FilterPanel extends ObservableReactComponent<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>([...StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]); + const set = new Set<string>([...StrListCast(this.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]); if (facetHeader === 'tags') allCollectionDocs.forEach(child => StrListCast(child[facetHeader]) @@ -255,59 +213,13 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; - @computed get fieldsDropdown() { - const filteredOptions = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.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.map(facet => ({ value: facet, label: facet })); - - return ( - <Select - styles={{ - control: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - placeholder: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - input: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - option: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor, - }), - menuList: (baseStyles, state) => ({ - ...baseStyles, - backgroundColor: SettingsManager.userBackgroundColor, - }), - }} - placeholder={'add a filter'} - options={options} - isMulti={false} - onChange={val => this.facetClick((val as UserOptions).value)} - onKeyDown={e => e.stopPropagation()} - //onMenuClose={onClose} - value={null} - closeMenuOnSelect={true} - /> - ); - } - render() { return ( <div className="filterBox-treeView"> <div className="filterBox-select"> - <div style={{ width: '100%' }}>{this.fieldsDropdown}</div> + <div style={{ width: '100%' }}> + <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder={true} placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} /> + </div> {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */} {/* <div className="filterBox-select-bool"> <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}> @@ -343,11 +255,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { className="filterBox-facetHeader-remove" onClick={action(e => { if (renderInfo.facetHeader === 'text') { - Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, 'match', 'remove'); + Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove'); } else { for (var key of this.facetValues(renderInfo.facetHeader)) { if (this.mapActiveFiltersToFacets.get(key)) { - Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove'); + Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove'); } } } @@ -355,7 +267,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { this._chosenFacetsCollapse.delete(renderInfo.facetHeader); if (renderInfo.domain) { - Doc.setDocRangeFilter(this.targetDoc, renderInfo.facetHeader, renderInfo.domain, 'remove'); + Doc.setDocRangeFilter(this.Document, renderInfo.facetHeader, renderInfo.domain, 'remove'); } })}> <CiCircleRemove />{' '} @@ -379,16 +291,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { case 'text': return ( <input - key={this.targetDoc[Id]} + key={this.Document[Id]} placeholder={'enter text to match'} defaultValue={ - StrListCast(this.targetDoc._childFilters) + StrListCast(this.Document._childFilters) .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader) ?.split(Doc.FilterSep)[1] } style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} - onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} - onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)} + onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} + onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)} /> ); case 'checkbox': @@ -399,12 +311,12 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { <input style={{ width: 20, marginLeft: 20 }} checked={['check', 'exists'].includes( - StrListCast(this.targetDoc._childFilters) + StrListCast(this.Document._childFilters) .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) ?.split(Doc.FilterSep)[2] ?? '' )} type={type} - onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} + onChange={undoable(e => Doc.setDocFilter(this.Document, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} /> {facetValue} </div> @@ -414,64 +326,51 @@ export class FilterPanel extends ObservableReactComponent<filterProps> { case 'range': const domain = renderInfoDomain; const range = renderInfoRange; - - if (range) { - console.log('this is info range ' + range[0] + ' , ' + range[1]); - } - if (domain) { - console.log('this is info domain ' + domain[0] + ', ' + domain[1]); - return ( - <> - {/* <div className="sliderBox-outerDiv-checkBox" style={{ float: 'left' }}> - <Checkbox color="primary" onChange={action(() => console.log('on change'))} /> - </div> */} - - <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}> - <Slider - mode={2} - step={Math.min(1, 0.1 * (domain[1] - domain[0]))} - domain={[domain[0], domain[1]]} // -1000, 1000 - rootStyle={{ position: 'relative', width: '100%' }} - onChange={values => Doc.setDocRangeFilter(this.targetDoc, facetHeader, values)} - values={renderInfoRange!}> - <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> - <Handles> - {({ handles, activeHandleID, getHandleProps }) => ( - <div className="slider-handles"> - {handles.map((handle, i) => { - // const value = i === 0 ? defaultValues[0] : defaultValues[1]; - return ( - <div> - <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} /> - </div> - ); - })} - </div> - )} - </Handles> - <Tracks left={false} right={false}> - {({ tracks, getTrackProps }) => ( - <div className="slider-tracks"> - {tracks.map(({ id, source, target }) => ( - <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} /> - ))} - </div> - )} - </Tracks> - <Ticks count={5}> - {({ ticks }) => ( - <div className="slider-ticks"> - {ticks.map(tick => ( - <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} /> - ))} - </div> - )} - </Ticks> - </Slider> - </div> - </> + <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}> + <Slider + mode={2} + step={Math.min(1, 0.1 * (domain[1] - domain[0]))} + domain={[domain[0], domain[1]]} // -1000, 1000 + rootStyle={{ position: 'relative', width: '100%' }} + onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)} + values={renderInfoRange!}> + <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> + <Handles> + {({ handles, activeHandleID, getHandleProps }) => ( + <div className="slider-handles"> + {handles.map((handle, i) => { + // const value = i === 0 ? defaultValues[0] : defaultValues[1]; + return ( + <div> + <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} /> + </div> + ); + })} + </div> + )} + </Handles> + <Tracks left={false} right={false}> + {({ tracks, getTrackProps }) => ( + <div className="slider-tracks"> + {tracks.map(({ id, source, target }) => ( + <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} /> + ))} + </div> + )} + </Tracks> + <Ticks count={5}> + {({ ticks }) => ( + <div className="slider-ticks"> + {ticks.map(tick => ( + <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} /> + ))} + </div> + )} + </Ticks> + </Slider> + </div> ); } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 38f6aa3e7..b92edd165 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,12 +1,10 @@ -import { toUpper } from 'lodash'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; -import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; @@ -15,7 +13,7 @@ import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { EditableView } from '../EditableView'; +import { FieldsDropdown } from '../FieldsDropdown'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FieldView'; import { PresBox } from '../nodes/trails'; @@ -192,51 +190,6 @@ export class CollectionTimeView extends CollectionSubView() { ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); }; - @computed get _allFacets() { - const facets = new Set<string>(); - this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); - Doc.AreProtosEqual(this.dataDoc, this.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); - return Array.from(facets); - } - menuCallback = (x: number, y: number) => { - ContextMenu.Instance.clearItems(); - const keySet: Set<string> = new Set(['tags']); - - this.childLayoutPairs.map(pair => - this._allFacets - .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string') - .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0])) - .map(fieldKey => keySet.add(fieldKey)) - ); - - const docItems: ContextMenuProps[] = Array.from(keySet).map(fieldKey => - ({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); // prettier-ignore - docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' }); - ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' }); - ContextMenu.Instance.displayMenu(x, y, ':'); - }; - - @computed get pivotKeyUI() { - return ( - <div className={'pivotKeyEntry'}> - <EditableView - GetValue={returnEmptyString} - SetValue={(value: any) => { - if (value?.length) { - this.layoutDoc._pivotField = value; - return true; - } - return false; - }} - background={'#f1efeb'} // this._props.headingObject ? this._props.headingObject.color : "#f1efeb"; - contents={':' + StrCast(this.layoutDoc._pivotField)} - showMenuOnLoad={true} - display={'inline'} - menuCallback={this.menuCallback} - /> - </div> - ); - } render() { let nonNumbers = 0; @@ -263,7 +216,6 @@ export class CollectionTimeView extends CollectionSubView() { return ( <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this._props.PanelWidth(), height: '100%' }}> - {this.pivotKeyUI} {this.contents} {!this._props.isSelected() || !doTimeline ? null : ( <> @@ -272,6 +224,9 @@ export class CollectionTimeView extends CollectionSubView() { <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} /> </> )} + <div style={{ right: 0, top: 0, position: 'absolute' }}> + <FieldsDropdown Document={this.Document} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} /> + </div> </div> ); } diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 51672513b..e73ab25b5 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -78,7 +78,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole } if (SelectionManager.Views.length) { SelectionManager.Docs.forEach(doc => { - Doc.GetProto(doc).layout_headingColor = color; + doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color; doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title'); }); } else { diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index c4dab16fb..5421c1b50 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -180,7 +180,6 @@ .documentView-titleWrapper, .documentView-titleWrapper-hover { - overflow: hidden; color: $black; transform-origin: top left; top: 0; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index abb1aa59f..b0e8cf6ff 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -48,6 +48,7 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; +import { FieldsDropdown } from '../FieldsDropdown'; interface Window { MediaRecorder: MediaRecorder; } @@ -784,28 +785,25 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document } captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); - fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => { - const filteredFields = Object.entries(DocOptions).reduce((set, [field, opts]) => (opts.filterable ? set.add(field) : set), new Set(reqdFields)); + fieldsDropdown = (placeholder: string) => { return ( - <div style={{ width: dropdownWidth }}> - <div - ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))} - onPointerDown={action(e => (this._changingTitleField = true))} - style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> - <Dropdown - activeChanged={action(isOpen => !isOpen && (this._changingTitleField = false))} - selectedVal={placeholder} - setSelectedVal={onChange} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - closeOnSelect={true} - dropdownType={DropdownType.SELECT} - items={Array.from(filteredFields).map(facet => ({ val: facet, text: facet }))} - width={100} - fillWidth - /> - </div> + <div + ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))} + onPointerDown={action(e => (this._changingTitleField = true))} + style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> + <FieldsDropdown + Document={this.Document} + placeholder={placeholder} + selectFunc={action((field: string | number) => { + if (this.layoutDoc.layout_showTitle) { + this.layoutDoc._layout_showTitle = field; + } else if (!this._props.layout_showTitle) { + Doc.UserDoc().layout_showTitle = field; + } + this._changingTitleField = false; + })} + menuClose={action(() => (this._changingTitleField = false))} + /> </div> ); }; @@ -839,22 +837,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document background, pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined, }}> - {!dropdownWidth - ? null - : this.fieldsDropdown( - [StrCast(this.layoutDoc.layout_showTitle)], - dropdownWidth, - StrCast(this.layoutDoc.layout_showTitle).split(':')[0], - action((field: string | number) => { - if (this.layoutDoc.layout_showTitle) { - this.layoutDoc._layout_showTitle = field; - } else if (!this._props.layout_showTitle) { - Doc.UserDoc().layout_showTitle = field; - } - this._changingTitleField = false; - }), - action(() => (this._changingTitleField = false)) - )} + {!dropdownWidth ? null : <div style={{ width: dropdownWidth }}>{this.fieldsDropdown(showTitle)}</div>} <div style={{ width: `calc(100% - ${dropdownWidth}px)`, @@ -864,10 +847,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document }}> <EditableView ref={this._titleRef} - contents={showTitle - .split(';') - .map(field => targetDoc[field.trim()]?.toString()) - .join(' \\ ')} + contents={ + showTitle + .split(';') + .map(field => targetDoc[field.trim()]?.toString()) + .join(' \\ ') || '-unset-' + } display="block" oneLine={true} fontSize={(this.titleHeight / 15) * 10} |