aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoryipstanley <stanley_yip@brown.edu>2019-07-19 17:01:52 -0400
committeryipstanley <stanley_yip@brown.edu>2019-07-19 17:01:52 -0400
commit8c266048054dccc20851f79b08c82aa552765158 (patch)
tree19106491ede918ccafa2a3c2be4a458499e9cc67 /src
parente4ef3a04e3bd0bb20ed70181d51a9c419a00613f (diff)
parent17f53f604e0087615c2baff6cffa344771301b5e (diff)
Merge branch 'schema_view_improvements_2' of https://github.com/browngraphicslab/Dash-Web into schema_view_improvements_2
Diffstat (limited to 'src')
-rw-r--r--src/client/util/DragManager.ts13
-rw-r--r--src/client/views/EditableView.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx8
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx86
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx193
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss84
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx262
7 files changed, 499 insertions, 159 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 323908302..f9f6b05c0 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -288,6 +288,15 @@ export namespace DragManager {
[id: string]: any;
}
+ // for column dragging in schema view
+ export class ColumnDragData {
+ constructor(colKey: string) {
+ this.colKey = colKey;
+ }
+ colKey: string;
+ [id: string]: any;
+ }
+
export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) {
StartDrag([ele], dragData, downX, downY, options);
}
@@ -296,6 +305,10 @@ export namespace DragManager {
StartDrag([ele], dragData, downX, downY, options);
}
+ export function StartColumnDrag(ele: HTMLElement, dragData: ColumnDragData, downX: number, downY: number, options?:DragOptions) {
+ StartDrag([ele], dragData, downX, downY, options);
+ }
+
export let AbortDrag: () => void = emptyFunction;
function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 989fb1be9..42faedf9d 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -31,6 +31,7 @@ export interface EditableProps {
oneLine?: boolean;
editing?: boolean;
onClick?: (e: React.MouseEvent) => boolean;
+ isEditingCallback?: (isEditing: boolean) => void;
}
/**
@@ -48,6 +49,11 @@ export class EditableView extends React.Component<EditableProps> {
}
@action
+ componentWillReceiveProps(nextProps: EditableProps) {
+ this._editing = nextProps.editing ? true : false;
+ }
+
+ @action
onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Tab") {
this.props.OnTab && this.props.OnTab();
@@ -55,13 +61,16 @@ export class EditableView extends React.Component<EditableProps> {
if (!e.ctrlKey) {
if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
this._editing = false;
+ this.props.isEditingCallback && this.props.isEditingCallback(false);
}
} else if (this.props.OnFillDown) {
this.props.OnFillDown(e.currentTarget.value);
this._editing = false;
+ this.props.isEditingCallback && this.props.isEditingCallback(false);
}
} else if (e.key === "Escape") {
this._editing = false;
+ this.props.isEditingCallback && this.props.isEditingCallback(false);
}
}
@@ -69,6 +78,7 @@ export class EditableView extends React.Component<EditableProps> {
onClick = (e: React.MouseEvent) => {
if (!this.props.onClick || !this.props.onClick(e)) {
this._editing = true;
+ this.props.isEditingCallback && this.props.isEditingCallback(true);
}
e.stopPropagation();
}
@@ -85,7 +95,7 @@ export class EditableView extends React.Component<EditableProps> {
render() {
if (this._editing) {
return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
- onBlur={action(() => this._editing = false)} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
+ onBlur={action(() => {this._editing = false; this.props.isEditingCallback && this.props.isEditingCallback(false);})} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation}
style={{ display: this.props.display, fontSize: this.props.fontSize }} />;
} else {
return (
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 72d993b5a..ab027c425 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -134,7 +134,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
};
let onItemDown = (e: React.PointerEvent) => {
SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document,
- (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc), this._document[props.fieldKey] instanceof Doc ? "alias" : undefined)(e);
+ this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e);
};
let field = props.Document[props.fieldKey];
@@ -153,7 +153,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
<div className="collectionSchemaView-cellContents" ref={this.dropRef} onPointerDown={onItemDown} key={props.Document[Id]}>
<EditableView
editing={this._isEditing}
- // isEditingCallback={this.isEditingCallback}
+ isEditingCallback={this.isEditingCallback}
display={"inline"}
contents={contents}
height={Number(MAX_ROW_HEIGHT)}
@@ -229,8 +229,8 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
render() {
let reference = React.createRef<HTMLDivElement>();
let onItemDown = (e: React.PointerEvent) => {
- // (!this.props.CollectionView.props.isSelected() ? undefined :
- // SetupDrag(reference, () => props.Document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
+ (!this.props.CollectionView.props.isSelected() ? undefined :
+ SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
};
return (
<div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index d6ebaf8d8..316b3f0ff 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -8,6 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Flyout, anchorPoints } from "../DocumentDecorations";
import { ColumnType } from "./CollectionSchemaView";
import { emptyFunction } from "../../../Utils";
+import { contains } from "typescript-collections/dist/lib/arrays";
library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn);
@@ -21,6 +22,8 @@ export interface HeaderProps {
setIsEditing: (isEditing: boolean) => void;
deleteColumn: (column: string) => void;
setColumnType: (key: string, type: ColumnType) => void;
+ setColumnSort: (key: string, desc: boolean) => void;
+ removeColumnSort: (key: string) => void;
}
export class CollectionSchemaHeader extends React.Component<HeaderProps> {
@@ -43,6 +46,8 @@ export class CollectionSchemaHeader extends React.Component<HeaderProps> {
deleteColumn={this.props.deleteColumn}
onlyShowOptions={false}
setColumnType={this.props.setColumnType}
+ setColumnSort={this.props.setColumnSort}
+ removeColumnSort={this.props.removeColumnSort}
/>
</div>
);
@@ -59,18 +64,10 @@ export interface AddColumnHeaderProps {
@observer
export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
- // @observable private _creatingColumn: boolean = false;
-
- // @action
- // onClick = (e: React.MouseEvent): void => {
- // this._creatingColumn = true;
- // }
-
render() {
let addButton = <button><FontAwesomeIcon icon="plus" size="sm" /></button>;
return (
<div className="collectionSchemaView-header-addColumn" >
- {/* {this._creatingColumn ? <></> : */}
<CollectionSchemaColumnMenu
keyValue=""
possibleKeys={this.props.possibleKeys}
@@ -84,6 +81,8 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe
deleteColumn={action(emptyFunction)}
onlyShowOptions={true}
setColumnType={action(emptyFunction)}
+ setColumnSort={action(emptyFunction)}
+ removeColumnSort={action(emptyFunction)}
/>
</div>
);
@@ -105,12 +104,36 @@ export interface ColumnMenuProps {
deleteColumn: (column: string) => void;
onlyShowOptions: boolean;
setColumnType: (key: string, type: ColumnType) => void;
+ setColumnSort: (key: string, desc: boolean) => void;
+ removeColumnSort: (key: string) => void;
}
@observer
export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> {
@observable private _isOpen: boolean = false;
+ @observable private _node : HTMLDivElement | null = null;
+ // @observable private _node = React.createRef<HTMLDivElement>();
+ @observable private _test = "test";
+
+ componentDidMount() {
+ document.addEventListener("pointerdown", this.detectClick);
+ console.log("did mount", this._node);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("pointerdown", this.detectClick);
+ }
- @action toggleIsOpen = (): void => {
+ detectClick = (e: PointerEvent): void => {
+ console.log("click", this);
+ if (this._node && this._node.contains(e.target as Node)) {
+ } else {
+ this._isOpen = false;
+ this.props.setIsEditing(false);
+ }
+ }
+
+ @action
+ toggleIsOpen = (): void => {
this._isOpen = !this._isOpen;
this.props.setIsEditing(this._isOpen);
}
@@ -121,6 +144,14 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
this.props.setColumnType(this.props.keyValue, type);
}
+ @action
+ setNode = (node: HTMLDivElement): void => {
+ if (node) {
+ this._node = node;
+ console.log("set node to ", this._node);
+ }
+ }
+
renderTypes = () => {
if (this.props.typeConst) return <></>;
return (
@@ -147,6 +178,19 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
);
}
+ renderSorting = () => {
+ return (
+ <div className="collectionSchema-headerMenu-group">
+ <label>Sort by:</label>
+ <div className="columnMenu-sort">
+ <div className="columnMenu-option" onClick={() => this.props.setColumnSort(this.props.keyValue, false)}>Sort ascending</div>
+ <div className="columnMenu-option" onClick={() => this.props.setColumnSort(this.props.keyValue, true)}>Sort descending</div>
+ <div className="columnMenu-option" onClick={() => this.props.removeColumnSort(this.props.keyValue)}>Clear sorting</div>
+ </div>
+ </div>
+ );
+ }
+
renderContent = () => {
return (
<div className="collectionSchema-header-menuOptions">
@@ -159,11 +203,13 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
canAddNew={true}
addNew={this.props.addNew}
onSelect={this.props.onSelect}
+ setIsEditing={this.props.setIsEditing}
/>
</div>
{this.props.onlyShowOptions ? <></> :
<>
{this.renderTypes()}
+ {this.renderSorting()}
<div className="collectionSchema-headerMenu-group">
<button onClick={() => this.props.deleteColumn(this.props.keyValue)}>Delete Column</button>
</div>
@@ -174,21 +220,17 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
}
render() {
+ console.log("render", this._node);
return (
- <div className="collectionSchema-header-menu">
+ <div className="collectionSchema-header-menu" ref={this.setNode}>
<Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderContent()}>
- <div className="collectionSchema-header-toggler" onClick={() => { this.props.setIsEditing(true); }}>{this.props.menuButtonContent}</div>
+ <div className="collectionSchema-header-toggler" onClick={() => this.toggleIsOpen()}>{this.props.menuButtonContent}</div>
</ Flyout >
</div>
);
}
}
-{/* // <div className="collectionSchema-header-menu">
- // <div className="collectionSchema-header-toggler" onClick={() => this.toggleIsOpen()}>{this.props.menuButtonContent}</div>
- // {this.renderContent()}
- // </div> */}
-
interface KeysDropdownProps {
keyValue: string;
@@ -197,6 +239,7 @@ interface KeysDropdownProps {
canAddNew: boolean;
addNew: boolean;
onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
+ setIsEditing: (isEditing: boolean) => void;
}
@observer
class KeysDropdown extends React.Component<KeysDropdownProps> {
@@ -214,6 +257,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
this.props.onSelect(this._key, key, this.props.addNew);
this.setKey(key);
this._isOpen = false;
+ this.props.setIsEditing(false);
}
onChange = (val: string): void => {
@@ -223,15 +267,15 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
@action
onFocus = (e: React.FocusEvent): void => {
this._isOpen = true;
+ this.props.setIsEditing(true);
}
@action
onBlur = (e: React.FocusEvent): void => {
- // const that = this;
- if (this._canClose) this._isOpen = false;
- // setTimeout(function() { // TODO: this might be too hacky lol
- // that.setIsOpen(false);
- // }, 100);
+ if (this._canClose) {
+ this._isOpen = false;
+ this.props.setIsEditing(false);
+ }
}
@action
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
new file mode 100644
index 000000000..3a61881a7
--- /dev/null
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -0,0 +1,193 @@
+import React = require("react");
+import { ReactTableDefaults, TableCellRenderer, ComponentPropsGetterR, ComponentPropsGetter0 } from "react-table";
+import "./CollectionSchemaView.scss";
+import { Transform } from "../../util/Transform";
+import { Doc } from "../../../new_fields/Doc";
+import { DragManager, SetupDrag } from "../../util/DragManager";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Cast, FieldValue } from "../../../new_fields/Types";
+
+
+export interface MovableColumnProps {
+ columnRenderer: TableCellRenderer;
+ columnValue: string;
+ allColumns: string[];
+ reorderColumns: (toMove: string, relativeTo: string, before: boolean, columns: string[]) => void;
+ ScreenToLocalTransform: () => Transform;
+}
+export class MovableColumn extends React.Component<MovableColumnProps> {
+ private _header?: React.RefObject<HTMLDivElement> = React.createRef();
+ private _colDropDisposer?: DragManager.DragDropDisposer;
+
+ onPointerEnter = (e: React.PointerEvent): void => {
+ if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+ this._header!.current!.className = "collectionSchema-col-wrapper";
+ document.addEventListener("pointermove", this.onDragMove, true);
+ }
+ }
+ onPointerLeave = (e: React.PointerEvent): void => {
+ this._header!.current!.className = "collectionSchema-col-wrapper";
+ document.removeEventListener("pointermove", this.onDragMove, true);
+ }
+ onDragMove = (e: PointerEvent): void => {
+ let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ let rect = this._header!.current!.getBoundingClientRect();
+ let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ let before = x[0] < bounds[0];
+ this._header!.current!.className = "collectionSchema-col-wrapper";
+ if (before) this._header!.current!.className += " col-before";
+ if (!before) this._header!.current!.className += " col-after";
+ e.stopPropagation();
+ }
+
+ createColDropTarget = (ele: HTMLDivElement) => {
+ this._colDropDisposer && this._colDropDisposer();
+ if (ele) {
+ this._colDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.colDrop.bind(this) } });
+ }
+ }
+
+ colDrop = (e: Event, de: DragManager.DropEvent) => {
+ document.removeEventListener("pointermove", this.onDragMove, true);
+ let x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ let rect = this._header!.current!.getBoundingClientRect();
+ let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ let before = x[0] < bounds[0];
+ if (de.data instanceof DragManager.ColumnDragData) {
+ this.props.reorderColumns(de.data.colKey, this.props.columnValue, before, this.props.allColumns);
+ return true;
+ }
+ return false;
+ }
+
+ setupDrag (ref: React.RefObject<HTMLElement>) {
+ let onRowMove = (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointerup', onRowUp);
+ let dragData = new DragManager.ColumnDragData(this.props.columnValue);
+ DragManager.StartColumnDrag(ref.current!, dragData, e.x, e.y);
+ };
+ let onRowUp = (): void => {
+ document.removeEventListener("pointermove", onRowMove);
+ document.removeEventListener('pointerup', onRowUp);
+ };
+ let onItemDown = (e: React.PointerEvent) => {
+ if (e.button === 0) {
+ e.stopPropagation();
+ document.addEventListener("pointermove", onRowMove);
+ document.addEventListener("pointerup", onRowUp);
+ }
+ };
+ return onItemDown;
+ }
+
+
+ render() {
+ let reference = React.createRef<HTMLDivElement>();
+ let onItemDown = this.setupDrag(reference);
+
+ return (
+ <div className="collectionSchema-col" ref={this.createColDropTarget}>
+ <div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ <div className="col-dragger" ref={reference} onPointerDown={onItemDown}>
+ {this.props.columnRenderer}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+export interface MovableRowProps {
+ ScreenToLocalTransform: () => Transform;
+ addDoc: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
+ moveDoc: DragManager.MoveFunction;
+}
+
+export class MovableRow extends React.Component<MovableRowProps> {
+ private _header?: React.RefObject<HTMLDivElement> = React.createRef();
+ private _rowDropDisposer?: DragManager.DragDropDisposer;
+
+ onPointerEnter = (e: React.PointerEvent): void => {
+ if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+ this._header!.current!.className = "collectionSchema-row-wrapper";
+ document.addEventListener("pointermove", this.onDragMove, true);
+ }
+ }
+ onPointerLeave = (e: React.PointerEvent): void => {
+ this._header!.current!.className = "collectionSchema-row-wrapper";
+ document.removeEventListener("pointermove", this.onDragMove, true);
+ }
+ onDragMove = (e: PointerEvent): void => {
+ let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ let rect = this._header!.current!.getBoundingClientRect();
+ let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
+ let before = x[1] < bounds[1];
+ this._header!.current!.className = "collectionSchema-row-wrapper";
+ if (before) this._header!.current!.className += " row-above";
+ if (!before) this._header!.current!.className += " row-below";
+ e.stopPropagation();
+ }
+
+ createRowDropTarget = (ele: HTMLDivElement) => {
+ this._rowDropDisposer && this._rowDropDisposer();
+ if (ele) {
+ this._rowDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } });
+ }
+ }
+
+ rowDrop = (e: Event, de: DragManager.DropEvent) => {
+ const { children = null, rowInfo } = this.props;
+ if (!rowInfo) return false;
+
+ const { original } = rowInfo;
+ const rowDoc = FieldValue(Cast(original, Doc));
+ if (!rowDoc) return false;
+
+ let x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ let rect = this._header!.current!.getBoundingClientRect();
+ let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
+ let before = x[1] < bounds[1];
+ if (de.data instanceof DragManager.DocumentDragData) {
+ e.stopPropagation();
+ if (de.data.draggedDocuments[0] === rowDoc) return true;
+ let addDocument = (doc: Doc) => this.props.addDoc(doc, rowDoc, before);
+ let movedDocs = de.data.draggedDocuments; //(de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
+ return (de.data.dropAction || de.data.userDropAction) ?
+ de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false)
+ : (de.data.moveDocument) ?
+ movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, rowDoc, addDocument) || added, false)
+ : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false);
+ }
+ return false;
+ }
+
+ render() {
+ const { children = null, rowInfo } = this.props;
+ if (!rowInfo) {
+ return <ReactTableDefaults.TrComponent>{children}</ReactTableDefaults.TrComponent>;
+ }
+
+ const { original } = rowInfo;
+ const doc = FieldValue(Cast(original, Doc));
+ if (!doc) return <></>;
+
+ let reference = React.createRef<HTMLDivElement>();
+ let onItemDown = SetupDrag(reference, () => doc, this.props.moveDoc);
+
+ return (
+ <div className="collectionSchema-row" ref={this.createRowDropTarget}>
+ <div className="collectionSchema-row-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ <div className="row-dragger" ref={reference} onPointerDown={onItemDown}>
+ <ReactTableDefaults.TrComponent>
+ {children}
+ </ReactTableDefaults.TrComponent>
+ </div>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 4bc7a778c..4ae9628a9 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -105,6 +105,14 @@
direction: ltr;
max-height: $MAX_ROW_HEIGHT;
+ &:nth-child(even) {
+ background-color: $light-color;
+ }
+
+ &:nth-child(odd) {
+ background-color: $light-color-secondary;
+ }
+
&:last-child {
border-bottom: $intermediate-color;
border-bottom-style: solid;
@@ -112,6 +120,10 @@
}
}
+ .rt-tr {
+ width: 100%;
+ }
+
.rt-td {
border-width: 1px;
border-right-color: $intermediate-color;
@@ -149,6 +161,20 @@
background: $light-color;
}
+.collectionSchema-col{
+ height: 100%;
+
+ .collectionSchema-col-wrapper {
+ &.col-before {
+ border-left: 2px solid red;
+ }
+ &.col-after {
+ border-right: 2px solid red;
+ }
+ }
+}
+
+
.collectionSchemaView-header {
height: 100%;
@@ -168,16 +194,8 @@
}
.collectionSchema-header-menuOptions {
- // position: absolute;
- // top: 30px;
- // left: 50%;
- // transform: translateX(-50%);
- // z-index: 9999;
- // background-color: $light-color-secondary;
color: black;
- // border: 1px solid $main-accent;
width: 175px;
- // padding: 10px;
text-align: left;
.collectionSchema-headerMenu-group {
@@ -235,15 +253,51 @@
}
}
-#preview-schema-checkbox-div {
- margin-left: 20px;
- font-size: 12px;
+.collectionSchema-row {
+ height: $MAX_ROW_HEIGHT;
+ // display: flex;
+
+ .row-dragger {
+ height: $MAX_ROW_HEIGHT;
+ }
+
+ .collectionSchema-row-wrapper {
+ max-height: $MAX_ROW_HEIGHT;
+ // width: 100%;
+ // border: 1px solid lightgray;
+
+ &.row-above {
+ border-top: 1px solid red;
+ }
+ &.row-below {
+ border-bottom: 1px solid red;
+ }
+ &.row-inside {
+ border: 1px solid red;
+ }
+ }
}
-.-even {
- background: $light-color !important;
+
+.collectionSchemaView-cellWrapper {
+ // height: $MAX_ROW_HEIGHT;
+ // background-color: red;
+ height: 100%;
+ padding: 4px;
+
+ &.focused {
+ // background-color: yellowgreen;
+ border: 2px solid yellowgreen;
+
+ input {
+ outline: 0;
+ border: none;
+ background-color: yellow;
+ }
+ }
}
-.-odd {
- background: $light-color-secondary !important;
+#preview-schema-checkbox-div {
+ margin-left: 20px;
+ font-size: 12px;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 3ef58bcaf..ffc9d7d09 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -4,7 +4,7 @@ import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, trace, untracked } from "mobx";
import { observer } from "mobx-react";
-import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults, TableCellRenderer } from "react-table";
+import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults, TableCellRenderer, Column } from "react-table";
import "react-table/react-table.css";
import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
import { Doc, DocListCast, DocListCastAsync, Field, FieldResult } from "../../../new_fields/Doc";
@@ -33,7 +33,7 @@ import { undoBatch } from "../../util/UndoManager";
import { timesSeries } from "async";
import { CollectionSchemaHeader, CollectionSchemaAddColumnHeader } from "./CollectionSchemaHeaders";
import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaBooleanCell, CollectionSchemaCheckboxCell } from "./CollectionSchemaCells";
-
+import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
library.add(faCog);
library.add(faPlus);
@@ -54,89 +54,80 @@ const columnTypes: Map<string, ColumnType> = new Map([
["page", ColumnType.Number], ["curPage", ColumnType.Number], ["libraryBrush", ColumnType.Boolean], ["zIndex", ColumnType.Number]
]);
-// @observer
-// class KeyToggle extends React.Component<{ keyName: string, checked: boolean, toggle: (key: string) => void }> {
-// constructor(props: any) {
-// super(props);
-// }
-
-// render() {
-// return (
-// <div key={this.props.keyName}>
-// <input type="checkbox" checked={this.props.checked} onChange={() => this.props.toggle(this.props.keyName)} />
-// {this.props.keyName}
-// </div>
-// );
-// }
-// }
-
@observer
export class CollectionSchemaView extends CollectionSubView(doc => doc) {
private _mainCont?: HTMLDivElement;
private _startPreviewWidth = 0;
private DIVIDER_WIDTH = 4;
- @observable _columns: Array<string> = ["title", "data", "author"];
@observable _selectedIndex = 0;
@observable _columnsPercentage = 0;
@observable _keys: string[] = [];
@observable _newKeyName: string = "";
@observable previewScript: string = "";
@observable _headerIsEditing: boolean = false;
+ @observable _cellIsEditing: boolean = false;
+ @observable _focusedCell: {row: number, col: number} = {row: 0, col: 0};
+ @observable _sortedColumns: Map<string, {id: string, desc: boolean}> = new Map();
@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 - this.DIVIDER_WIDTH - this.previewWidth(); }
@computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); }
- set columns(columns: string[]) { this.props.Document.schemaColumns = new List<string>(columns); }
+ set columns(columns: string[]) {this.props.Document.schemaColumns = new List<string>(columns); }
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
- @computed get tableColumns() {
+ @computed get tableColumns(): Column<Doc>[] {
let possibleKeys = this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.toUpperCase() === key.toUpperCase()) === -1);
-
let cols = this.columns.map(col => {
+ let focusedRow = this._focusedCell.row;
+ let focusedCol = this._focusedCell.col;
+ let isEditable = !this._headerIsEditing;
+ let header = <CollectionSchemaHeader
+ keyValue={col}
+ possibleKeys={possibleKeys}
+ existingKeys={this.columns}
+ keyType={this.getColumnType(col)}
+ typeConst={columnTypes.get(col) !== undefined}
+ onSelect={this.changeColumns}
+ setIsEditing={this.setHeaderIsEditing}
+ deleteColumn={this.deleteColumn}
+ setColumnType={this.setColumnType}
+ setColumnSort={this.setColumnSort}
+ removeColumnSort={this.removeColumnSort}
+ />;
+
return {
- Header: <CollectionSchemaHeader
- keyValue={col}
- possibleKeys={possibleKeys}
- existingKeys={this.columns}
- keyType={this.getColumnType(col)}
- typeConst={columnTypes.get(col) !== undefined}
- onSelect={this.changeColumns}
- setIsEditing={this.setHeaderIsEditing}
- deleteColumn={this.deleteColumn}
- setColumnType={this.setColumnType}
- />,
+ Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.columns} reorderColumns={this.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />,
accessor: (doc: Doc) => doc ? doc[col] : 0,
id: col,
Cell: (rowProps: CellInfo) => {
let row = rowProps.index;
let column = this.columns.indexOf(rowProps.column.id!);
- // let isFocused = focusedRow === row && focusedCol === column;
- let isFocused = false;
+ let isFocused = focusedRow === row && focusedCol === column;
let props: CellProps = {
row: row,
col: column,
rowProps: rowProps,
isFocused: isFocused,
- changeFocusedCellByDirection: action(emptyFunction),//this.changeFocusedCellByDirection,
- changeFocusedCellByIndex: action(emptyFunction), //this.changeFocusedCellByIndex,
+ changeFocusedCellByDirection: this.changeFocusedCellByDirection,
+ changeFocusedCellByIndex: this.changeFocusedCellByIndex,
CollectionView: this.props.CollectionView,
ContainingCollection: this.props.ContainingCollectionView,
Document: this.props.Document,
fieldKey: this.props.fieldKey,
renderDepth: this.props.renderDepth, addDocTab: this.props.addDocTab,
moveDocument: this.props.moveDocument,
- setIsEditing: action(emptyFunction), //this.setCellIsEditing,
- isEditable: true //isEditable
+ setIsEditing: this.setCellIsEditing,
+ isEditable: isEditable
};
let colType = this.getColumnType(col);
- if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props}/>
- if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props}/>
- if (colType === ColumnType.Boolean) return <CollectionSchemaBooleanCell {...props} />
- if (colType === ColumnType.Checkbox) return <CollectionSchemaCheckboxCell {...props} />
- return <CollectionSchemaCell {...props}/>
+ if (colType === ColumnType.Number) return <CollectionSchemaNumberCell {...props}/>;
+ if (colType === ColumnType.String) return <CollectionSchemaStringCell {...props}/>;
+ if (colType === ColumnType.Boolean) return <CollectionSchemaBooleanCell {...props} />;
+ if (colType === ColumnType.Checkbox) return <CollectionSchemaCheckboxCell {...props} />;
+ return <CollectionSchemaCell {...props}/>;
}
};
}) as {Header: TableCellRenderer, accessor: (doc: Doc) => FieldResult<Field>, id: string, Cell: (rowProps: CellInfo) => JSX.Element}[];
@@ -156,18 +147,26 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return cols;
}
- onHeaderDrag = (columnName: string) => {
- let schemaDoc = Cast(this.props.Document.schemaDoc, Doc);
- if (schemaDoc instanceof Doc) {
- let columnDocs = DocListCast(schemaDoc.data);
- if (columnDocs) {
- let ddoc = columnDocs.find(doc => doc.title === columnName);
- if (ddoc) {
- return ddoc;
- }
- }
- }
- return this.props.Document;
+ // onHeaderDrag = (columnName: string) => {
+ // let schemaDoc = Cast(this.props.Document.schemaDoc, Doc);
+ // if (schemaDoc instanceof Doc) {
+ // let columnDocs = DocListCast(schemaDoc.data);
+ // if (columnDocs) {
+ // let ddoc = columnDocs.find(doc => doc.title === columnName);
+ // if (ddoc) {
+ // return ddoc;
+ // }
+ // }
+ // }
+ // return this.props.Document;
+ // }
+
+ componentDidMount() {
+ document.addEventListener("keydown", this.onKeyDown);
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener("keydown", this.onKeyDown);
}
private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
@@ -176,6 +175,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return {};
}
return {
+ ScreenToLocalTransform: this.props.ScreenToLocalTransform,
+ addDoc: (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before),
+ moveDoc: (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc),
+ rowInfo,
onClick: action((e: React.MouseEvent, handleOriginal: Function) => {
that.props.select(e.ctrlKey);
that._selectedIndex = rowInfo.index;
@@ -184,10 +187,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
handleOriginal();
}
}),
- style: {
- background: rowInfo.index === this._selectedIndex ? "lightGray" : "white",
- //color: rowInfo.index === this._selectedIndex ? "white" : "black"
- }
};
}
@@ -197,23 +196,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
@action
- setHeaderIsEditing = (isEditing: boolean) => {
- this._headerIsEditing = isEditing;
+ setCellIsEditing = (isEditing: boolean): void => {
+ this._cellIsEditing = isEditing;
}
@action
- toggleKey = (key: string) => {
- let list = Cast(this.props.Document.schemaColumns, listSpec("string"));
- if (list === undefined) {
- this.props.Document.schemaColumns = list = new List<string>([key]);
- } else {
- const index = list.indexOf(key);
- if (index === -1) {
- list.push(key);
- } else {
- list.splice(index, 1);
- }
- }
+ setHeaderIsEditing = (isEditing: boolean): void => {
+ this._headerIsEditing = isEditing;
}
//toggles preview side-panel of schema
@@ -262,6 +251,45 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
}
+ onKeyDown = (e: KeyboardEvent): void => {
+ if (!this._cellIsEditing && !this._headerIsEditing) {
+ let direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : "";
+ this.changeFocusedCellByDirection(direction);
+ }
+ }
+
+ @action
+ changeFocusedCellByDirection = (direction: string): void => {
+ switch (direction) {
+ case "tab":
+ if (this._focusedCell.col + 1 === this.columns.length && this._focusedCell.row + 1 === this.childDocs.length) {
+ this._focusedCell = { row: 0, col: 0 };
+ } else if (this._focusedCell.col + 1 === this.columns.length) {
+ this._focusedCell = { row: this._focusedCell.row + 1, col: 0 };
+ } else {
+ this._focusedCell = { row: this._focusedCell.row, col: this._focusedCell.col + 1 };
+ }
+ break;
+ case "right":
+ this._focusedCell = { row: this._focusedCell.row, col: this._focusedCell.col + 1 === this.columns.length ? this._focusedCell.col : this._focusedCell.col + 1 };
+ break;
+ case "left":
+ this._focusedCell = { row: this._focusedCell.row, col: this._focusedCell.col === 0 ? this._focusedCell.col : this._focusedCell.col - 1 };
+ break;
+ case "up":
+ this._focusedCell = { row: this._focusedCell.row === 0 ? this._focusedCell.row : this._focusedCell.row - 1, col: this._focusedCell.col };
+ break;
+ case "down":
+ this._focusedCell = { row: this._focusedCell.row + 1 === this.childDocs.length ? this._focusedCell.row : this._focusedCell.row + 1, col: this._focusedCell.col };
+ break;
+ }
+ }
+
+ @action
+ changeFocusedCellByIndex = (row: number, col: number): void => {
+ this._focusedCell = { row: row, col: col };
+ }
+
@action
makeDB = async () => {
let csv: string = this.columns.reduce((val, col) => val + col + ",", "");
@@ -333,17 +361,38 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
let newTypesDoc = new Doc();
newTypesDoc[key] = type;
this.props.Document.schemaColumnTypes = newTypesDoc;
- console.log("no typesDoc");
return;
} else {
typesDoc[key] = type;
}
}
- // @action
- // newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- // this._newKeyName = e.currentTarget.value;
- // }
+ @action
+ setColumns = (columns: string[]) => {
+ this.columns = columns;
+ }
+
+ reorderColumns = (toMove: string, relativeTo: string, before: boolean, columnsValues: string[]) => {
+ let columns = [...columnsValues];
+ let oldIndex = columns.indexOf(toMove);
+ let relIndex = columns.indexOf(relativeTo);
+ let newIndex = (oldIndex > relIndex && !before) ? relIndex + 1 : (oldIndex < relIndex && before) ? relIndex - 1 : relIndex;
+
+ if (oldIndex === newIndex) return;
+
+ columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
+ this.setColumns(columns);
+ }
+
+ @action
+ setColumnSort = (column: string, descending: boolean) => {
+ this._sortedColumns.set(column, {id: column, desc: descending});
+ }
+
+ @action
+ removeColumnSort = (column: string) => {
+ this._sortedColumns.delete(column);
+ }
@computed
get previewDocument(): Doc | undefined {
@@ -370,44 +419,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
return Array.from(Object.keys(keys));
}
- // get documentKeysCheckList() {
- // const docs = DocListCast(this.props.Document[this.props.fieldKey]);
- // let 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] = true);
- // return Array.from(Object.keys(keys)).map(item =>
- // (<KeyToggle checked={keys[item]} key={item} keyName={item} toggle={this.toggleKey} />));
- // }
-
- // get tableOptionsPanel() {
- // return !this.props.active() ? (null) :
- // (<Flyout
- // anchorPoint={anchorPoints.RIGHT_TOP}
- // content={<div>
- // <div id="schema-options-header"><h5><b>Options</b></h5></div>
- // <div id="options-flyout-div">
- // <h6 className="schema-options-subHeader">Preview Window</h6>
- // <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} /> Show Preview </div>
- // <h6 className="schema-options-subHeader" >Displayed Columns</h6>
- // <ul id="schema-col-checklist" >
- // {this.documentKeysCheckList}
- // </ul>
- // <input value={this._newKeyName} onChange={this.newKeyChange} />
- // <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
- // </div>
- // </div>
- // }>
- // <button id="schemaOptionsMenuBtn" ><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
- // </Flyout>);
- // }
-
@computed
get reactTable() {
let previewWidth = this.previewWidth() + 2 * this.borderWidth + this.DIVIDER_WIDTH + 1;
@@ -418,9 +429,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
pageSize={this.childDocs.length}
showPagination={false}
columns={this.tableColumns}
- // column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
getTrProps={this.getTrProps}
sortable={false}
+ TrComponent={MovableRow}
+ sorted={Array.from(this._sortedColumns.values())}
/>;
}
@@ -468,10 +480,20 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
this.previewScript = script;
}
+ @computed
+ get schemaToolbar() {
+ return (
+ <div className="collectionSchemaView-toolbar">
+ <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />Show Preview</div>
+ </div>
+ );
+ }
+
render() {
return (
<div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
onDrop={(e: React.DragEvent) => this.onDrop(e, {})} onContextMenu={this.onContextMenu} ref={this.createTarget}>
+ {this.schemaToolbar}
{this.reactTable}
{this.dividerDragger}
{!this.previewWidth() ? (null) : this.previewPanel}
@@ -480,6 +502,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
);
}
}
+
+
interface CollectionSchemaPreviewProps {
Document?: Doc;
DataDocument?: Doc;
@@ -554,6 +578,8 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
}
return undefined;
}
+
+
render() {
let input = this.props.previewScript === undefined ? (null) :
<div ref={this.createTarget}><input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange}