diff options
-rw-r--r-- | src/client/documents/Documents.ts | 6 | ||||
-rw-r--r-- | src/client/util/TooltipTextMenu.tsx | 6 | ||||
-rw-r--r-- | src/client/views/EditableView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/GlobalKeyHandler.ts | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaCells.tsx | 65 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSchemaView.tsx | 114 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 74 | ||||
-rw-r--r-- | src/client/views/collections/KeyRestrictionRow.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.scss | 70 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 84 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 16 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 53 | ||||
-rw-r--r-- | src/client/views/pdf/Page.tsx | 12 |
13 files changed, 370 insertions, 138 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e6fe1b8b3..ee1b9fd0d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -582,12 +582,12 @@ export namespace Docs { export namespace DocUtils { export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default", sourceContext?: Doc) { - if (LinkManager.Instance.doesLinkExist(source, target)) return; + if (LinkManager.Instance.doesLinkExist(source, target)) return undefined; let sv = DocumentManager.Instance.getDocumentView(source); if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return; - if (target === CurrentUserUtils.UserDocument) return; + if (target === CurrentUserUtils.UserDocument) return undefined; - let linkDoc; + let linkDoc: Doc | undefined; UndoManager.RunInBatch(() => { linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: "100%" }); linkDoc.type = DocumentType.LINK; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index a4c053de2..6214b568c 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,6 +1,6 @@ import { action } from "mobx"; import { Dropdown, MenuItem, icons, } from "prosemirror-menu"; //no import css -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; +import { EditorState, NodeSelection, TextSelection, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { schema } from "./RichTextSchema"; import { Schema, NodeType, MarkType, Mark, ResolvedPos } from "prosemirror-model"; @@ -321,6 +321,10 @@ export class TooltipTextMenu { } } + makeLinkWithState = (state: EditorState, target: string, location: string) => { + let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); + } + makeLink = (target: string, location: string) => { let node = this.view.state.selection.$from.nodeAfter; let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 373b63282..c3612fee9 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -134,7 +134,7 @@ export class EditableView extends React.Component<EditableProps> { return ( <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }} - onClick={this.onClick} > + onClick={this.onClick}> <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{this.props.contents}</span> </div> ); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 5050f34ab..d52c05b2f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -152,9 +152,9 @@ export default class KeyManager { stopPropagation = false; break; case "a": - case "c": case "v": case "x": + case "c": stopPropagation = false; preventDefault = false; break; diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index c91f1017b..f1e013edd 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -24,7 +24,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faExpand } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; +import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; library.add(faExpand); @@ -44,6 +44,8 @@ export interface CellProps { setIsEditing: (isEditing: boolean) => void; isEditable: boolean; setPreviewDoc: (doc: Doc) => void; + setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean; + getField: (row: number, col?: number) => void; } @observer @@ -82,11 +84,18 @@ export class CollectionSchemaCell extends React.Component<CellProps> { @action onPointerDown = (e: React.PointerEvent): void => { this.props.changeFocusedCellByIndex(this.props.row, this.props.col); + this.props.setPreviewDoc(this.props.rowProps.original); + + let field = this.props.rowProps.original[this.props.rowProps.column.id!]; + let doc = FieldValue(Cast(field, Doc)); + if (typeof field === "object" && doc) this.props.setPreviewDoc(doc); } - applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => { - const res = run({ this: doc }); + 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; return true; } @@ -146,10 +155,16 @@ export class CollectionSchemaCell extends React.Component<CellProps> { addDocTab: this.props.addDocTab, }; - // let onItemDown = (e: React.PointerEvent) => { - // SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - // 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]; + let doc = FieldValue(Cast(field, Doc)); + let fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); + + let onItemDown = (e: React.PointerEvent) => { + if (fieldIsDoc) { + SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, + 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 onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) { dragRef!.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; @@ -159,7 +174,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> { dragRef!.current!.className = "collectionSchemaView-cellContainer"; }; - let field = props.Document[props.fieldKey]; let contents: any = "incorrect type"; if (type === undefined) contents = <FieldView {...props} />; if (type === "number") contents = typeof field === "number" ? NumCast(field) : "--" + typeof field + "--"; @@ -175,18 +189,16 @@ export class CollectionSchemaCell extends React.Component<CellProps> { if (this.props.isFocused && this.props.isEditable) className += " focused"; if (this.props.isFocused && !this.props.isEditable) className += " inactive"; - let doc = FieldValue(Cast(field, Doc)); - if (type === "document") console.log("doc", typeof field); - let fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); - let docExpander = ( - <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} > - <FontAwesomeIcon icon="expand" size="sm" /> - </div> - ); + + // let docExpander = ( + // <div className="collectionSchemaView-cellContents-docExpander" onPointerDown={this.expandDoc} > + // <FontAwesomeIcon icon="expand" size="sm" /> + // </div> + // ); return ( - <div className="collectionSchemaView-cellContainer" ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> - <div className={className} ref={this._focusRef} tabIndex={-1}> + <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> + <div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}> <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}> <EditableView editing={this._isEditing} @@ -203,24 +215,27 @@ export class CollectionSchemaCell extends React.Component<CellProps> { } } SetValue={(value: string) => { - let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name } }); + if (value.startsWith(":=")) { + return this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col); + } + let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); if (!script.compiled) { return false; } - return this.applyToDoc(props.Document, script.run); + return this.applyToDoc(props.Document, this.props.row, this.props.col, script.run); }} OnFillDown={async (value: string) => { - let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name } }); + let script = CompileScript(value, { requiredType: type, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); if (!script.compiled) { return; } const run = script.run; - //TODO This should be able to be refactored to compile the script once const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]); - val && val.forEach(doc => this.applyToDoc(doc, run)); - }} /> + val && val.forEach((doc, i) => this.applyToDoc(doc, i, this.props.col, run)); + }} + /> </div > - {fieldIsDoc ? docExpander : null} + {/* {fieldIsDoc ? docExpander : null} */} </div> </div> ); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index d504f9799..77ed2d8dc 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -202,7 +202,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { Document={this.props.Document} // child doc PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} - // childDocs={this.childDocs} + childDocs={this.childDocs} CollectionView={this.props.CollectionView} ContainingCollectionView={this.props.ContainingCollectionView} fieldKey={this.props.fieldKey} // might just be this. @@ -252,7 +252,7 @@ export interface SchemaTableProps { dataDoc?: Doc; PanelHeight: () => number; PanelWidth: () => number; - // childDocs: Doc[]; + childDocs: Doc[]; CollectionView: CollectionView | CollectionPDFView | CollectionVideoView; ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; fieldKey: string; @@ -299,8 +299,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> { let focusedCol = this._focusedCell.col; let isEditable = !this._headerIsEditing;// && this.props.isSelected(); - let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - let children = DocListCast(cdoc[this.props.fieldKey]); + // let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; + // let children = DocListCast(cdoc[this.props.fieldKey]); + let children = this.props.childDocs; if (children.reduce((found, doc) => found || doc.type === "collection", false)) { columns.push( @@ -321,13 +322,12 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } let cols = this.columns.map(col => { - let header = <CollectionSchemaHeader keyValue={col} possibleKeys={possibleKeys} existingKeys={this.columns.map(c => c.heading)} keyType={this.getColumnType(col)} - typeConst={col.type !== undefined || columnTypes.get(col.heading) !== undefined} + typeConst={columnTypes.get(col.heading) !== undefined} onSelect={this.changeColumns} setIsEditing={this.setHeaderIsEditing} deleteColumn={this.deleteColumn} @@ -360,7 +360,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> { moveDocument: this.props.moveDocument, setIsEditing: this.setCellIsEditing, isEditable: isEditable, - setPreviewDoc: this.props.setPreviewDoc + setPreviewDoc: this.props.setPreviewDoc, + setComputed: this.setComputed, + getField: this.getField, }; let colType = this.getColumnType(col); @@ -423,7 +425,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { tableRemoveDoc = (document: Doc): boolean => { let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + let children = this.props.childDocs; if (children.indexOf(document) !== -1) { children.splice(children.indexOf(document), 1); return true; @@ -518,7 +521,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { this.changeFocusedCellByDirection(direction); let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + let children = this.props.childDocs; const pdoc = FieldValue(children[this._focusedCell.row]); pdoc && this.props.setPreviewDoc(pdoc); } @@ -527,7 +531,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { @action changeFocusedCellByDirection = (direction: string): void => { let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + let children = this.props.childDocs; switch (direction) { case "tab": if (this._focusedCell.col + 1 === this.columns.length && this._focusedCell.row + 1 === children.length) { @@ -558,7 +563,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { @action changeFocusedCellByIndex = (row: number, col: number): void => { let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); this._focusedCell = { row: row, col: col }; this.props.setFocused(this.props.Document); @@ -569,7 +574,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { createRow = () => { let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []); + let children = this.props.childDocs; let newDoc = Docs.Create.TextDocument({ width: 100, height: 30 }); let proto = Doc.GetProto(newDoc); @@ -681,7 +687,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } get documentKeys() { - const docs = DocListCast(this.props.Document[this.props.fieldKey]); + // const docs = DocListCast(this.props.Document[this.props.fieldKey]); + let docs = this.props.childDocs; 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 @@ -710,7 +717,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { get reactTable() { let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; - let children = DocListCast(cdoc[this.props.fieldKey]); + // let children = DocListCast(cdoc[this.props.fieldKey]); + let children = this.props.childDocs; let previewWidth = this.previewWidth(); // + 2 * this.borderWidth + this.DIVIDER_WIDTH + 1; let hasCollectionChild = children.reduce((found, doc) => found || doc.type === "collection", false); @@ -756,7 +764,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { let csv: string = this.columns.reduce((val, col) => val + col + ",", ""); csv = csv.substr(0, csv.length - 1) + "\n"; let self = this; - DocListCast(this.props.Document.data).map(doc => { + this.props.childDocs.map(doc => { csv += self.columns.reduce((val, col) => val + (doc[col.heading] ? doc[col.heading]!.toString() : "0") + ",", ""); csv = csv.substr(0, csv.length - 1) + "\n"; }); @@ -772,6 +780,82 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } } + getField = (row: number, col?: number) => { + // const docs = DocListCast(this.props.Document[this.props.fieldKey]); + + let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document; + // const docs = DocListCast(cdoc[this.props.fieldKey]); + let docs = this.props.childDocs; + + row = row % docs.length; + while (row < 0) row += docs.length; + const columns = this.columns; + const doc = docs[row]; + if (col === undefined) { + return doc; + } + if (col >= 0 && col < columns.length) { + const column = this.columns[col].heading; + return doc[column]; + } + return undefined; + } + + createTransformer = (row: number, col: number): Transformer => { + const self = this; + const captures: { [name: string]: Field } = {}; + + const transformer: ts.TransformerFactory<ts.SourceFile> = context => { + return root => { + function visit(node: ts.Node) { + node = ts.visitEachChild(node, visit, context); + if (ts.isIdentifier(node)) { + const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; + const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; + if (isntPropAccess && isntPropAssign) { + if (node.text === "$r") { + return ts.createNumericLiteral(row.toString()); + } else if (node.text === "$c") { + return ts.createNumericLiteral(col.toString()); + } else if (node.text === "$") { + if (ts.isCallExpression(node.parent)) { + // captures.doc = self.props.Document; + // captures.key = self.props.fieldKey; + } + } + } + } + + return node; + } + return ts.visitNode(root, visit); + }; + }; + + // const getVars = () => { + // return { capturedVariables: captures }; + // }; + + return { transformer, /*getVars*/ }; + } + + setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => { + script = + `const $ = (row:number, col?:number) => { + if(col === undefined) { + return (doc as any)[key][row + ${row}]; + } + return (doc as any)[key][row + ${row}][(doc as any).schemaColumns[col + ${col}].heading]; + } + return ${script}`; + const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: true, transformer: this.createTransformer(row, col) }); + if (compiled.compiled) { + doc[field] = new ComputedField(compiled); + return true; + } + return false; + } + render() { // if (SelectionManager.SelectedDocuments().length > 0) console.log(StrCast(SelectionManager.SelectedDocuments()[0].Document.title)); // if (DocumentManager.Instance.getDocumentView(this.props.Document)) console.log(StrCast(this.props.Document.title), SelectionManager.IsSelected(DocumentManager.Instance.getDocumentView(this.props.Document)!)) diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index cf6079196..cf5bc451a 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -69,62 +69,50 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC children(docs: Doc[]) { let parent = this.props.parent; - this.props.parent._docXfs.length = 0; + parent._docXfs.length = 0; return docs.map((d, i) => { - let layoutDoc = Doc.expandTemplateLayout(d, parent.props.DataDoc); let headings = this.props.headings(); let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); let pair = Doc.GetLayoutDataDocPair(parent.props.Document, parent.props.DataDoc, parent.props.fieldKey, d); let width = () => (d.nativeWidth && !BoolCast(d.ignoreAspect) ? Math.min(pair.layout[WidthSym](), parent.columnWidth) : parent.columnWidth) / (uniqueHeadings.length + 1); let height = () => parent.getDocHeight(pair.layout); - if (parent.singleColumn) { - let dxf; - let dref = React.createRef<HTMLDivElement>(); - if (uniqueHeadings.length > 0) { - dxf = () => this.getDocTransform(layoutDoc, dref.current!); - this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); - } - else { - //have to add the height of all previous single column sections or the doc decorations will be in the wrong place. - dxf = () => this.getSingleDocTransform(layoutDoc, i, width()); - this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); - } - let rowHgtPcnt = height(); - return <div className="collectionStackingView-columnDoc" key={d[Id]} ref={dref} style={{ width: width(), marginTop: i === 0 ? 0 : parent.gridGap, height: `${rowHgtPcnt}` }} > - {this.props.parent.getDisplayDoc(layoutDoc, d, dxf, width)} - </div>; - } else { - let dref = React.createRef<HTMLDivElement>(); - let dxf = () => this.getDocTransform(layoutDoc, dref.current!); - this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); - let rowHgtPcnt = height(); - let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); - let divStyle = parent.singleColumn ? { width: width(), marginTop: i === 0 ? 0 : parent.gridGap, height: `${rowHgtPcnt}` } : { gridRowEnd: `span ${rowSpan}` }; - return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={divStyle} > - {this.props.parent.getDisplayDoc(layoutDoc, d, dxf, width)} - </div>; - } + let dref = React.createRef<HTMLDivElement>(); + // if (uniqueHeadings.length > 0) { + let dxf = () => this.getDocTransform(pair.layout, dref.current!); + this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); + // } + // else { + // //have to add the height of all previous single column sections or the doc decorations will be in the wrong place. + // let dxf = () => this.getDocTransform(layoutDoc, i, width()); + // this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); + // } + let rowHgtPcnt = height(); + let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); + let style = parent.singleColumn ? { width: width(), marginTop: i === 0 ? 0 : parent.gridGap, height: `${rowHgtPcnt}` } : { gridRowEnd: `span ${rowSpan}` }; + return <div className={`collectionStackingView-${parent.singleColumn ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > + {this.props.parent.getDisplayDoc(pair.layout, pair.data, dxf, width)} + </div>; + // } else { + // let dref = React.createRef<HTMLDivElement>(); + // let dxf = () => this.getDocTransform(layoutDoc, dref.current!); + // this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height }); + // let rowHgtPcnt = height(); + // let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); + // let divStyle = parent.singleColumn ? { width: width(), marginTop: i === 0 ? 0 : parent.gridGap, height: `${rowHgtPcnt}` } : { gridRowEnd: `span ${rowSpan}` }; + // return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={divStyle} > + // {this.props.parent.getDisplayDoc(layoutDoc, d, dxf, width)} + // </div>; + // } }); } - getSingleDocTransform(doc: Doc, ind: number, width: number) { - let localY = this.props.parent.filteredChildren.reduce((height, d, i) => - height + (i < ind ? this.props.parent.getDocHeight(Doc.expandTemplateLayout(d, this.props.parent.props.DataDoc)) + this.props.parent.gridGap : 0), this.props.parent.yMargin); - let translate = this.props.parent.props.ScreenToLocalTransform().inverse().transformPoint((this.props.parent.props.PanelWidth() - width) / 2, localY); - return this.offsetTransform(doc, translate[0], translate[1]); - } - - offsetTransform(doc: Doc, translateX: number, translateY: number) { - let outerXf = Utils.GetScreenTransform(this.props.parent._masonryGridRef!); - let offset = this.props.parent.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - return this.props.parent.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.props.parent.columnWidth); - } - getDocTransform(doc: Doc, dref: HTMLDivElement) { let { scale, translateX, translateY } = Utils.GetScreenTransform(dref); let outerXf = Utils.GetScreenTransform(this.props.parent._masonryGridRef!); let offset = this.props.parent.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - return this.props.parent.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.props.parent.columnWidth); + return this.props.parent.props.ScreenToLocalTransform(). + translate(offset[0], offset[1] - (this.props.parent.chromeCollapsed ? 0 : 100)). + scale(NumCast(doc.width, 1) / this.props.parent.columnWidth); } getValue = (value: string): any => { diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx index f292557b5..4f3d82114 100644 --- a/src/client/views/collections/KeyRestrictionRow.tsx +++ b/src/client/views/collections/KeyRestrictionRow.tsx @@ -18,10 +18,12 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr if (this._key && this._value) { let parsedValue: string | number = `"${this._value}"`; let parsed = parseInt(this._value); + let type = "string"; if (!isNaN(parsed)) { parsedValue = parsed; + type = "number"; } - let scriptText = `(doc.${this._key} ${this._contains ? "===" : "!=="} ${parsedValue})`; + let scriptText = `${this._contains ? "" : "!"}((doc.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))`; this.props.script(scriptText); } return ( diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index d3045ae2f..a24abb32e 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -1,34 +1,37 @@ @import "../globalCssVariables"; + .ProseMirror { - width: 100%; - height: 100%; - min-height: 100%; - font-family: $serif; + width: 100%; + height: 100%; + min-height: 100%; + font-family: $serif; } .ProseMirror:focus { - outline: none !important; + outline: none !important; } -.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden { - background: inherit; - padding: 0; - border-width: 0px; - border-radius: inherit; - border-color: $intermediate-color; - box-sizing: border-box; - background-color: inherit; - border-style: solid; - overflow-y: auto; - overflow-x: hidden; - color: initial; - height: 100%; - pointer-events: all; +.formattedTextBox-cont-scroll, +.formattedTextBox-cont-hidden { + background: inherit; + padding: 0; + border-width: 0px; + border-radius: inherit; + border-color: $intermediate-color; + box-sizing: border-box; + background-color: inherit; + border-style: solid; + overflow-y: auto; + overflow-x: hidden; + color: initial; + height: 100%; + pointer-events: all; } .formattedTextBox-cont-hidden { pointer-events: none; } + .formattedTextBox-inner-rounded { height: calc(100% - 25px); width: calc(100% - 40px); @@ -38,23 +41,28 @@ left: 20; } +.formattedTextBox-inner-rounded div, +.formattedTextBox-inner div { + padding: 10px; +} + .menuicon { - display: inline-block; - border-right: 1px solid rgba(0, 0, 0, 0.2); - color: #888; - line-height: 1; - padding: 0 7px; - margin: 1px; - cursor: pointer; - text-align: center; - min-width: 1.4em; + display: inline-block; + border-right: 1px solid rgba(0, 0, 0, 0.2); + color: #888; + line-height: 1; + padding: 0 7px; + margin: 1px; + cursor: pointer; + text-align: center; + min-width: 1.4em; } .strong, .heading { - font-weight: bold; + font-weight: bold; } .em { - font-style: italic; -} + font-style: italic; +}
\ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d5f539194..f019868aa 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -5,17 +5,17 @@ import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; +import { EditorState, Plugin, Transaction, Selection } from "prosemirror-state"; import { NodeType, Slice, Node, Fragment } from 'prosemirror-model'; -import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Doc, Opt } from "../../../new_fields/Doc"; +import { Doc, Opt, DocListCast } from "../../../new_fields/Doc"; import { Id, Copy } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; -import { Docs } from '../../documents/Documents'; +import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; @@ -122,12 +122,40 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this.props.isOverlay) { DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } + + document.addEventListener("paste", this.paste); } @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "dummy"); } @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + paste = (e: ClipboardEvent) => { + if (e.clipboardData && this._editorView) { + let pdfPasteText = `${Utils.GenerateDeterministicGuid("pdf paste")}`; + for (let i = 0; i < e.clipboardData.items.length; i++) { + let item = e.clipboardData.items.item(i); + if (item.type === "text/plain") { + item.getAsString((text) => { + let pdfPasteIndex = text.indexOf(pdfPasteText); + if (pdfPasteIndex > -1) { + let insertText = text.substr(0, pdfPasteIndex); + const tx = this._editorView!.state.tr.insertText(insertText); + // tx.setSelection(new Selection(tx.)) + const state = this._editorView!.state; + this._editorView!.dispatch(tx); + if (this._toolTipTextMenu) { + // this._toolTipTextMenu.makeLinkWithState(state) + } + e.stopPropagation(); + e.preventDefault(); + } + }); + } + } + } + } + dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); @@ -272,7 +300,48 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { - return false; + let cbe = event as ClipboardEvent; + let docId: string; + let regionId: string; + if (!cbe.clipboardData) { + return false; + } + let linkId: string; + docId = cbe.clipboardData.getData("dash/pdfOrigin"); + regionId = cbe.clipboardData.getData("dash/pdfRegion"); + if (!docId || !regionId) { + return false; + } + + DocServer.GetRefField(docId).then(doc => { + DocServer.GetRefField(regionId).then(region => { + if (!(doc instanceof Doc) || !(region instanceof Doc)) { + return; + } + + let annotations = DocListCast(region.annotations); + annotations.forEach(anno => anno.target = this.props.Document); + let fieldExtDoc = Doc.resolvedFieldDataDoc(doc, "data", "true"); + let targetAnnotations = DocListCast(fieldExtDoc.annotations); + if (targetAnnotations) { + targetAnnotations.push(region); + fieldExtDoc.annotations = new List<Doc>(targetAnnotations); + } + + let link = DocUtils.MakeLink(this.props.Document, region); + if (link) { + cbe.clipboardData!.setData("dash/linkDoc", link[Id]); + linkId = link[Id]; + let frag = addMarkToFrag(slice.content); + slice = new Slice(frag, slice.openStart, slice.openEnd); + var tr = view.state.tr.replaceSelection(slice); + view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); + } + }); + }); + + return true; + function addMarkToFrag(frag: Fragment) { const nodes: Node[] = []; frag.forEach(node => nodes.push(addLinkMark(node))); @@ -285,7 +354,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type.name === "link"); - const link = view.state.schema.mark(view.state.schema.marks.link, { href: "http://localhost:1050/doc/[link document id]", location: "onRight" }); + const link = view.state.schema.mark(view.state.schema.marks.link, { href: `http://localhost:1050/doc/${linkId}`, location: "onRight" }); if (linkIndex !== -1) { marks.splice(linkIndex, 1, link); } else { @@ -293,11 +362,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } return node.mark(marks); } - let frag = addMarkToFrag(slice.content); - slice = new Slice(frag, slice.openStart, slice.openEnd); - var tr = view.state.tr.replaceSelection(slice); - view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); - return true; } private setupEditor(config: any, doc: Doc, fieldKey: string) { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 5a5e6e6dd..f527c0595 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -24,6 +24,8 @@ import { Flyout, anchorPoints } from '../DocumentDecorations'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ScriptField } from '../../../new_fields/ScriptField'; import { KeyCodes } from '../../northstar/utils/KeyCodes'; +import { Utils } from '../../../Utils'; +import { Id } from '../../../new_fields/FieldSymbols'; type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const PdfDocument = makeInterface(positionSchema, pageSchema); @@ -67,10 +69,24 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen componentDidMount() { if (this.props.setPdfBox) this.props.setPdfBox(this); + + document.removeEventListener("copy", this.copy); + document.addEventListener("copy", this.copy); } componentWillUnmount() { this._reactionDisposer && this._reactionDisposer(); + document.removeEventListener("copy", this.copy); + } + + private copy = (e: ClipboardEvent) => { + if (this.props.active()) { + if (e.clipboardData) { + e.clipboardData.setData("text/plain", text); + e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); + e.preventDefault(); + } + } } public GetPage() { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index b7ded7e06..6fef8a4de 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -20,6 +20,7 @@ import { ScriptField } from "../../../new_fields/ScriptField"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Annotation from "./Annotation"; import { KeyCodes } from "../../northstar/utils/KeyCodes"; +import { DocServer } from "../../DocServer"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); export const scale = 2; @@ -91,6 +92,7 @@ export class Viewer extends React.Component<IViewerProps> { // private _textContent: Pdfjs.TextContent[] = []; private _pdfFindController: any; private _searchString: string = ""; + private _selectionText: string = ""; constructor(props: IViewerProps) { super(props); @@ -101,6 +103,10 @@ export class Viewer extends React.Component<IViewerProps> { this._mainCont = React.createRef(); } + setSelectionText = (text: string) => { + this._selectionText = text; + } + componentDidUpdate = (prevProps: IViewerProps) => { if (this.scrollY !== prevProps.scrollY) { this.renderPages(); @@ -156,6 +162,9 @@ export class Viewer extends React.Component<IViewerProps> { if (this._mainCont.current) { this._dropDisposer = this._mainCont.current && DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } }); } + + document.removeEventListener("copy", this.copy); + document.addEventListener("copy", this.copy); } componentWillUnmount = () => { @@ -163,6 +172,42 @@ export class Viewer extends React.Component<IViewerProps> { this._annotationReactionDisposer && this._annotationReactionDisposer(); this._filterReactionDisposer && this._filterReactionDisposer(); this._dropDisposer && this._dropDisposer(); + document.removeEventListener("copy", this.copy); + } + + private copy = (e: ClipboardEvent) => { + if (this.props.parent.props.active()) { + let text = this._selectionText; + if (e.clipboardData) { + e.clipboardData.setData("text/plain", text); + e.clipboardData.setData("dash/pdfOrigin", this.props.parent.props.Document[Id]); + let annoDoc = this.makeAnnotationDocument(undefined, 0, "#0390fc"); + e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]); + e.preventDefault(); + } + } + // let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); + // if (targetAnnotations) { + // targetAnnotations.push(destDoc); + // } + } + + paste = (e: ClipboardEvent) => { + if (e.clipboardData) { + if (e.clipboardData.getData("dash/pdfOrigin") === this.props.parent.props.Document[Id]) { + let linkDocId = e.clipboardData.getData("dash/linkDoc"); + if (linkDocId) { + DocServer.GetRefField(linkDocId).then(async (link) => { + if (!(link instanceof Doc)) { + return; + } + let proto = Doc.GetProto(link); + let source = await Cast(proto.anchor1, Doc); + proto.anchor2 = this.makeAnnotationDocument(source, 0, "#0390fc", false); + }); + } + } + } } scrollTo(y: number) { @@ -213,7 +258,7 @@ export class Viewer extends React.Component<IViewerProps> { } @action - makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string): Doc => { + makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string, createLink: boolean = true): Doc => { let annoDocs: Doc[] = []; let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); @@ -242,7 +287,7 @@ export class Viewer extends React.Component<IViewerProps> { mainAnnoDoc.y = Math.max(minY, 0); mainAnnoDoc.annotations = new List<Doc>(annoDocs); - if (sourceDoc) { + if (sourceDoc && createLink) { DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); } this._savedAnnotations.clear(); @@ -258,7 +303,6 @@ export class Viewer extends React.Component<IViewerProps> { let targetAnnotations = DocListCast(this.props.parent.fieldExtensionDoc.annotations); if (targetAnnotations) { targetAnnotations.push(destDoc); - this.props.parent.fieldExtensionDoc.annotations = new List<Doc>(targetAnnotations); } else { this.props.parent.fieldExtensionDoc.annotations = new List<Doc>([destDoc]); @@ -292,6 +336,7 @@ export class Viewer extends React.Component<IViewerProps> { this._isPage[page] = "page"; this._visibleElements[page] = ( <Page + setSelectionText={this.setSelectionText} size={this._pageSizes[page]} pdf={this.props.pdf} page={page} @@ -611,7 +656,7 @@ export class Viewer extends React.Component<IViewerProps> { <div className="viewer" style={this._searching ? { position: "absolute", top: 0 } : {}}> {this._visibleElements} </div> - <div className="pdfViewer-text" ref={this._viewer} style={{ transform: "scale(1.5)", transformOrigin: "top left" }} /> + <div className="pdfViewer-text" ref={this._viewer} onCopy={() => console.log("gello world")} style={{ transform: "scale(1.5)", transformOrigin: "top left" }} /> <div className="pdfViewer-annotationLayer" style={{ height: this.props.parent.Document.nativeHeight, width: `100%`, diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index a6864e0f3..c205617b4 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -29,6 +29,7 @@ interface IPageProps { createAnnotation: (div: HTMLDivElement, page: number) => void; makeAnnotationDocuments: (doc: Doc | undefined, scale: number, color: string, linkTo: boolean) => Doc; getScrollFromPage: (page: number) => number; + setSelectionText: (text: string) => void; } @observer @@ -353,7 +354,8 @@ export default class Page extends React.Component<IPageProps> { else { let sel = window.getSelection(); if (sel && sel.type === "Range") { - this.createTextAnnotation(sel); + let selRange = sel.getRangeAt(0); + this.createTextAnnotation(sel, selRange); PDFMenu.Instance.jumpTo(e.clientX, e.clientY); } } @@ -371,8 +373,8 @@ export default class Page extends React.Component<IPageProps> { } @action - createTextAnnotation = (sel: Selection) => { - let clientRects = sel.getRangeAt(0).getClientRects(); + createTextAnnotation = (sel: Selection, selRange: Range) => { + let clientRects = selRange.getClientRects(); if (this._textLayer.current) { let boundingRect = this._textLayer.current.getBoundingClientRect(); for (let i = 0; i < clientRects.length; i++) { @@ -389,6 +391,10 @@ export default class Page extends React.Component<IPageProps> { } } } + let text = selRange.extractContents().textContent; + if (text) { + this.props.setSelectionText(text); + } // clear selection if (sel.empty) { // Chrome sel.empty(); |