import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, untracked, runInAction } from "mobx";
import { observer } from "mobx-react";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
import "react-table/react-table.css";
import { emptyFunction, returnFalse, returnZero } from "../../../Utils";
import { SetupDrag } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
import { anchorPoints, Flyout } from "../DocumentDecorations";
import '../DocumentDecorations.scss';
import { EditableView } from "../EditableView";
import { DocumentView } from "../nodes/DocumentView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { Opt, Field, Doc } from "../../../new_fields/Doc";
import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { listSpec } from "../../../new_fields/Schema";
import { List } from "../../../new_fields/List";
import { Id } from "../../../new_fields/RefField";
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@observer
class KeyToggle extends React.Component<{ keyName: string, checked: boolean, toggle: (key: string) => void }> {
constructor(props: any) {
super(props);
}
render() {
return (
this.props.toggle(this.props.keyName)} />
{this.props.keyName}
);
}
}
@observer
export class CollectionSchemaView extends CollectionSubView(doc => doc) {
private _mainCont?: HTMLDivElement;
private _startSplitPercent = 0;
private DIVIDER_WIDTH = 4;
@observable _columns: Array = ["title", "data", "author"];
@observable _selectedIndex = 0;
@observable _columnsPercentage = 0;
@observable _keys: string[] = [];
@observable _newKeyName: string = "";
@computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); }
@computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); }
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
Document: rowProps.value[0],
fieldKey: rowProps.value[1],
ContainingCollectionView: this.props.CollectionView,
isSelected: returnFalse,
select: emptyFunction,
isTopMost: false,
selectOnLoad: false,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
active: returnFalse,
whenActiveChanged: emptyFunction,
PanelHeight: returnZero,
PanelWidth: returnZero,
};
let contents = (
);
let reference = React.createRef();
let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument);
let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc });
if (!res.success) return false;
const field = res.result;
doc[props.fieldKey] = field;
return true;
};
return (
{
let field = props.Document[props.fieldKey];
if (field) {
//TODO Types
// return field.ToScriptString();
return String(field);
}
return "";
}}
SetValue={(value: string) => {
let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
if (!script.compiled) {
return false;
}
return applyToDoc(props.Document, script.run);
}}
OnFillDown={(value: string) => {
let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
if (!script.compiled) {
return;
}
const run = script.run;
//TODO This should be able to be refactored to compile the script once
const val = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc));
if (val) {
val.forEach(doc => applyToDoc(doc, run));
}
}}>
);
}
private getTrProps: ComponentPropsGetterR = (state, rowInfo) => {
const that = this;
if (!rowInfo) {
return {};
}
return {
onClick: action((e: React.MouseEvent, handleOriginal: Function) => {
that.props.select(e.ctrlKey);
that._selectedIndex = rowInfo.index;
if (handleOriginal) {
handleOriginal();
}
}),
style: {
background: rowInfo.index === this._selectedIndex ? "lightGray" : "white",
//color: rowInfo.index === this._selectedIndex ? "white" : "black"
}
};
}
private createTarget = (ele: HTMLDivElement) => {
this._mainCont = ele;
super.CreateDropTarget(ele);
}
@action
toggleKey = (key: string) => {
let list = Cast(this.props.Document.schemaColumns, listSpec("string"));
if (list === undefined) {
this.props.Document.schemaColumns = list = new List([key]);
} else {
const index = list.indexOf(key);
if (index === -1) {
list.push(key);
} else {
list.splice(index, 1);
}
}
}
//toggles preview side-panel of schema
@action
toggleExpander = (event: React.ChangeEvent) => {
this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
}
@action
onDividerMove = (e: PointerEvent): void => {
let nativeWidth = this._mainCont!.getBoundingClientRect();
this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
}
@action
onDividerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
if (this._startSplitPercent === this.splitPercentage) {
this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
}
}
onDividerDown = (e: React.PointerEvent) => {
this._startSplitPercent = this.splitPercentage;
e.stopPropagation();
e.preventDefault();
document.addEventListener("pointermove", this.onDividerMove);
document.addEventListener('pointerup', this.onDividerUp);
}
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (this.props.isSelected()) e.stopPropagation();
else e.preventDefault();
}
}
onWheel = (e: React.WheelEvent): void => {
if (this.props.active()) {
e.stopPropagation();
}
}
@action
addColumn = () => {
this.columns.push(this._newKeyName);
this._newKeyName = "";
}
@action
newKeyChange = (e: React.ChangeEvent) => {
this._newKeyName = e.currentTarget.value;
}
@observable previewScript: string = "";
@action
onPreviewScriptChange = (e: React.ChangeEvent) => {
this.previewScript = e.currentTarget.value;
}
get previewDocument(): Doc | undefined {
const children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined;
return selected ? (this.previewScript ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
}
get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }
get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }
get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; }
private previewDocNativeWidth = () => Cast(this.previewDocument!.nativeWidth, "number", this.previewRegionWidth);
private previewDocNativeHeight = () => Cast(this.previewDocument!.nativeHeight, "number", this.previewRegionHeight);
private previewContentScaling = () => {
let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth);
if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) {
return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight);
}
return wscale;
}
private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling();
private previewPanelHeight = () => this.previewDocNativeHeight() * this.previewContentScaling();
get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; }
getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
- this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset,
- this.borderWidth).scale(1 / this.previewContentScaling())
@computed
get previewPanel() {
// let doc = CompileScript(this.previewScript, { this: selected }, true)();
const previewDoc = this.previewDocument;
return !previewDoc ? (null) : (
);
}
get documentKeysCheckList() {
const docs = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
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 =>
( ));
}
get tableOptionsPanel() {
return !this.props.active() ? (null) :
(
}>
);
}
@computed
get dividerDragger() {
return this.splitPercentage === 0 ? (null) :
;
}
render() {
library.add(faCog);
library.add(faPlus);
//This can't just pass FieldValue to filter because filter passes other arguments to the passed in function, which end up as default values in FieldValue
const children = (this.children || []).filter(doc => FieldValue(doc));
return (
this.onDrop(e, {})} ref={this.createTarget}>
({
Header: col,
accessor: (doc: Doc) => [doc, col],
id: col
}))}
column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
getTrProps={this.getTrProps}
/>
{this.dividerDragger}
{this.previewPanel}
{this.tableOptionsPanel}
);
}
}