From 57458679ffe87302b46c64ffaa05f967c84623df Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 25 Aug 2020 14:42:15 -0400 Subject: major rewrite of schema table and schem cells. lists/docs work now. dates display better. resizing is better and faster (but not fast). --- src/client/views/EditableView.tsx | 111 ++-- .../views/collections/CollectionSchemaCells.tsx | 686 +++++---------------- src/client/views/collections/CollectionView.tsx | 7 +- src/client/views/collections/SchemaTable.tsx | 30 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- src/fields/DateField.ts | 4 +- src/fields/List.ts | 2 +- src/fields/util.ts | 11 +- 8 files changed, 232 insertions(+), 622 deletions(-) (limited to 'src') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d0e6ed463..8b2fa9472 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -52,10 +52,6 @@ export interface EditableProps { color?: string | undefined; onDrop?: any; placeholder?: string; - highlight?: boolean; - positions?: number[]; - search?: string; - bing?: () => string | undefined; } /** @@ -94,25 +90,30 @@ export class EditableView extends React.Component { @action onKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Tab") { - e.stopPropagation(); - this.finalizeEdit(e.currentTarget.value, e.shiftKey, false); - this.props.OnTab && this.props.OnTab(e.shiftKey); - } else if (e.key === "Enter") { - e.stopPropagation(); - if (!e.ctrlKey) { + switch (e.key) { + case "Tab": + e.stopPropagation(); this.finalizeEdit(e.currentTarget.value, e.shiftKey, false); - } else if (this.props.OnFillDown) { - this.props.OnFillDown(e.currentTarget.value); + this.props.OnTab && this.props.OnTab(e.shiftKey); + break; + case "Enter": + e.stopPropagation(); + if (!e.ctrlKey) { + this.finalizeEdit(e.currentTarget.value, e.shiftKey, false); + } else if (this.props.OnFillDown) { + this.props.OnFillDown(e.currentTarget.value); + this._editing = false; + this.props.isEditingCallback?.(false); + } + break; + case "Escape": + e.stopPropagation(); this._editing = false; this.props.isEditingCallback?.(false); - } - } else if (e.key === "Escape") { - e.stopPropagation(); - this._editing = false; - this.props.isEditingCallback?.(false); - } else if (e.key === ":") { - this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); + break; + case ":": + this.props.menuCallback?.(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); + break; } } @@ -121,11 +122,9 @@ export class EditableView extends React.Component { e.nativeEvent.stopPropagation(); if (this._ref.current && this.props.showMenuOnLoad) { this.props.menuCallback?.(this._ref.current.getBoundingClientRect().x, this._ref.current.getBoundingClientRect().y); - } else { - if (!this.props.onClick?.(e)) { - this._editing = true; - this.props.isEditingCallback?.(true); - } + } else if (!this.props.onClick?.(e)) { + this._editing = true; + this.props.isEditingCallback?.(true); } e.stopPropagation(); } @@ -178,10 +177,7 @@ export class EditableView extends React.Component { defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus={true} - onKeyPress={e => { - e.stopPropagation(); - (e.nativeEvent as any).Aaaa = 3; - }} + onKeyPress={e => e.stopPropagation()} onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true)} onPointerDown={this.stopPropagation} onClick={this.stopPropagation} onPointerUp={this.stopPropagation} style={{ display: this.props.display, fontSize: this.props.fontSize, minWidth: 20 }} @@ -189,54 +185,25 @@ export class EditableView extends React.Component { />; } - returnHighlights() { - const results = []; - const contents = this.props.bing!(); - - if (contents !== undefined) { - if (this.props.positions !== undefined) { - const positions = this.props.positions; - const length = this.props.search!.length; - - // contents = String(this.props.contents.valueOf()); - - results.push({contents ? contents.slice(0, this.props.positions[0]) : this.props.placeholder?.valueOf()}); - positions.forEach((num, cur) => { - results.push({contents ? contents.slice(num, num + length) : this.props.placeholder?.valueOf()}); - let end = 0; - cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; - results.push({contents ? contents.slice(num + length, end) : this.props.placeholder?.valueOf()}); - } - ); - } - return results; - } - else { - return {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}; - } - } - render() { if (this._editing && this.props.GetValue() !== undefined) { return this.props.sizeToContent ?
-
{this.props.GetValue()}
+
+ {this.props.GetValue()} +
{this.renderEditor()} -
: this.renderEditor(); - } else { - setTimeout(() => this.props.autosuggestProps?.resetValue(), 0); - return (this.props.contents instanceof ObjectField ? (null) : -
- {this.props.highlight === undefined || this.props.positions === undefined || this.props.bing === undefined ? - {//}, color: this.props.contents ? "black" : "grey" }}>{ - this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()} - - : this.returnHighlights()} -
- ); + : + this.renderEditor(); } + setTimeout(() => this.props.autosuggestProps?.resetValue(), 0); + return this.props.contents instanceof ObjectField ? (null) : +
+ { + this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()} + +
; } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index ac789ab48..95f425524 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -10,27 +10,24 @@ import { DateField } from "../../../fields/DateField"; import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; -import { RichTextField } from "../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ComputedField } from "../../../fields/ScriptField"; import { BoolCast, Cast, DateCast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero, Utils } from "../../../Utils"; +import { Utils } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; -import { DragManager, SetupDrag } from "../../util/DragManager"; +import { DragManager } from "../../util/DragManager"; import { KeyCodes } from "../../util/KeyCodes"; import { CompileScript } from "../../util/Scripting"; import { SearchUtil } from "../../util/SearchUtil"; import { SnappingManager } from "../../util/SnappingManager"; -import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import '../DocumentDecorations.scss'; import { EditableView } from "../EditableView"; -import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../globalCssVariables.scss'; +import { MAX_ROW_HEIGHT } from '../globalCssVariables.scss'; import { DocumentIconContainer } from "../nodes/DocumentIcon"; -import { FieldViewProps } from "../nodes/FieldView"; import { OverlayView } from "../OverlayView"; import "./CollectionSchemaView.scss"; import { CollectionView } from "./CollectionView"; @@ -61,20 +58,24 @@ export interface CellProps { @observer export class CollectionSchemaCell extends React.Component { + public static resolvedFieldKey(column: string, rowDoc: Doc) { + const fieldKey = column; + if (fieldKey.startsWith("*")) { + const rootKey = fieldKey.substring(1); + const allKeys = [...Array.from(Object.keys(rowDoc)), ...Array.from(Object.keys(Doc.GetProto(rowDoc)))]; + const matchedKeys = allKeys.filter(key => key.includes(rootKey)); + if (matchedKeys.length) return matchedKeys[0]; + } + return fieldKey; + } @observable protected _isEditing: boolean = false; protected _focusRef = React.createRef(); - protected _document = this.props.rowProps.original; + protected _rowDoc = this.props.rowProps.original; protected _dropDisposer?: DragManager.DragDropDisposer; - - async componentDidMount() { - document.addEventListener("keydown", this.onKeyDown); - } - @observable contents: string = ""; - componentWillUnmount() { - document.removeEventListener("keydown", this.onKeyDown); - } + componentDidMount() { document.addEventListener("keydown", this.onKeyDown); } + componentWillUnmount() { document.removeEventListener("keydown", this.onKeyDown); } @action onKeyDown = (e: KeyboardEvent): void => { @@ -96,7 +97,7 @@ export class CollectionSchemaCell extends React.Component { @action onPointerDown = async (e: React.PointerEvent): Promise => { - + this.onItemDown(e); this.props.changeFocusedCellByIndex(this.props.row, this.props.col); this.props.setPreviewDoc(this.props.rowProps.original); @@ -110,33 +111,26 @@ export class CollectionSchemaCell extends React.Component { } catch { } } - // this._isEditing = true; - // this.props.setIsEditing(true); - - const field = this.props.rowProps.original[this.props.rowProps.column.id!]; - const doc = FieldValue(Cast(field, Doc)); - if (typeof field === "object" && doc) this.props.setPreviewDoc(doc); + const doc = Cast(this._rowDoc[this.renderFieldKey], Doc, null); + doc && this.props.setPreviewDoc(doc); } @undoBatch applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => { const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) }); if (!res.success) return false; - // doc[this.props.fieldKey] = res.result; - // return true; - doc[this.props.rowProps.column.id as string] = res.result; + doc[this.renderFieldKey] = res.result; return true; } private drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - const fieldKey = this.props.rowProps.column.id as string; if (de.complete.docDragData.draggedDocuments.length === 1) { - this._document[fieldKey] = de.complete.docDragData.draggedDocuments[0]; + this._rowDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0]; } else { const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {}); - this._document[fieldKey] = coll; + this._rowDoc[this.renderFieldKey] = coll; } e.stopPropagation(); } @@ -147,22 +141,9 @@ export class CollectionSchemaCell extends React.Component { ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); } - // expandDoc = (e: React.PointerEvent) => { - // let field = this.props.rowProps.original[this.props.rowProps.column.id as string]; - // let doc = FieldValue(Cast(field, Doc)); - - // this.props.setPreviewDoc(doc!); - - // // this.props.changeFocusedCellByIndex(this.props.row, this.props.col); - - // e.stopPropagation(); - // } - - returnHighlights(bing: (() => string), positions?: number[]) { - const results = []; - const contents = bing(); - - if (positions !== undefined) { + returnHighlights(contents: string, positions?: number[]) { + if (positions) { + const results = []; StrCast(this.props.Document._searchString); const length = StrCast(this.props.Document._searchString).length; const color = contents ? "black" : "grey"; @@ -177,72 +158,24 @@ export class CollectionSchemaCell extends React.Component { ); return results; } - else { - return {contents ? contents?.valueOf() : "undefined"}; - } + return {contents ? contents?.valueOf() : "undefined"}; } - type: string = ""; + @computed get renderFieldKey() { return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original); } + onItemDown = async (e: React.PointerEvent) => { + if (this.props.Document._searchDoc) { + const doc = Doc.GetProto(this._rowDoc); + const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc); + const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); + DocumentManager.Instance.jumpToDocument(this._rowDoc, false, () => undefined, targetContext); + } + }; renderCellWithType(type: string | undefined) { const dragRef: React.RefObject = React.createRef(); - const props: FieldViewProps = { - Document: this.props.rowProps.original, - DataDoc: this.props.rowProps.original, - LibraryPath: [], - dropAction: "alias", - bringToFront: emptyFunction, - rootSelected: returnFalse, - fieldKey: this.props.rowProps.column.id as string, - docFilters: returnEmptyFilter, - searchFilterDocs: returnEmptyDoclist, - ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document, - isSelected: returnFalse, - select: emptyFunction, - renderDepth: this.props.renderDepth + 1, - ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, - active: returnFalse, - whenActiveChanged: emptyFunction, - PanelHeight: returnZero, - PanelWidth: returnZero, - NativeHeight: returnZero, - NativeWidth: returnZero, - addDocTab: this.props.addDocTab, - pinToPres: this.props.pinToPres, - ContentScaling: returnOne - }; + const fieldKey = this.renderFieldKey; + const field = this._rowDoc[fieldKey]; - let matchedKeys = [props.fieldKey]; - if (props.fieldKey.startsWith("*")) { - const allKeys = Array.from(Object.keys(props.Document)); - allKeys.push(...Array.from(Object.keys(Doc.GetProto(props.Document)))); - matchedKeys = allKeys.filter(key => key.includes(props.fieldKey.substring(1))); - } - const fieldKey = matchedKeys.length ? matchedKeys[0] : props.fieldKey; - const field = props.Document[fieldKey]; - const doc = FieldValue(Cast(field, Doc)); - const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); - - const onItemDown = async (e: React.PointerEvent) => { - if (this.props.Document._searchDoc) { - const doc = Doc.GetProto(this.props.rowProps.original); - const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc); - let targetContext = undefined; - if (aliasdoc.length > 0) { - targetContext = Cast(aliasdoc[0].context, Doc) as Doc; - } - DocumentManager.Instance.jumpToDocument(this.props.rowProps.original, false, undefined, targetContext); - } - else { - fieldIsDoc && - SetupDrag(this._focusRef, - () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument, - this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); - } - }; const onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) { dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; @@ -252,31 +185,8 @@ export class CollectionSchemaCell extends React.Component { dragRef.current!.className = "collectionSchemaView-cellContainer"; }; - let contents: any = "incorrect type"; - if (type === undefined) contents = field === undefined ? undefined : Field.toString(field as Field);//StrCast(field) === "" ? "--" : ; - if (type === "number") contents = typeof field === "number" ? NumCast(field) : "--" + typeof field + "--"; - if (type === "string") { - fieldKey === "text" ? - contents = Cast(field, RichTextField)?.Text : - contents = typeof field === "string" ? (StrCast(field) === "" ? "--" : StrCast(field)) : "--" + typeof field + "--"; - } - if (type === "boolean") contents = typeof field === "boolean" ? (BoolCast(field) ? "true" : "false") : "--" + typeof field + "--"; - if (type === "document") { - const doc = FieldValue(Cast(field, Doc)); - contents = typeof field === "object" ? doc ? StrCast(doc.title) === "" ? "--" : StrCast(doc.title) : `--${typeof field}--` : `--${typeof field}--`; - } - if (type === "image") { - const image = FieldValue(Cast(field, ImageField)); - const doc = FieldValue(Cast(field, Doc)); - contents = typeof field === "object" ? doc ? StrCast(doc.title) === "" ? "--" : StrCast(doc.title) : `--${typeof field}--` : `--${typeof field}--`; - } - if (type === "list") { - contents = typeof field === "object" ? doc ? StrCast(field) === "" ? "--" : StrCast(field) : `--${typeof field}--` : `--${typeof field}--`; - } - if (type === "date") { - contents = typeof field === "object" ? doc ? StrCast(field) === "" ? "--" : StrCast(field) : `--${typeof field}--` : `--${typeof field}--`; - } - + let contents = Field.toString(field as Field); + contents = contents === "" ? "--" : contents; let className = "collectionSchemaView-cellWrapper"; if (this._isEditing) className += " editing"; @@ -284,11 +194,8 @@ export class CollectionSchemaCell extends React.Component { if (this.props.isFocused && !this.props.isEditable) className += " inactive"; const positions = []; - let cfield = props.Document[props.fieldKey]; - this.type = props.fieldKey; if (StrCast(this.props.Document._searchString).toLowerCase() !== "") { - let term = (cfield instanceof Promise) ? "...promise pending..." : Field.toString(cfield as Field); - term = term.toLowerCase(); + let term = (field instanceof Promise) ? "...promise pending..." : contents.toLowerCase(); const search = StrCast(this.props.Document._searchString).toLowerCase(); let start = term.indexOf(search); let tally = 0; @@ -305,125 +212,55 @@ export class CollectionSchemaCell extends React.Component { positions.pop(); } } - let search = false; - if (this.props.Document._searchDoc) { - search = true; - } - const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined"; return ( -
-
-
- {!search ? +
+
+ {!this.props.Document._searchDoc ? 0 ? positions : undefined} - search={Cast(this.props.Document._searchString, "string", null)} editing={this._isEditing} isEditingCallback={this.isEditingCallback} display={"inline"} contents={contents} - highlight={positions.length > 0 ? true : undefined} - //contents={StrCast(contents)} height={"auto"} maxHeight={Number(MAX_ROW_HEIGHT)} placeholder={placeholder} - bing={() => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - if (cfield !== undefined) { - // if (typeof(cfield)===RichTextField) - const a = cfield as RichTextField; - const b = cfield as DateField; - console.log(b); - if (a.Text !== undefined) { - return (a.Text); - } - else if (b.toString() !== undefined) { - return b.toString(); - } - else if (StrCast(cfield)) { - return StrCast(cfield); - } - else { - return String(NumCast(cfield)); - } - } - }} GetValue={() => { - if (type === "number" && (contents === 0 || contents === "0")) { - return "0"; - } else { - const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - if (type === "number") { - return StrCast(cfield); - } - const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; - const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1]; - const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : - Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; - return val; - - } - + const cfield = ComputedField.WithoutComputed(() => FieldValue(field)); + const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; + const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1]; + return cscript ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : + Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; }} SetValue={action((value: string) => { let retVal = false; - if (value.startsWith(":=") || value.startsWith("=:=")) { const script = value.substring(value.startsWith("=:=") ? 3 : 2); - retVal = this.props.setComputed(script, value.startsWith(":=") ? Doc.GetProto(props.Document) : props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col); + retVal = this.props.setComputed(script, value.startsWith(":=") ? Doc.GetProto(this.props.Document) : this.props.Document, this.renderFieldKey, this.props.row, this.props.col); } else { const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - if (script.compiled) { - retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run); - } - + script.compiled && (retVal = this.applyToDoc(this._rowDoc, this.props.row, this.props.col, script.run)); } if (retVal) { this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing' this.props.setIsEditing(false); } return retVal; - - //return true; })} OnFillDown={async (value: string) => { const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - if (script.compiled) { - DocListCast(this.props.Document[this.props.fieldKey]). - forEach((doc, i) => value.startsWith(":=") ? - this.props.setComputed(value.substring(2), doc, this.props.rowProps.column.id!, i, this.props.col) : - this.applyToDoc(doc, i, this.props.col, script.run)); - } + script.compiled && DocListCast(field). + forEach((doc, i) => value.startsWith(":=") ? + this.props.setComputed(value.substring(2), doc, this.renderFieldKey, i, this.props.col) : + this.applyToDoc(doc, i, this.props.col, script.run)); }} /> : - this.returnHighlights(() => { - const dateCheck: Date | undefined = this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof DateField ? DateCast(this.props.rowProps.original[this.props.rowProps.column.id as string]).date : undefined; - if (dateCheck !== undefined) { - cfield = dateCheck.toLocaleString(); - } - if (props.fieldKey === "context") { - cfield = this.contents; - } - if (props.fieldKey === "*lastModified") { - if (FieldValue(props.Document["data-lastModified"]) !== undefined) { - const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["data-lastModified"])) as DateField; - cfield = d.date.toLocaleString(); - } - - else if (FieldValue(props.Document["text-lastModified"]) !== undefined) { - const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["text-lastModified"])) as DateField; - cfield = d.date.toLocaleString(); - } - } - return Field.toString(cfield as Field); - }, positions) + this.returnHighlights(contents, positions) }
- {/* {fieldIsDoc ? docExpander : null} */}
); @@ -433,43 +270,36 @@ export class CollectionSchemaCell extends React.Component { } @observer -export class CollectionSchemaNumberCell extends CollectionSchemaCell { - render() { return this.renderCellWithType("number"); } -} +export class CollectionSchemaNumberCell extends CollectionSchemaCell { render() { return this.renderCellWithType("number"); } } @observer -export class CollectionSchemaBooleanCell extends CollectionSchemaCell { - render() { return this.renderCellWithType("boolean"); } -} +export class CollectionSchemaBooleanCell extends CollectionSchemaCell { render() { return this.renderCellWithType("boolean"); } } @observer -export class CollectionSchemaStringCell extends CollectionSchemaCell { - render() { return this.renderCellWithType("string"); } -} +export class CollectionSchemaStringCell extends CollectionSchemaCell { render() { return this.renderCellWithType("string"); } } @observer export class CollectionSchemaDateCell extends CollectionSchemaCell { - @observable private _date: Date = this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof DateField ? DateCast(this.props.rowProps.original[this.props.rowProps.column.id as string]).date : - this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof Date ? this.props.rowProps.original[this.props.rowProps.column.id as string] : new Date(); + @computed get _date(): Opt { return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined; } @action handleChange = (date: any) => { - this._date = date; // const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } }); // if (script.compiled) { // this.applyToDoc(this._document, this.props.row, this.props.col, script.run); // } else { // ^ DateCast is always undefined for some reason, but that is what the field should be set to - this._document[this.props.rowProps.column.id as string] = date as Date; + this._rowDoc[this.renderFieldKey] = new DateField(date as Date); //} } render() { - return this.handleChange(date)} - onChange={date => this.handleChange(date)} - />; + return !this.props.isFocused ? {this._date ? Field.toString(this._date as Field) : "--"} : + this.handleChange(date)} + onChange={date => this.handleChange(date)} + />; } } @@ -478,45 +308,11 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { _overlayDisposer?: () => void; - private prop: FieldViewProps = { - Document: this.props.rowProps.original, - DataDoc: this.props.rowProps.original, - LibraryPath: [], - dropAction: "alias", - bringToFront: emptyFunction, - rootSelected: returnFalse, - fieldKey: this.props.rowProps.column.id as string, - ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document, - isSelected: returnFalse, - select: emptyFunction, - renderDepth: this.props.renderDepth + 1, - ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, - active: returnFalse, - whenActiveChanged: emptyFunction, - PanelHeight: returnZero, - PanelWidth: returnZero, - NativeHeight: returnZero, - NativeWidth: returnZero, - addDocTab: this.props.addDocTab, - pinToPres: this.props.pinToPres, - ContentScaling: returnOne, - docFilters: returnEmptyFilter, - searchFilterDocs: returnEmptyDoclist, - }; - @observable private _field = this.prop.Document[this.prop.fieldKey]; - @observable private _doc = FieldValue(Cast(this._field, Doc)); - @observable private _docTitle = this._doc?.title; - @observable private _preview = false; - @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); } - @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } - @computed get tableWidth() { return this.prop.PanelWidth() - 2 * this.borderWidth - 4 - this.previewWidth(); } + @computed get _doc() { return FieldValue(Cast(this._rowDoc[this.renderFieldKey], Doc)); } @action onSetValue = (value: string) => { - this._docTitle = value; - //this.prop.Document[this.prop.fieldKey] = this._text; + this._doc && (Doc.GetProto(this._doc).title = value); const script = CompileScript(value, { addReturn: true, @@ -526,46 +322,22 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { const results = script.compiled && script.run(); if (results && results.success) { - this._doc = results.result; - this._document[this.prop.fieldKey] = results.result; - this._docTitle = this._doc?.title; - + this._rowDoc[this.renderFieldKey] = results.result; return true; } return false; } + componentWillUnmount() { this.onBlur(); } + + onBlur = () => { this._overlayDisposer?.(); } onFocus = () => { - this._overlayDisposer?.(); + this.onBlur(); this._overlayDisposer = OverlayView.Instance.addElement(, { x: 0, y: 0 }); } @action - onOpenClick = () => { - this._preview = false; - if (this._doc) { - this.props.addDocTab(this._doc, "onRight"); - return true; - } - return false; - } - - @action - showPreview = (bool: boolean, e: any) => { - if (this._isEditing) { - this._preview = false; - } else { - if (bool) { - this.props.showDoc(this._doc, this.prop.DataDoc, e.clientX, e.clientY); - } else { - this.props.showDoc(undefined); - } - } - } - - @action - isEditingCalling = (isEditing: boolean): void => { - this.showPreview(false, ""); + isEditingCallback = (isEditing: boolean): void => { document.removeEventListener("keydown", this.onKeyDown); isEditing && document.addEventListener("keydown", this.onKeyDown); this._isEditing = isEditing; @@ -573,201 +345,80 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { this.props.changeFocusedCellByIndex(this.props.row, this.props.col); } - onDown = (e: any) => { - this.props.changeFocusedCellByIndex(this.props.row, this.props.col); - this.props.setPreviewDoc(this.props.rowProps.original); - - let url: string; - if (url = StrCast(this.props.rowProps.row.href)) { - try { - new URL(url); - const temp = window.open(url)!; - temp.blur(); - window.focus(); - } catch { } - } - - const field = this.props.rowProps.original[this.props.rowProps.column.id!]; - const doc = FieldValue(Cast(field, Doc)); - if (typeof field === "object" && doc) this.props.setPreviewDoc(doc); - - this.showPreview(true, e); - - } - render() { - if (typeof this._field === "object" && this._doc && this._docTitle) { - return ( -
{ this.showPreview(true, e); }} - onPointerLeave={(e) => { this.showPreview(false, e); }} + return !this._doc ? this.renderCellWithType("document") : +
+
- -
this._overlayDisposer?.()} - > - - { - return StrCast(this._docTitle); - }} - SetValue={action((value: string) => { - this.onSetValue(value); - this.showPreview(false, ""); - return true; - })} - /> -
-
-
+ StrCast(this._doc?.title)} + SetValue={action((value: string) => { + this.onSetValue(value); + return true; + })} + /> +
+
this._doc && this.props.addDocTab(this._doc, "onRight")} className="collectionSchemaView-cellContents-docButton"> +
- ); - } else { - return this.renderCellWithType("document"); - } +
; } } @observer export class CollectionSchemaImageCell extends CollectionSchemaCell { - // render() { - // return this.renderCellWithType("image"); - // } - - choosePath(url: URL, dataDoc: any) { - const lower = url.href.toLowerCase(); - if (url.protocol === "data") { - return url.href; - } else if (url.href.indexOf(window.location.origin) === -1) { - return Utils.CorsProxy(url.href); - } else if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) { - return url.href;//Why is this here - } + + choosePath(url: URL) { + if (url.protocol === "data") return url.href; + if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); + if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href;//Why is this here + const ext = path.extname(url.href); - const _curSuffix = "_o"; - return url.href.replace(ext, _curSuffix + ext); + return url.href.replace(ext, "_o" + path.extname(url.href)); } render() { - const props: FieldViewProps = { - Document: this.props.rowProps.original, - DataDoc: this.props.rowProps.original, - LibraryPath: [], - dropAction: "alias", - bringToFront: emptyFunction, - rootSelected: returnFalse, - fieldKey: this.props.rowProps.column.id as string, - ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document, - isSelected: returnFalse, - select: emptyFunction, - renderDepth: this.props.renderDepth + 1, - ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, - active: returnFalse, - whenActiveChanged: emptyFunction, - PanelHeight: returnZero, - PanelWidth: returnZero, - NativeHeight: returnZero, - NativeWidth: returnZero, - addDocTab: this.props.addDocTab, - pinToPres: this.props.pinToPres, - ContentScaling: returnOne, - docFilters: returnEmptyFilter, - searchFilterDocs: returnEmptyDoclist, - }; + const field = Cast(this._rowDoc[this.renderFieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc + const alts = DocListCast(this._rowDoc[this.renderFieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images + const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents + const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; + const url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; - let image = true; - let url = []; - if (props.DataDoc) { - const field = Cast(props.DataDoc[props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc - const alts = DocListCast(props.DataDoc[props.fieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images - const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url, props.DataDoc)); // access the primary layout data of the alternate documents - const paths = field ? [this.choosePath(field.url, props.DataDoc), ...altpaths] : altpaths; - if (paths.length) { - url = paths; - } else { - url = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; - image = false; - } - //url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; - } else { - url = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; - image = false; - } + const heightToWidth = NumCast(this._rowDoc._nativeHeight) / NumCast(this._rowDoc._nativeWidth); + let width = Math.min(75, this.props.rowProps.width); + const height = Math.min(75, width * heightToWidth); + width = height / heightToWidth; - const heightToWidth = NumCast(props.DataDoc?._nativeHeight) / NumCast(props.DataDoc?._nativeWidth); - const height = this.props.rowProps.width * heightToWidth; - - if (props.fieldKey === "data") { - if (url !== []) { - const reference = React.createRef(); - return ( -
-
- -
-
- ); - - } else { - return this.renderCellWithType("image"); - } - } else { - return this.renderCellWithType("image"); - } + const reference = React.createRef(); + return
+
+ +
+
; } } - - - @observer export class CollectionSchemaListCell extends CollectionSchemaCell { - _overlayDisposer?: () => void; - private prop: FieldViewProps = { - Document: this.props.rowProps.original, - DataDoc: this.props.rowProps.original, - LibraryPath: [], - dropAction: "alias", - bringToFront: emptyFunction, - rootSelected: returnFalse, - fieldKey: this.props.rowProps.column.id as string, - ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.CollectionView && this.props.CollectionView.props.Document, - isSelected: returnFalse, - select: emptyFunction, - renderDepth: this.props.renderDepth + 1, - ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, - active: returnFalse, - whenActiveChanged: emptyFunction, - PanelHeight: returnZero, - PanelWidth: returnZero, - NativeHeight: returnZero, - NativeWidth: returnZero, - addDocTab: this.props.addDocTab, - pinToPres: this.props.pinToPres, - ContentScaling: returnOne, - docFilters: returnEmptyFilter, - searchFilterDocs: returnEmptyDoclist, - }; - @observable private _field = this.prop.Document[this.prop.fieldKey]; - @observable private _optionsList = this._field as List; + @computed get _field() { return this._rowDoc[this.renderFieldKey]; } + @computed get _optionsList() { return this._field as List; } @observable private _opened = false; @observable private _text = "select an item"; @observable private _selectedNum = 0; @@ -777,7 +428,7 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { // change if its a document this._optionsList[this._selectedNum] = this._text = value; - (this.prop.Document[this.prop.fieldKey] as List).splice(this._selectedNum, 1, value); + (this._field as List).splice(this._selectedNum, 1, value); } @action @@ -792,33 +443,22 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { } render() { - let type = "list"; let link = false; const reference = React.createRef(); - if (typeof this._field === "object" && this._optionsList[0]) { - const options = !this._opened ? (null) :
- {this._optionsList.map((element, index) => { - let title = ""; - if (element instanceof Doc) { - type = "document"; - if (this.prop.fieldKey.toLowerCase() === "links") { - link = true; - type = "link"; - } - title = StrCast(element.title); - } - return
this.onSelected(StrCast(element), index)} > - {element} - {title} -
; - })} -
; + if (this._optionsList?.length) { + const options = !this._opened ? (null) : +
+ {this._optionsList.map((element, index) => { + const val = Field.toString(element); + return
this.onSelected(StrCast(element), index)} > + {val} +
; + })} +
; const plainText =
{this._text}
; - const textarea =
+ const textarea =
-
+
{link ? plainText : textarea}
@@ -858,26 +498,13 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { @observer export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { - @observable private _isChecked: boolean = typeof this.props.rowProps.original[this.props.rowProps.column.id as string] === "boolean" ? BoolCast(this.props.rowProps.original[this.props.rowProps.column.id as string]) : false; - - @action - toggleChecked = (e: React.ChangeEvent) => { - this._isChecked = e.target.checked; - const script = CompileScript(e.target.checked.toString(), { requiredType: "boolean", addReturn: true, params: { this: Doc.name } }); - script.compiled && this.applyToDoc(this._document, this.props.row, this.props.col, script.run); - } + @computed get _isChecked() { return BoolCast(this._rowDoc[this.renderFieldKey]); } render() { const reference = React.createRef(); - const onItemDown = (e: React.PointerEvent) => { - (!this.props.CollectionView?.props.isSelected() ? undefined : - SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); - }; return (
-
- -
+ this._rowDoc[this.renderFieldKey] = e.target.checked} />
); } @@ -887,23 +514,14 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { @observer export class CollectionSchemaButtons extends CollectionSchemaCell { render() { - const doc = this.props.rowProps.original; - const searchMatch = (backward: boolean = true) => Doc.SearchMatchNext(doc, backward); - // const reference = React.createRef(); - // const onItemDown = (e: React.PointerEvent) => { - // (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined : - // SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); - // }; - return !this.props.Document._searchDoc ? <> - : [DocumentType.PDF, DocumentType.RTF].includes(StrCast(doc.type) as DocumentType) ? -
- - -
: - <>; + return !this._rowDoc._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? <> : +
+ + +
; } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 828cdd282..bf81e4509 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -157,7 +157,10 @@ export class CollectionView extends Touchable Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc)); + added.map(doc => { + Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + doc.context = this.props.Document; + }); } else { added.map(doc => { @@ -180,8 +183,6 @@ export class CollectionView extends Touchable).push(...added); targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); - const lastModified = "lastModified"; - targetDataDoc[lastModified] = new DateField(new Date(Date.now())); } } } diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index 7f8ccf6ac..28242a5aa 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -5,7 +5,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table"; import "react-table/react-table.css"; -import { Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; +import { Doc, DocListCast, Field, Opt, AclPrivate, AclReadonly, DataSym } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; @@ -27,6 +27,9 @@ import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; import "./CollectionSchemaView.scss"; import { CollectionView } from "./CollectionView"; import { DocumentType } from "../../documents/DocumentTypes"; +import { GetEffectiveAcl } from "../../../fields/util"; +import { DateField } from "../../../fields/DateField"; +import { ImageField } from "../../../fields/URLField"; enum ColumnType { @@ -227,7 +230,8 @@ export class SchemaTable extends React.Component { showDoc: this.showDoc, }; - switch (this.getColumnType(col)) { + + switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) { case ColumnType.Number: return ; case ColumnType.String: return ; case ColumnType.Boolean: return ; @@ -235,7 +239,8 @@ export class SchemaTable extends React.Component { case ColumnType.Image: return ; case ColumnType.List: return ; case ColumnType.Date: return ; - default: return ; + default: + return ; } }, minWidth: 200, @@ -295,7 +300,15 @@ export class SchemaTable extends React.Component { } tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => { - return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); + const tableDoc = this.props.Document[DataSym]; + const effectiveAcl = GetEffectiveAcl(tableDoc); + + if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) { + doc.context = this.props.Document; + tableDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); + } + return false; } private getTrProps: ComponentPropsGetterR = (state, rowInfo) => { @@ -380,7 +393,14 @@ export class SchemaTable extends React.Component { } @action - getColumnType = (column: SchemaHeaderField): ColumnType => { + getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => { + if (doc && field && column.type === ColumnType.Any) { + const val = doc[CollectionSchemaCell.resolvedFieldKey(field, doc)]; + if (val instanceof ImageField) return ColumnType.Image; + if (val instanceof Doc) return ColumnType.Doc; + if (val instanceof DateField) return ColumnType.Date; + if (val instanceof List) return ColumnType.List; + } if (column.type && column.type !== 0) { return column.type; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 549610e53..491c7e7e0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1408,7 +1408,8 @@ export class CollectionFreeFormView extends CollectionSubView {this.children} -
+ +
{this.showTimeline ? : (null)} ; } diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts index bee62663e..48106d978 100644 --- a/src/fields/DateField.ts +++ b/src/fields/DateField.ts @@ -20,14 +20,14 @@ export class DateField extends ObjectField { } toString() { - return `${this.date.toISOString()}`; + return `${this.date.toLocaleString()}`; } [ToScriptString]() { return `new DateField(new Date(${this.date.toISOString()}))`; } [ToString]() { - return this.date.toISOString(); + return this.date.toLocaleString(); } getDate() { diff --git a/src/fields/List.ts b/src/fields/List.ts index 3601f282b..c9e4bd3c1 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -324,7 +324,7 @@ class ListImpl extends ObjectField { return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; } [ToString]() { - return "List"; + return `List(${(this as any).length})`; } } export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; diff --git a/src/fields/util.ts b/src/fields/util.ts index d8523dfa2..9679527e4 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -156,14 +156,17 @@ export enum SharingPermissions { */ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, user?: string): symbol { if (!target) return AclPrivate; + + // all changes received fromt the server must be processed as Admin if (in_prop === UpdatingFromServer || target[UpdatingFromServer]) return AclAdmin; - if (target[AclSym] && Object.keys(target[AclSym]).length) { + // if the current user is the author of the document / the current user is a member of the admin group + const userChecked = user || Doc.CurrentUserEmail; + if (userChecked === (target.__fields?.author || target.author)) return AclAdmin; - const userChecked = user || Doc.CurrentUserEmail; + if (target[AclSym] && Object.keys(target[AclSym]).length) { - // if the current user is the author of the document / the current user is a member of the admin group - if (userChecked === (target.__fields?.author || target.author) || currentUserGroups.includes("admin")) return AclAdmin; + if (currentUserGroups.includes("admin")) return AclAdmin; // if the ACL is being overriden or the property being modified is one of the playground fields (which can be freely modified) if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; -- cgit v1.2.3-70-g09d2