aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/collectionSchema/CollectionSchemaView.tsx')
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx841
1 files changed, 277 insertions, 564 deletions
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index c4ee1805f..a09d2722c 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,646 +1,359 @@
import React = require('react');
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from 'mobx';
+import { action, computed, observable, ObservableSet } from 'mobx';
import { observer } from 'mobx-react';
-import Measure from 'react-measure';
-import { Resize } from 'react-table';
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast } from '../../../../fields/Doc';
+import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
-import { PastelSchemaPalette, SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, NumCast } from '../../../../fields/Types';
-import { TraceMobx } from '../../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils';
-import { DocUtils } from '../../../documents/Documents';
+import { Cast, StrCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
+import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../../Utils';
+import { Docs, DocUtils } from '../../../documents/Documents';
+import { DragManager } from '../../../util/DragManager';
import { SelectionManager } from '../../../util/SelectionManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ContextMenuProps } from '../../ContextMenuItem';
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss';
-import { DocumentView } from '../../nodes/DocumentView';
-import { DefaultStyleProvider } from '../../StyleProvider';
+import { EditableView } from '../../EditableView';
+import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionSchemaView.scss';
-import { SchemaTable } from './SchemaTable';
-// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
+import { SchemaColumnHeader } from './SchemaColumnHeader';
+import { SchemaRowBox } from './SchemaRowBox';
export enum ColumnType {
- Any,
Number,
String,
Boolean,
Doc,
Image,
- List,
- Date,
}
-// this map should be used for keys that should have a const type of value
-const columnTypes: Map<string, ColumnType> = new Map([
- ['title', ColumnType.String],
- ['x', ColumnType.Number],
- ['y', ColumnType.Number],
- ['_width', ColumnType.Number],
- ['_height', ColumnType.Number],
- ['_nativeWidth', ColumnType.Number],
- ['_nativeHeight', ColumnType.Number],
- ['isPrototype', ColumnType.Boolean],
- ['_curPage', ColumnType.Number],
- ['_currentTimecode', ColumnType.Number],
- ['zIndex', ColumnType.Number],
-]);
+
+const defaultColumnKeys: string[] = ['title', 'type', 'author', 'text', 'data', 'tags'];
@observer
export class CollectionSchemaView extends CollectionSubView() {
- private _previewCont?: HTMLDivElement;
-
- @observable _previewDoc: Doc | undefined = undefined;
- @observable _focusedTable: Doc = this.props.Document;
- @observable _col: any = '';
- @observable _menuWidth = 0;
- @observable _headerOpen = false;
- @observable _headerIsEditing = false;
- @observable _menuHeight = 0;
- @observable _pointerX = 0;
- @observable _pointerY = 0;
- @observable _openTypes: boolean = false;
-
- @computed get previewWidth() {
- return () => NumCast(this.props.Document.schemaPreviewWidth);
- }
- @computed get previewHeight() {
- return () => this.props.PanelHeight() - 2 * this.borderWidth;
- }
- @computed get tableWidth() {
- return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth();
- }
- @computed get borderWidth() {
- return Number(COLLECTION_BORDER_WIDTH);
- }
- @computed get scale() {
- return this.props.ScreenToLocalTransform().Scale;
- }
- @computed get columns() {
- return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []);
- }
- set columns(columns: SchemaHeaderField[]) {
- this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns);
+ private _ref: HTMLDivElement | null = null;
+ private _lastSelectedRow: number | undefined;
+ private _selectedDocSortedArray: Doc[] = [];
+ private _closestDropIndex: number = 0;
+ private _minColWidth: number = 120;
+
+ @observable _rowMenuWidth: number = 100;
+ @observable _selectedDocs: ObservableSet = new ObservableSet<SchemaRowBox>();
+ @observable _isDragging: boolean = false;
+ @observable _displayColumnWidths: number[] | undefined;
+
+ @computed get columnKeys() {
+ return Cast(this.layoutDoc.columnKeys, listSpec('string'), defaultColumnKeys);
}
- @computed get menuCoordinates() {
- let searchx = 0;
- let searchy = 0;
- if (this.props.Document._searchDoc) {
- const el = document.getElementsByClassName('collectionSchemaView-searchContainer')[0];
- if (el !== undefined) {
- const rect = el.getBoundingClientRect();
- searchx = rect.x;
- searchy = rect.y;
- }
- }
- const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx;
- const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy;
- return this.props.ScreenToLocalTransform().transformPoint(x, y);
+ @computed get storedColumnWidths() {
+ return Cast(
+ this.layoutDoc.columnWidths,
+ listSpec('number'),
+ this.columnKeys.map(() => (this.props.PanelWidth() - this._rowMenuWidth) / this.columnKeys.length)
+ );
}
- get documentKeys() {
- 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)))));
-
- this.columns.forEach(key => (keys[key.heading] = true));
- return Array.from(Object.keys(keys));
+ @computed get displayColumnWidths() {
+ return this._displayColumnWidths ?? this.storedColumnWidths;
}
- @action setHeaderIsEditing = (isEditing: boolean) => (this._headerIsEditing = isEditing);
-
@undoBatch
- setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
- this._openTypes = false;
- if (columnTypes.get(columnField.heading)) return;
-
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setType(NumCast(type));
- columns[index] = columnField;
- this.columns = columns;
- }
- });
+ @action
+ changeColumnKey = (index: number, newKey: string) => {
+ let currKeys = this.columnKeys;
+ currKeys[index] = newKey;
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
+ return true;
+ };
@undoBatch
- setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setColor(color);
- columns[index] = columnField;
- this.columns = columns; // need to set the columns to trigger rerender
- }
+ @action
+ addColumn = (index: number) => {
+ let currKeys = this.columnKeys;
+ currKeys.splice(index, 0, 'title');
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
};
@undoBatch
@action
- setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
- const columns = this.columns;
- columns.forEach(col => col.setDesc(undefined));
-
- const index = columns.findIndex(c => c.heading === columnField.heading);
- const column = columns[index];
- column.setDesc(descending);
- columns[index] = column;
- this.columns = columns;
+ removeColumn = (index: number) => {
+ let currKeys = this.columnKeys;
+ currKeys.splice(index, 1);
+ this.layoutDoc.columnKeys = new List<string>(currKeys);
};
- renderTypes = (col: any) => {
- if (columnTypes.get(col.heading)) return null;
-
- const type = col.type;
-
- const anyType = (
- <div className={'columnMenu-option' + (type === ColumnType.Any ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Any)}>
- <FontAwesomeIcon icon={'align-justify'} size="sm" />
- Any
- </div>
- );
-
- const numType = (
- <div className={'columnMenu-option' + (type === ColumnType.Number ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Number)}>
- <FontAwesomeIcon icon={'hashtag'} size="sm" />
- Number
- </div>
- );
-
- const textType = (
- <div className={'columnMenu-option' + (type === ColumnType.String ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.String)}>
- <FontAwesomeIcon icon={'font'} size="sm" />
- Text
- </div>
- );
-
- const boolType = (
- <div className={'columnMenu-option' + (type === ColumnType.Boolean ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
- <FontAwesomeIcon icon={'check-square'} size="sm" />
- Checkbox
- </div>
- );
-
- const listType = (
- <div className={'columnMenu-option' + (type === ColumnType.List ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.List)}>
- <FontAwesomeIcon icon={'list-ul'} size="sm" />
- List
- </div>
- );
-
- const docType = (
- <div className={'columnMenu-option' + (type === ColumnType.Doc ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
- <FontAwesomeIcon icon={'file'} size="sm" />
- Document
- </div>
- );
-
- const imageType = (
- <div className={'columnMenu-option' + (type === ColumnType.Image ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Image)}>
- <FontAwesomeIcon icon={'image'} size="sm" />
- Image
- </div>
- );
-
- const dateType = (
- <div className={'columnMenu-option' + (type === ColumnType.Date ? ' active' : '')} onClick={() => this.setColumnType(col, ColumnType.Date)}>
- <FontAwesomeIcon icon={'calendar'} size="sm" />
- Date
- </div>
- );
-
- const allColumnTypes = (
- <div className="columnMenu-types">
- {anyType}
- {numType}
- {textType}
- {boolType}
- {listType}
- {docType}
- {imageType}
- {dateType}
- </div>
- );
-
- const justColType =
- type === ColumnType.Any
- ? anyType
- : type === ColumnType.Number
- ? numType
- : type === ColumnType.String
- ? textType
- : type === ColumnType.Boolean
- ? boolType
- : type === ColumnType.List
- ? listType
- : type === ColumnType.Doc
- ? docType
- : type === ColumnType.Date
- ? dateType
- : imageType;
-
- return (
- <div className="collectionSchema-headerMenu-group" onClick={action(() => (this._openTypes = !this._openTypes))}>
- <div>
- <label style={{ cursor: 'pointer' }}>Column type:</label>
- <FontAwesomeIcon icon={'caret-down'} size="lg" style={{ float: 'right', transform: `rotate(${this._openTypes ? '180deg' : 0})`, transition: '0.2s all ease' }} />
- </div>
- {this._openTypes ? allColumnTypes : justColType}
- </div>
- );
+ @action
+ startResize = (e: any, index: number, left: boolean) => {
+ this._displayColumnWidths = this.storedColumnWidths;
+ setupMoveUpEvents(this, e, (e, delta) => this.resizeColumn(e, index, left), this.finishResize, emptyFunction);
};
- renderSorting = (col: any) => {
- const sort = col.desc;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Sort by:</label>
- <div className="columnMenu-sort">
- <div className={'columnMenu-option' + (sort === true ? ' active' : '')} onClick={() => this.setColumnSort(col, true)}>
- <FontAwesomeIcon icon="sort-amount-down" size="sm" />
- Sort descending
- </div>
- <div className={'columnMenu-option' + (sort === false ? ' active' : '')} onClick={() => this.setColumnSort(col, false)}>
- <FontAwesomeIcon icon="sort-amount-up" size="sm" />
- Sort ascending
- </div>
- <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}>
- <FontAwesomeIcon icon="times" size="sm" />
- Clear sorting
- </div>
- </div>
- </div>
- );
- };
+ @action
+ resizeColumn = (e: PointerEvent, index: number, left: boolean) => {
+ if (this._displayColumnWidths) {
+ let shrinking;
+ let growing;
+
+ let change = e.movementX;
+
+ if (left && index !== 0) {
+ growing = change < 0 ? index : index - 1;
+ shrinking = change < 0 ? index - 1 : index;
+ } else if (!left && index !== this.columnKeys.length - 1) {
+ growing = change > 0 ? index : index + 1;
+ shrinking = change > 0 ? index + 1 : index;
+ }
- renderColors = (col: any) => {
- const selected = col.color;
+ if (shrinking === undefined || growing === undefined) return true;
- const pink = PastelSchemaPalette.get('pink2');
- const purple = PastelSchemaPalette.get('purple2');
- const blue = PastelSchemaPalette.get('bluegreen1');
- const yellow = PastelSchemaPalette.get('yellow4');
- const red = PastelSchemaPalette.get('red2');
- const gray = '#f1efeb';
+ change = Math.abs(change);
+ if (this._displayColumnWidths[shrinking] - change < this._minColWidth) {
+ change = this._displayColumnWidths[shrinking] - this._minColWidth;
+ }
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Color:</label>
- <div className="columnMenu-colors">
- <div className={'columnMenu-colorPicker' + (selected === pink ? ' active' : '')} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === purple ? ' active' : '')} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === blue ? ' active' : '')} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === yellow ? ' active' : '')} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === red ? ' active' : '')} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
- <div className={'columnMenu-colorPicker' + (selected === gray ? ' active' : '')} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
- </div>
- </div>
- );
- };
+ this._displayColumnWidths[shrinking] -= change;
+ this._displayColumnWidths[growing] += change;
- @undoBatch
- @action
- changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, 'f1efeb')]);
- } else {
- if (addNew) {
- columns.push(new SchemaHeaderField(newKey, 'f1efeb'));
- this.columns = columns;
- } else {
- const index = columns.map(c => c.heading).indexOf(oldKey);
- if (index > -1) {
- const column = columns[index];
- column.setHeading(newKey);
- columns[index] = column;
- this.columns = columns;
- if (filter) {
- Doc.setDocFilter(this.props.Document, newKey, filter, 'match');
- } else {
- this.props.Document._docFilters = undefined;
- }
- }
- }
+ return false;
}
+ return true;
};
+ // @undoBatch
@action
- openHeader = (col: any, screenx: number, screeny: number) => {
- this._col = col;
- this._headerOpen = true;
- this._pointerX = screenx;
- this._pointerY = screeny;
+ finishResize = () => {
+ console.log('finished');
+ this.layoutDoc.columnWidths = new List<number>(this._displayColumnWidths);
+ this._displayColumnWidths = undefined;
};
@action
- closeHeader = () => {
- this._headerOpen = false;
- };
-
- @undoBatch
- @action
- deleteColumn = (key: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([]);
- } else {
- const index = columns.map(c => c.heading).indexOf(key);
- if (index > -1) {
- columns.splice(index, 1);
- this.columns = columns;
+ selectRow = (e: React.PointerEvent, doc: Doc, index: number) => {
+ const ctrl = e.ctrlKey || e.metaKey;
+ const shift = e.shiftKey;
+ if (shift && this._lastSelectedRow !== undefined) {
+ const startRow = Math.min(this._lastSelectedRow, index);
+ const endRow = Math.max(this._lastSelectedRow, index);
+ for (let i = startRow; i <= endRow; i++) {
+ const currDoc: Doc = this.childDocs[i];
+ if (!this._selectedDocs.has(currDoc)) this._selectedDocs.add(currDoc);
}
+ this._lastSelectedRow = endRow;
+ } else if (ctrl) {
+ if (!this._selectedDocs.has(doc)) {
+ this._selectedDocs.add(doc);
+ this._lastSelectedRow = index;
+ } else {
+ this._selectedDocs.delete(doc);
+ }
+ } else {
+ this._selectedDocs.clear();
+ this._selectedDocs.add(doc);
+ this._lastSelectedRow = index;
}
- this.closeHeader();
- };
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, -this.borderWidth);
- };
-
- @action
- onHeaderClick = (e: React.PointerEvent) => {
- e.stopPropagation();
+ if (this._lastSelectedRow && this._selectedDocs.size > 0) {
+ SelectionManager.SelectSchemaViewDoc(this.childDocs[this._lastSelectedRow]);
+ } else {
+ SelectionManager.SelectSchemaViewDoc(undefined);
+ }
};
@action
- onWheel(e: React.WheelEvent) {
- const scale = this.props.ScreenToLocalTransform().Scale;
- this.props.isContentActive(true) && e.stopPropagation();
- }
-
- @computed get renderMenuContent() {
- TraceMobx();
- return (
- <div className="collectionSchema-header-menuOptions">
- {this.renderTypes(this._col)}
- {this.renderColors(this._col)}
- <div className="collectionSchema-headerMenu-group">
- <button
- onClick={() => {
- this.deleteColumn(this._col.heading);
- }}>
- Hide Column
- </button>
- </div>
- </div>
- );
- }
-
- private createTarget = (ele: HTMLDivElement) => {
- this._previewCont = ele;
- super.CreateDropTarget(ele);
+ sortedSelectedDocs = (): Doc[] => {
+ return this.childDocs.filter(doc => this._selectedDocs.has(doc));
};
- isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
-
- @action setFocused = (doc: Doc) => (this._focusedTable = doc);
-
- @action setPreviewDoc = (doc: Opt<Doc>) => {
- SelectionManager.SelectSchemaViewDoc(doc);
- this._previewDoc = doc;
- };
-
- //toggles preview side-panel of schema
- @action
- toggleExpander = () => {
- this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
+ setDropIndex = (index: number) => {
+ this._closestDropIndex = index;
};
- onDividerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
- };
@action
- onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const nativeWidth = this._previewCont!.getBoundingClientRect();
- const minWidth = 40;
- const maxWidth = 1000;
- const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
- const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
- this.props.Document.schemaPreviewWidth = width;
- return false;
- };
-
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (this.props.isSelected(true)) e.stopPropagation();
- else this.props.select(false);
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (super.onInternalDrop(e, de)) {
+ this._isDragging = false;
+ const pushedDocs: Doc[] = this.childDocs.filter((doc: Doc, index: number) => index >= this._closestDropIndex && !this._selectedDocs.has(doc));
+ this.props.removeDocument?.(pushedDocs);
+ this.props.removeDocument?.(this._selectedDocSortedArray);
+ this.addDocument(this._selectedDocSortedArray);
+ this.addDocument(pushedDocs);
+ return true;
}
+ return false;
};
- @computed
- get previewDocument(): Doc | undefined {
- return this._previewDoc;
- }
-
- @computed
- get dividerDragger() {
- return this.previewWidth() === 0 ? null : (
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown}>
- <div className="collectionSchemaView-dividerDragger" />
- </div>
- );
- }
-
- @computed
- get previewPanel() {
- return (
- <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
- {!this.previewDocument ? null : (
- <DocumentView
- Document={this.previewDocument}
- DataDoc={undefined}
- fitContentsToBox={returnTrue}
- dontCenter={'y'}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={this.rootSelected}
- PanelWidth={this.previewWidth}
- PanelHeight={this.previewHeight}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- styleProvider={DefaultStyleProvider}
- docViewPath={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
- />
- )}
- </div>
- );
- }
-
- @computed
- get schemaTable() {
- return (
- <SchemaTable
- Document={this.props.Document}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- childDocs={this.childDocs}
- CollectionView={this.props.CollectionView}
- ContainingCollectionView={this.props.ContainingCollectionView}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- fieldKey={this.props.fieldKey}
- renderDepth={this.props.renderDepth}
- moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- active={this.props.isContentActive}
- onDrop={this.onExternalDrop}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- isSelected={this.props.isSelected}
- isFocused={this.isFocused}
- setFocused={this.setFocused}
- setPreviewDoc={this.setPreviewDoc}
- deleteDocument={this.props.removeDocument}
- addDocument={this.props.addDocument}
- dataDoc={this.props.DataDoc}
- columns={this.columns}
- documentKeys={this.documentKeys}
- headerIsEditing={this._headerIsEditing}
- openHeader={this.openHeader}
- onClick={this.onTableClick}
- onPointerDown={emptyFunction}
- onResizedChange={this.onResizedChange}
- setColumns={this.setColumns}
- reorderColumns={this.reorderColumns}
- changeColumns={this.changeColumns}
- setHeaderIsEditing={this.setHeaderIsEditing}
- changeColumnSort={this.setColumnSort}
- />
- );
- }
-
- @computed
- public get schemaToolbar() {
- return (
- <div className="collectionSchemaView-toolbar">
- <div className="collectionSchemaView-toolbar-item">
- <div id="preview-schema-checkbox-div">
- <input type="checkbox" key={'Show Preview'} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />
- Show Preview
- </div>
- </div>
- </div>
+ @action
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ console.log('hello');
+ super.onExternalDrop(
+ e,
+ {},
+ undoBatch(
+ action(docus => {
+ this._isDragging = false;
+ docus.map((doc: Doc) => this.addDocument(doc));
+ })
+ )
);
- }
-
- onSpecificMenu = (e: React.MouseEvent) => {
- if ((e.target as any)?.className?.includes?.('collectionSchemaView-cell') || e.target instanceof HTMLSpanElement) {
- const cm = ContextMenu.Instance;
- const options = cm.findByDescription('Options...');
- const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : [];
- optionItems.push({ description: 'remove', event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: 'trash' });
- !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' });
- cm.displayMenu(e.clientX, e.clientY);
- (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
- e.stopPropagation();
- }
};
@action
- onTableClick = (e: React.MouseEvent): void => {
- if (!(e.target as any)?.className?.includes?.('collectionSchemaView-cell') && !(e.target instanceof HTMLSpanElement)) {
- this.setPreviewDoc(undefined);
- } else {
- e.stopPropagation();
+ startDrag = (e: React.PointerEvent, doc: Doc) => {
+ if (!this._selectedDocs.has(doc)) {
+ this._selectedDocs.clear();
+ this._selectedDocs.add(doc);
+ this._lastSelectedRow = this.childDocs.indexOf(doc);
+ SelectionManager.SelectSchemaViewDoc(doc);
}
- this.setFocused(this.props.Document);
- this.closeHeader();
- };
-
- onResizedChange = (newResized: Resize[], event: any) => {
- const columns = this.columns;
- newResized.forEach(resized => {
- const index = columns.findIndex(c => c.heading === resized.id);
- const column = columns[index];
- column.setWidth(resized.value);
- columns[index] = column;
- });
- this.columns = columns;
+ this._isDragging = true;
+ this._selectedDocSortedArray = this.sortedSelectedDocs();
+ const dragData = new DragManager.DocumentDragData(this._selectedDocSortedArray, 'move');
+ dragData.moveDocument = this.props.moveDocument;
+ const dragItem: HTMLElement[] = [];
+ const dragDiv = document.createElement('div');
+ dragDiv.className = 'presItem-multiDrag';
+ dragDiv.innerText = 'Move ' + this._selectedDocs.size + ' row' + (this._selectedDocs.size > 1 ? 's' : '');
+ dragDiv.style.position = 'absolute';
+ dragDiv.style.top = e.clientY + 'px';
+ dragDiv.style.left = e.clientX - 50 + 'px';
+ dragItem.push(dragDiv);
+
+ DragManager.StartDocumentDrag(
+ dragItem.map(ele => ele),
+ dragData,
+ e.clientX,
+ e.clientY,
+ undefined
+ );
+ return true;
};
@action
- setColumns = (columns: SchemaHeaderField[]) => (this.columns = columns);
-
- @undoBatch
- reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
- const columns = [...columnsValues];
- const oldIndex = columns.indexOf(toMove);
- const relIndex = columns.indexOf(relativeTo);
- const newIndex = oldIndex > relIndex && !before ? relIndex + 1 : oldIndex < relIndex && before ? relIndex - 1 : relIndex;
+ addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
+ if (!value && !forceEmptyNote) return false;
+ const newDoc = Docs.Create.TextDocument(value, { title: value });
+ FormattedTextBox.SelectOnLoad = newDoc[Id];
+ FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' ';
+ return this.props.addDocument?.(newDoc) || false;
+ };
- if (oldIndex === newIndex) return;
+ menuCallback = (x: number, y: number) => {
+ ContextMenu.Instance.clearItems();
+ const layoutItems: ContextMenuProps[] = [];
+ const docItems: ContextMenuProps[] = [];
+ const dataDoc = this.props.DataDoc || this.props.Document;
+
+ DocUtils.addDocumentCreatorMenuItems(
+ doc => {
+ FormattedTextBox.SelectOnLoad = StrCast(doc[Id]);
+ return this.addDocument(doc);
+ },
+ this.addDocument,
+ x,
+ y,
+ true
+ );
- columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
- this.columns = columns;
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string')
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document));
+ if (created) {
+ if (this.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ }
+ return this.props.addDocument?.(created);
+ }
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ Array.from(Object.keys(Doc.GetProto(dataDoc)))
+ .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length)
+ .map(fieldKey =>
+ docItems.push({
+ description: ':' + fieldKey,
+ event: () => {
+ const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey });
+ if (created) {
+ const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document;
+ if (container.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, container);
+ return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created);
+ }
+ return this.props.addDocument?.(created) || false;
+ }
+ },
+ icon: 'compress-arrows-alt',
+ })
+ );
+ !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' });
+ !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' });
+ ContextMenu.Instance.setDefaultItem('::', (name: string): void => {
+ Doc.GetProto(this.props.Document)[name] = '';
+ const created = Docs.Create.TextDocument('', { title: name, _width: 250, _autoHeight: true });
+ if (created) {
+ if (this.props.Document.isTemplateDoc) {
+ Doc.MakeMetadataFieldTemplate(created, this.props.Document);
+ }
+ this.props.addDocument?.(created);
+ }
+ });
+ ContextMenu.Instance.displayMenu(x, y, undefined, true);
};
- onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
-
render() {
- TraceMobx();
- if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0);
- const menuContent = this.renderMenuContent;
- const menu = (
- <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ transform: `translate(${this.menuCoordinates[0]}px, ${this.menuCoordinates[1]}px)` }}>
- <Measure
- offset
- onResize={action((r: any) => {
- const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
- this._menuWidth = dim[0];
- this._menuHeight = dim[1];
- })}>
- {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
- </Measure>
- </div>
- );
return (
<div
- className={'collectionSchemaView' + (this.props.Document._searchDoc ? '-searchContainer' : '-container')}
- style={{
- overflow: this.props.scrollOverflow === true ? 'scroll' : undefined,
- backgroundColor: 'white',
- pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined,
- width: this.props.PanelWidth() || '100%',
- height: this.props.PanelHeight() || '100%',
- position: 'relative',
- }}>
- <div
- className="collectionSchemaView-tableContainer"
- style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
- onContextMenu={this.onSpecificMenu}
- onPointerDown={this.onPointerDown}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
- onDrop={e => this.onExternalDrop(e, {})}
- ref={this.createTarget}>
- {this.schemaTable}
+ className="collectionSchemaView"
+ ref={(ele: HTMLDivElement | null) => {
+ this._ref = ele;
+ this.createDashEventsTarget(ele);
+ }}
+ onPointerDown={() => this._selectedDocs.clear()}
+ onDrop={this.onExternalDrop.bind(this)}>
+ <div className="schema-table">
+ <div className="schema-header-row">
+ {this.columnKeys.map((key, index) => (
+ <SchemaColumnHeader
+ columnIndex={index}
+ columnKeys={this.columnKeys}
+ columnWidths={this.displayColumnWidths}
+ changeColumnKey={this.changeColumnKey}
+ addColumn={this.addColumn}
+ removeColumn={this.removeColumn}
+ resizeColumn={this.startResize}
+ />
+ ))}
+ </div>
+ <div className="schema-table-content">
+ {this.childDocs.map((doc: Doc, index: number) => (
+ <SchemaRowBox
+ {...this.props}
+ Document={doc}
+ ContainingCollectionDoc={this.props.CollectionView?.props.Document}
+ ContainingCollectionView={this.props.CollectionView}
+ rowIndex={index}
+ columnKeys={this.columnKeys}
+ columnWidths={this.displayColumnWidths}
+ rowMenuWidth={this._rowMenuWidth}
+ selectedRows={this._selectedDocs}
+ selectRow={this.selectRow}
+ startDrag={this.startDrag}
+ dragging={this._isDragging}
+ dropIndex={this.setDropIndex}
+ />
+ ))}
+ </div>
</div>
- {this.dividerDragger}
- {!this.previewWidth() ? null : this.previewPanel}
- {this._headerOpen && this.props.isContentActive() ? menu : null}
+ <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} placeholder={"Type ':' for commands"} contents={'+ New Node'} menuCallback={this.menuCallback} />
</div>
);
}