aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-04-27 16:45:59 -0400
committerbobzel <zzzman@gmail.com>2023-04-27 16:45:59 -0400
commitba5b687011526188bb024ddf37c254aaf285c06d (patch)
tree47df4f82053066d75ed32683375c2bb965be35d5 /src
parentb82a909a63a6de414d075735453240ebc02f5aa3 (diff)
performance tuning for schema views to avoid re-rendering each table cell when child docs change.
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx20
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx105
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx19
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx48
4 files changed, 94 insertions, 98 deletions
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 97eed7752..efd73a927 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -255,15 +255,17 @@ export class CollectionLinearView extends CollectionSubView() {
})}
/>
- <div
- className="collectionLinearView-content"
- style={{
- height: this.dimension(),
- flexDirection: flexDir as any,
- gap: flexGap,
- }}>
- {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
- </div>
+ {!this.layoutDoc.linearViewIsExpanded ? null : (
+ <div
+ className="collectionLinearView-content"
+ style={{
+ height: this.dimension(),
+ flexDirection: flexDir as any,
+ gap: flexGap,
+ }}>
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+ </div>
+ )}
</div>
</div>
);
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 8cd307adf..50a91a60a 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,9 +1,9 @@
import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, ObservableMap, trace, untracked } from 'mobx';
+import { action, computed, observable, ObservableMap, observe, reaction, trace, untracked } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, Field, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, NumListCast, StrListCast } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
@@ -47,6 +47,7 @@ const defaultColumnKeys: string[] = ['title', 'type', 'author', 'creationDate',
@observer
export class CollectionSchemaView extends CollectionSubView() {
+ private _keysDisposer: any;
private _closestDropIndex: number = 0;
private _previewRef: HTMLDivElement | null = null;
private _makeNewColumn: boolean = false;
@@ -58,15 +59,13 @@ export class CollectionSchemaView extends CollectionSubView() {
public static _rowMenuWidth: number = 60;
public static _previewDividerWidth: number = 4;
public static _newNodeInputHeight: number = 30;
+ public fieldInfos = new ObservableMap<string, FInfo>();
- @computed get _selectedDocs() {
- return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.props.Document));
- }
+ @observable _menuKeys: string[] = [];
@observable _rowEles: ObservableMap = new ObservableMap<Doc, HTMLDivElement>();
@observable _colEles: HTMLDivElement[] = [];
@observable _displayColumnWidths: number[] | undefined;
@observable _columnMenuIndex: number | undefined;
- @observable _fieldInfos: [string, FInfo][] = [];
@observable _newFieldWarning: string = '';
@observable _makeNewField: boolean = false;
@observable _newFieldDefault: any = 0;
@@ -76,34 +75,12 @@ export class CollectionSchemaView extends CollectionSubView() {
@observable _filterSearchValue: string = '';
@observable _selectedCell: [Doc, number] | undefined;
- get fieldInfos() {
- const docs = this.childDocs;
- const keys: { [key: string]: boolean } = {};
- // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
- // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
- // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
- // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
- // is displayed (unlikely) it won't show up until something else changes.
- //TODO Types
- untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)))));
-
- let computedKeys: { [key: string]: FInfo } = {};
-
- Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => {
- computedKeys[pair[0]] = pair[1];
- });
-
- Object.keys(keys).forEach((key: string) => {
- if (!(key in computedKeys)) {
- computedKeys[key] = new FInfo('');
- }
- });
-
- return computedKeys;
+ @computed get _selectedDocs() {
+ return SelectionManager.Docs().filter(doc => Doc.AreProtosEqual(DocCast(doc.context), this.rootDoc));
}
- get documentKeys() {
- return Object.keys(this.fieldInfos);
+ @computed get documentKeys() {
+ return Array.from(this.fieldInfos.keys());
}
@computed get previewWidth() {
@@ -119,20 +96,15 @@ export class CollectionSchemaView extends CollectionSubView() {
}
@computed get storedColumnWidths() {
- let widths = Cast(
+ const widths = NumListCast(
this.layoutDoc.columnWidths,
- listSpec('number'),
this.columnKeys.map(() => (this.tableWidth - CollectionSchemaView._rowMenuWidth) / this.columnKeys.length)
);
const totalWidth = widths.reduce((sum, width) => sum + width, 0);
if (totalWidth !== this.tableWidth - CollectionSchemaView._rowMenuWidth) {
- widths = widths.map(w => {
- const proportion = w / totalWidth;
- return proportion * (this.tableWidth - CollectionSchemaView._rowMenuWidth);
- });
+ return widths.map(w => (w / totalWidth) * (this.tableWidth - CollectionSchemaView._rowMenuWidth));
}
-
return widths;
}
@@ -148,19 +120,37 @@ export class CollectionSchemaView extends CollectionSubView() {
return BoolCast(this.layoutDoc.sortDesc);
}
- rowIndex(doc: Doc) {
- return this.sortedDocs.docs.indexOf(doc);
- }
-
+ @action
componentDidMount() {
this.props.setContentView?.(this);
document.addEventListener('keydown', this.onKeyDown);
+
+ Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1]));
+ this._keysDisposer = observe(
+ this.rootDoc[this.fieldKey ?? 'data'] as List<Doc>,
+ change => {
+ switch (change.type as any) {
+ case 'splice':
+ // prettier-ignore
+ (change as any).added.forEach((doc: Doc) => // for each document added
+ Doc.GetAllPrototypes(doc).forEach(proto => // for all of its prototypes (and itself)
+ Object.keys(proto).forEach(action(key => // check if any of its keys are new, and add them
+ !this.fieldInfos.get(key) && this.fieldInfos.set(key, new FInfo(''))))));
+ break;
+ case 'update': //let oldValue = change.oldValue; // fill this in if the entire child list will ever be reassigned with a new list
+ }
+ },
+ true
+ );
}
componentWillUnmount() {
+ this._keysDisposer?.();
document.removeEventListener('keydown', this.onKeyDown);
}
+ rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc);
+
@action
onKeyDown = (e: KeyboardEvent) => {
if (this._selectedDocs.length > 0) {
@@ -554,8 +544,7 @@ export class CollectionSchemaView extends CollectionSubView() {
onSearchKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'Enter':
- const menuKeys = Object.keys(this._fieldInfos);
- menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(menuKeys[0]) : action(() => (this._makeNewField = true))();
+ this._menuKeys.length > 0 && this._menuValue.length > 0 ? this.setKey(this._menuKeys[0]) : action(() => (this._makeNewField = true))();
break;
case 'Escape':
this.closeColumnMenu();
@@ -584,7 +573,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this._makeNewColumn = false;
this._columnMenuIndex = index;
this._menuValue = '';
- this._fieldInfos = Object.entries(this.fieldInfos);
+ this._menuKeys = this.documentKeys;
this._makeNewField = false;
this._newFieldWarning = '';
this._makeNewField = false;
@@ -630,7 +619,7 @@ export class CollectionSchemaView extends CollectionSubView() {
@action
updateKeySearch = (e: React.ChangeEvent<HTMLInputElement>) => {
this._menuValue = e.target.value;
- this._fieldInfos = Object.entries(this.fieldInfos).filter(value => value[0].toLowerCase().includes(this._menuValue.toLowerCase()));
+ this._menuKeys = this.documentKeys.filter(value => value.toLowerCase().includes(this._menuValue.toLowerCase()));
};
getFieldFilters = (field: string) => StrListCast(this.Document._docFilters).filter(filter => filter.split(':')[0] == field);
@@ -732,7 +721,7 @@ export class CollectionSchemaView extends CollectionSubView() {
{ passive: false }
)
}>
- {this._fieldInfos.map(([key, info]) => (
+ {this._menuKeys.map(key => (
<div
className="schema-search-result"
onPointerDown={e => {
@@ -742,13 +731,13 @@ export class CollectionSchemaView extends CollectionSubView() {
<p>
<span className="schema-search-result-key">
{key}
- {info.fieldType ? ', ' : ''}
+ {this.fieldInfos.get(key)!.fieldType ? ', ' : ''}
</span>
- <span className="schema-search-result-type" style={{ color: info.readOnly ? 'red' : 'inherit' }}>
- {info.fieldType}
+ <span className="schema-search-result-type" style={{ color: this.fieldInfos.get(key)!.readOnly ? 'red' : 'inherit' }}>
+ {this.fieldInfos.get(key)!.fieldType}
</span>
</p>
- <p className="schema-search-result-desc">{info.description}</p>
+ <p className="schema-search-result-desc">{this.fieldInfos.get(key)!.description}</p>
</div>
))}
</div>
@@ -888,13 +877,7 @@ export class CollectionSchemaView extends CollectionSubView() {
</div>
{this._columnMenuIndex !== undefined && this.renderColumnMenu}
{this._filterColumnIndex !== undefined && this.renderFilterMenu}
- <CollectionSchemaViewDocs
- schema={this}
- childDocs={this.sortedDocsFunc}
- setRef={(ref: HTMLDivElement | null) => {
- this._tableContentRef = ref;
- }}
- />
+ <CollectionSchemaViewDocs schema={this} childDocs={this.sortedDocsFunc} setRef={(ref: HTMLDivElement | null) => (this._tableContentRef = ref)} />
<EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} height={CollectionSchemaView._newNodeInputHeight} />
</div>
{this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown}></div>}
@@ -923,7 +906,7 @@ export class CollectionSchemaView extends CollectionSubView() {
moveDocument={this.props.moveDocument}
addDocument={this.addRow}
removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
+ whenChildContentsActiveChanged={returnFalse}
addDocTab={this.props.addDocTab}
pinToPres={this.props.pinToPres}
bringToFront={returnFalse}
@@ -978,7 +961,7 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
bringToFront={emptyFunction}
isDocumentActive={this.props.schema.props.childDocumentsActive?.() ? this.props.schema.props.isDocumentActive : this.props.schema.isContentActive}
isContentActive={emptyFunction}
- whenChildContentsActiveChanged={active => this.props.schema.props.whenChildContentsActiveChanged(active)}
+ whenChildContentsActiveChanged={this.props.schema.props.whenChildContentsActiveChanged}
hideDecorations={true}
hideTitle={true}
hideDocumentButtonBar={true}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 9864820a3..9772ce118 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -12,6 +12,8 @@ import { FieldView, FieldViewProps } from '../../nodes/FieldView';
import { CollectionSchemaView } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
import { SchemaTableCell } from './SchemaTableCell';
+import { computedFn } from 'mobx-utils';
+import { Doc } from '../../../../fields/Doc';
@observer
export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -24,12 +26,9 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
bounds = () => this._ref?.getBoundingClientRect();
@computed get schemaView() {
- const vpath = this.props.docViewPath();
- return vpath.length > 1 ? (vpath[vpath.length - 2].ComponentView as CollectionSchemaView) : undefined;
+ return this.props.DocumentView?.().props.docViewPath().lastElement()?.ComponentView as CollectionSchemaView;
}
- schemaViewFunc = () => this.schemaView;
-
@computed get schemaDoc() {
return this.props.DocumentView?.().props.docViewPath().lastElement()?.rootDoc;
}
@@ -86,6 +85,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
document.removeEventListener('pointermove', this.onPointerMove);
};
+ getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey));
+ selectCell = (doc: Doc, col: number) => this.schemaView?.selectCell(doc, col);
+ deselectCell = () => this.schemaView?.deselectCell();
+ selectedCell = () => this.schemaView?._selectedCell;
+ setColumnValues = (field: any, value: any) => this.schemaView?.setColumnValues(field, value) ?? false;
render() {
return (
<div
@@ -126,11 +130,14 @@ export class SchemaRowBox extends ViewBoxBaseComponent<FieldViewProps>() {
key={key}
Document={this.rootDoc}
col={index}
- schemaView={this.schemaViewFunc}
fieldKey={key}
columnWidth={this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth}
isRowActive={this.props.isContentActive}
- // setColumnValues={(field, value) => this.schemaView?.setColumnValues(field, value) ?? false}
+ getFinfo={this.getFinfo}
+ selectCell={this.selectCell}
+ deselectCell={this.deselectCell}
+ selectedCell={this.selectedCell}
+ setColumnValues={this.setColumnValues}
/>
))}
</div>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 374b92d72..686b21283 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,31 +1,34 @@
import React = require('react');
+import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
-import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils';
+import { extname } from 'path';
+import DatePicker from 'react-datepicker';
+import { DateField } from '../../../../fields/DateField';
+import { Doc, DocListCast, Field } from '../../../../fields/Doc';
+import { Cast, DateCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero, Utils } from '../../../../Utils';
import { Transform } from '../../../util/Transform';
import { EditableView } from '../../EditableView';
+import { Colors } from '../../global/globalEnums';
import { FieldView, FieldViewProps } from '../../nodes/FieldView';
import { KeyValueBox } from '../../nodes/KeyValueBox';
import { DefaultStyleProvider } from '../../StyleProvider';
import { CollectionSchemaView, ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
-import { action, computed, observable } from 'mobx';
-import { extname } from 'path';
-import { Cast, DateCast, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
-import { DateField } from '../../../../fields/DateField';
-import DatePicker from 'react-datepicker';
-import { Colors } from '../../global/globalEnums';
-import { SelectionManager } from '../../../util/SelectionManager';
+import { FInfo } from '../../../documents/Documents';
export interface SchemaTableCellProps {
Document: Doc;
col: number;
- schemaView: () => CollectionSchemaView | undefined;
+ deselectCell: () => void;
+ selectCell: (doc: Doc, col: number) => void;
+ selectedCell: () => [Doc, number] | undefined;
fieldKey: string;
columnWidth: number;
isRowActive: () => boolean | undefined;
- // setColumnValues: (field: string, value: string) => boolean;
+ getFinfo: (fieldKey: string) => FInfo | undefined;
+ setColumnValues: (field: string, value: string) => boolean;
}
@observer
@@ -33,11 +36,11 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
private _editorRef: EditableView | null = null;
@computed get readOnly() {
- return this.props.schemaView()?.fieldInfos[this.props.fieldKey]?.readOnly ?? false;
+ return this.props.getFinfo(this.props.fieldKey)?.readOnly ?? false;
}
@computed get selected() {
- const selected: [Doc, number] | undefined = this.props.schemaView()?._selectedCell;
+ const selected: [Doc, number] | undefined = this.props.selectedCell();
return this.props.isRowActive() && selected && selected[0] == this.props.Document && selected[1] == this.props.col;
}
@@ -57,11 +60,12 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
this._editorRef?.setIsFocused(true);
}
if (e.key == 'Escape') {
- this.props.schemaView()?.deselectCell();
+ this.props.deselectCell();
}
};
-
- get defaultCellContent() {
+ colWidthFunc = () => this.props.columnWidth;
+ colRowHeightFunc = () => CollectionSchemaView._rowHeight;
+ @computed get defaultCellContent() {
const props: FieldViewProps = {
Document: this.props.Document,
docFilters: returnEmptyFilter,
@@ -81,8 +85,8 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
whenChildContentsActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
- PanelWidth: () => this.props.columnWidth,
- PanelHeight: () => CollectionSchemaView._rowHeight,
+ PanelWidth: this.colWidthFunc,
+ PanelHeight: this.colRowHeightFunc,
addDocTab: returnFalse,
pinToPres: returnZero,
};
@@ -95,7 +99,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
GetValue={() => Field.toKeyValueString(this.props.Document, this.props.fieldKey)}
SetValue={(value: string, shiftDown?: boolean, enterKey?: boolean) => {
if (shiftDown && enterKey) {
- // this.props.setColumnValues(this.props.fieldKey, value);
+ this.props.setColumnValues(this.props.fieldKey, value);
}
return KeyValueBox.SetField(this.props.Document, this.props.fieldKey, value);
}}
@@ -106,7 +110,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
}
get getCellType() {
- const columnTypeStr = this.props.schemaView()?.fieldInfos[this.props.fieldKey]?.fieldType;
+ const columnTypeStr = this.props.getFinfo(this.props.fieldKey)?.fieldType;
if (columnTypeStr) {
if (columnTypeStr in FInfotoColType) {
return FInfotoColType[columnTypeStr];
@@ -139,7 +143,7 @@ export class SchemaTableCell extends React.Component<SchemaTableCellProps> {
<div
className="schema-table-cell"
onPointerDown={action(e => {
- if (!this.selected) this.props.schemaView()?.selectCell(this.props.Document, this.props.col);
+ if (!this.selected) this.props.selectCell(this.props.Document, this.props.col);
})}
style={{ width: this.props.columnWidth, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}>
{this.content}