diff options
Diffstat (limited to 'src')
53 files changed, 897 insertions, 903 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index a5d9bd0ca..8bd7f2f5c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -18,7 +18,7 @@ export class Utils { return { scale: 1, translateX: 1, translateY: 1 } } const rect = ele.getBoundingClientRect(); - const scale = ele.offsetWidth == 0 && rect.width == 0 ? 1 : rect.width / ele.offsetWidth; + const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth; const translateX = rect.left; const translateY = rect.top; @@ -86,4 +86,14 @@ export class Utils { } } +export function returnTrue() { + return true; +} + +export function returnFalse() { + return false; +} + +export function emptyFunction() { } + export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
\ No newline at end of file diff --git a/src/client/Server.ts b/src/client/Server.ts index 37e3c2c0d..45c7144ca 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -21,11 +21,11 @@ export class Server { let fn = (cb: (field: Opt<Field>) => void) => { let cached = this.ClientFieldsCached.get(fieldid); - if (!cached) { + if (cached === undefined) { this.ClientFieldsCached.set(fieldid, FieldWaiting); SocketStub.SEND_FIELD_REQUEST(fieldid, action((field: Field | undefined) => { let cached = this.ClientFieldsCached.get(fieldid); - if (cached != FieldWaiting) + if (cached !== FieldWaiting) cb(cached); else { if (field) { @@ -36,23 +36,22 @@ export class Server { cb(field) } })); - } else if (cached != FieldWaiting) { + } else if (cached !== FieldWaiting) { setTimeout(() => cb(cached as Field), 0); } else { - reaction(() => { - return this.ClientFieldsCached.get(fieldid); - }, (field, reaction) => { - if (field !== "<Waiting>") { - reaction.dispose() - cb(field) - } - }) + reaction(() => this.ClientFieldsCached.get(fieldid), + (field, reaction) => { + if (field !== FieldWaiting) { + reaction.dispose() + cb(field) + } + }) } } if (callback) { fn(callback); } else { - return new Promise(res => fn(res)); + return new Promise(fn); } } @@ -92,24 +91,23 @@ export class Server { } } } - reaction(() => { - return waitingFieldIds.map(id => this.ClientFieldsCached.get(id)); - }, (cachedFields, reaction) => { - if (!cachedFields.some(field => !field || field === FieldWaiting)) { - reaction.dispose(); - for (let field of cachedFields) { - let realField = field as Field; - existingFields[realField.Id] = realField; + reaction(() => waitingFieldIds.map(id => this.ClientFieldsCached.get(id)), + (cachedFields, reaction) => { + if (!cachedFields.some(field => !field)) { + reaction.dispose(); + for (let field of cachedFields) { + let realField = field as Field; + existingFields[realField.Id] = realField; + } + cb({ ...fields, ...existingFields }) } - cb({ ...fields, ...existingFields }) - } - }, { fireImmediately: true }) + }, { fireImmediately: true }) })); }; if (callback) { fn(callback); } else { - return new Promise(res => fn(res)); + return new Promise(fn); } } @@ -153,19 +151,19 @@ export class Server { @action private static cacheField(clientField: Field) { var cached = this.ClientFieldsCached.get(clientField.Id); - if (!cached || cached == FieldWaiting) { + if (!cached) { this.ClientFieldsCached.set(clientField.Id, clientField); } else { // probably should overwrite the values within any field that was already here... } - return this.ClientFieldsCached.get(clientField.Id) as Field; + return this.ClientFieldsCached.get(clientField.Id); } @action static updateField(field: { _id: string, data: any, type: Types }) { if (Server.ClientFieldsCached.has(field._id)) { var f = Server.ClientFieldsCached.get(field._id); - if (f && f != FieldWaiting) { + if (f) { // console.log("Applying : " + field._id); f.UpdateFromServer(field.data); f.init(() => { }); diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts index 5045037c5..c3cd8bee6 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -55,7 +55,7 @@ export class SocketStub { if (callback) { fn(callback); } else { - return new Promise(res => fn(res)) + return new Promise(fn) } } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1f0744782..1b1485c12 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -17,7 +17,8 @@ import { HistogramOperation } from "../northstar/operations/HistogramOperation"; import { Server } from "../Server"; import { CollectionPDFView } from "../views/collections/CollectionPDFView"; import { CollectionVideoView } from "../views/collections/CollectionVideoView"; -import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; +import { CollectionView } from "../views/collections/CollectionView"; +import { CollectionViewType } from "../views/collections/CollectionBaseView"; import { AudioBox } from "../views/nodes/AudioBox"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 49ebe5ebc..5938bf9b1 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -117,14 +117,14 @@ export class HistogramBox extends React.Component<FieldViewProps> { activateHistogramOperation(catalog?: Catalog) { if (catalog) { - this.props.doc.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => { + this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => { this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty; if (this.HistoOp != HistogramOperation.Empty) { - reaction(() => this.props.doc.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true }); - reaction(() => this.props.doc.GetList(KeyStore.BrushingDocs, []).length, + reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true }); + reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length, () => { - let brushingDocs = this.props.doc.GetList(KeyStore.BrushingDocs, [] as Document[]); - let proto = this.props.doc.GetPrototype() as Document; + let brushingDocs = this.props.Document.GetList(KeyStore.BrushingDocs, [] as Document[]); + let proto = this.props.Document.GetPrototype() as Document; this.HistoOp.BrushLinks.splice(0, this.HistoOp.BrushLinks.length, ...brushingDocs.map((brush, i) => { brush.SetNumber(KeyStore.BackgroundColor, StyleConstants.BRUSH_COLORS[i % StyleConstants.BRUSH_COLORS.length]); let brushed = brush.GetList(KeyStore.BrushingDocs, [] as Document[]); @@ -139,8 +139,8 @@ export class HistogramBox extends React.Component<FieldViewProps> { render() { let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>"; let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>"; - var h = this.props.isTopMost ? this.PanelHeight : this.props.doc.GetNumber(KeyStore.Height, 0); - var w = this.props.isTopMost ? this.PanelWidth : this.props.doc.GetNumber(KeyStore.Width, 0); + var h = this.props.isTopMost ? this.PanelHeight : this.props.Document.GetNumber(KeyStore.Height, 0); + var w = this.props.isTopMost ? this.PanelWidth : this.props.Document.GetNumber(KeyStore.Width, 0); let loff = this.SizeConverter.LeftOffset; let toff = this.SizeConverter.TopOffset; let roff = this.SizeConverter.RightOffset; diff --git a/src/client/northstar/manager/Gateway.ts b/src/client/northstar/manager/Gateway.ts index 5ae5e4f47..3e72a50ae 100644 --- a/src/client/northstar/manager/Gateway.ts +++ b/src/client/northstar/manager/Gateway.ts @@ -246,18 +246,18 @@ export class Settings { else { this.ServerUrl = environment["SERVER_URL"] ? environment["SERVER_URL"] : document.URL; }*/ - this.ServerUrl = environment["SERVER_URL"] ? environment["SERVER_URL"] : document.URL; - this.ServerApiPath = environment["SERVER_API_PATH"]; - this.SampleSize = environment["SAMPLE_SIZE"]; - this.XBins = environment["X_BINS"]; - this.YBins = environment["Y_BINS"]; - this.SplashTimeInMS = environment["SPLASH_TIME_IN_MS"]; - this.ShowFpsCounter = environment["SHOW_FPS_COUNTER"]; - this.ShowShutdownButton = environment["SHOW_SHUTDOWN_BUTTON"]; - this.IsMenuFixed = environment["IS_MENU_FIXED"]; - this.IsDarpa = environment["IS_DARPA"]; - this.IsIGT = environment["IS_IGT"]; - this.DegreeOfParallelism = environment["DEGREE_OF_PARALLISM"]; + this.ServerUrl = environment.SERVER_URL ? environment.SERVER_URL : document.URL; + this.ServerApiPath = environment.SERVER_API_PATH; + this.SampleSize = environment.SAMPLE_SIZE; + this.XBins = environment.X_BINS; + this.YBins = environment.Y_BINS; + this.SplashTimeInMS = environment.SPLASH_TIME_IN_MS; + this.ShowFpsCounter = environment.SHOW_FPS_COUNTER; + this.ShowShutdownButton = environment.SHOW_SHUTDOWN_BUTTON; + this.IsMenuFixed = environment.IS_MENU_FIXED; + this.IsDarpa = environment.IS_DARPA; + this.IsIGT = environment.IS_IGT; + this.DegreeOfParallelism = environment.DEGREE_OF_PARALLISM; } public static get Instance(): Settings { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index bf59fbb43..fb489edb6 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -29,7 +29,7 @@ export class DocumentManager { public getAllDocumentViews(collection: Document) { return this.DocumentViews.filter(dv => - dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document == collection); + dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection); } public getDocumentView(toFind: Document): DocumentView | null { @@ -42,12 +42,12 @@ export class DocumentManager { let doc = view.props.Document; // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) { - if (Object.is(doc, toFind)) { + if (doc === toFind) { toReturn = view; return; } let docSrc = doc.GetT(KeyStore.Prototype, Document); - if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) { + if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) { toReturn = view; } }) @@ -63,11 +63,11 @@ export class DocumentManager { let doc = view.props.Document; // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) { - if (Object.is(doc, toFind)) { + if (doc === toFind) { toReturn.push(view); } else { let docSrc = doc.GetT(KeyStore.Prototype, Document); - if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) { + if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) { toReturn.push(view); } } @@ -80,11 +80,11 @@ export class DocumentManager { public get LinkedDocumentViews() { return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); - if (linksList && linksList != FieldWaiting && linksList.Data.length) { + if (linksList && linksList !== FieldWaiting && linksList.Data.length) { pairs.push(...linksList.Data.reduce((pairs, link) => { if (link instanceof Document) { let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); - if (linkToDoc && linkToDoc != FieldWaiting) { + if (linkToDoc && linkToDoc !== FieldWaiting) { DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { pairs.push({ a: dv, b: docView1, l: link }) }) diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index e8f8cce7c..02e0a18df 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -6,33 +6,26 @@ import { ImageField } from "../../fields/ImageField"; import { KeyStore } from "../../fields/KeyStore"; import { CollectionView } from "../views/collections/CollectionView"; import { DocumentView } from "../views/nodes/DocumentView"; +import { returnFalse } from "../../Utils"; -export function setupDrag( - _reference: React.RefObject<HTMLDivElement>, - docFunc: () => Document, - removeFunc: (containingCollection: CollectionView) => void = () => { } -) { - let onRowMove = action( - (e: PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); +export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction) { + let onRowMove = action((e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener("pointerup", onRowUp); - var dragData = new DragManager.DocumentDragData([docFunc()]); - dragData.removeDocument = removeFunc; - DragManager.StartDocumentDrag([_reference.current!], dragData); - } - ); - let onRowUp = action( - (e: PointerEvent): void => { - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener("pointerup", onRowUp); - } - ); + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener('pointerup', onRowUp); + var dragData = new DragManager.DocumentDragData([docFunc()]); + dragData.moveDocument = moveFunc; + DragManager.StartDocumentDrag([_reference.current!], dragData); + }); + let onRowUp = action((e: PointerEvent): void => { + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener('pointerup', onRowUp); + }); let onItemDown = (e: React.PointerEvent) => { // if (this.props.isSelected() || this.props.isTopMost) { - if (e.button == 0) { + if (e.button === 0) { e.stopPropagation(); if (e.shiftKey) { CollectionDockingView.Instance.StartOtherDrag([docFunc()], e); @@ -103,7 +96,7 @@ export namespace DragManager { "Element is already droppable, can't make it droppable again" ); } - element.dataset["canDrop"] = "true"; + element.dataset.canDrop = "true"; const handler = (e: Event) => { const ce = e as CustomEvent<DropEvent>; options.handlers.drop(e, ce.detail); @@ -111,10 +104,11 @@ export namespace DragManager { element.addEventListener("dashOnDrop", handler); return () => { element.removeEventListener("dashOnDrop", handler); - delete element.dataset["canDrop"]; + delete element.dataset.canDrop }; } + export type MoveFunction = (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; export class DocumentDragData { constructor(dragDoc: Document[]) { this.draggedDocuments = dragDoc; @@ -125,7 +119,7 @@ export namespace DragManager { xOffset?: number; yOffset?: number; aliasOnDrop?: boolean; - removeDocument?: (collectionDrop: CollectionView) => void; + moveDocument?: MoveFunction; [id: string]: any; } @@ -214,11 +208,11 @@ export namespace DragManager { let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField); if (pdfBox && pdfBox.childElementCount && thumbnail) { let img = new Image(); - img!.src = thumbnail.toString(); - img!.style.position = "absolute"; - img!.style.width = `${rect.width / scaleX}px`; - img!.style.height = `${rect.height / scaleY}px`; - pdfBox.replaceChild(img!, pdfBox.children[0]); + img.src = thumbnail.toString(); + img.style.position = "absolute"; + img.style.width = `${rect.width / scaleX}px`; + img.style.height = `${rect.height / scaleY}px`; + pdfBox.replaceChild(img, pdfBox.children[0]) } } @@ -287,9 +281,9 @@ export namespace DragManager { }); const target = document.elementFromPoint(e.x, e.y); removed.map(r => { - let dragEle: HTMLElement = r[0]!; - let parent: HTMLElement | null = r[1]; - if (parent) parent.appendChild(dragEle); + let dragEle = r[0]; + let parent = r[1]; + if (parent && dragEle) parent.appendChild(dragEle); }); if (target) { if (finishDrag) finishDrag(dragData); diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 4e97b9401..8aef44a3a 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -26,7 +26,7 @@ export interface ExecutableScript { } function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript { - const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error)); + const compiled = !(diagnostics && diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error)); let func: () => Opt<Field>; if (compiled && script) { @@ -40,7 +40,7 @@ function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [ paramNames.push(prop); params.push(scope[prop]); } - let thisParam = scope["this"]; + let thisParam = scope.this; let compiledFunction = new Function(...paramNames, script); func = function (): Opt<Field> { return compiledFunction.apply(thisParam, params) @@ -49,10 +49,8 @@ function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [ func = () => undefined; } - return Object.assign(func, - { - compiled - }); + Object.assign(func, { compiled }); + return func as ExecutableScript; } interface File { @@ -125,9 +123,9 @@ export function CompileScript(script: string, scope?: { [name: string]: any }, a } export function ToField(data: any): Opt<Field> { - if (typeof data == "string") { + if (typeof data === "string") { return new TextField(data); - } else if (typeof data == "number") { + } else if (typeof data === "number") { return new NumberField(data); } return undefined; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 958c14491..5e1fa9576 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -11,13 +11,20 @@ export namespace SelectionManager { SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it if (!ctrlPressed) { - manager.SelectedDocuments = []; + this.DeselectAll(); } if (manager.SelectedDocuments.indexOf(doc) === -1) { manager.SelectedDocuments.push(doc); + doc.props.onActiveChanged(true); } } + + @action + DeselectAll(): void { + manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false)) + manager.SelectedDocuments = []; + } } const manager = new Manager(); @@ -33,13 +40,13 @@ export namespace SelectionManager { export function DeselectAll(except?: Document): void { let found: DocumentView | undefined = undefined; if (except) { - for (let i = 0; i < manager.SelectedDocuments.length; i++) { - let view = manager.SelectedDocuments[i]; - if (view.props.Document == except) found = view; + for (const view of manager.SelectedDocuments) { + if (view.props.Document === except) found = view; } } - manager.SelectedDocuments.length = 0; - if (found) manager.SelectedDocuments.push(found); + + manager.DeselectAll() + if (found) manager.SelectDoc(found, false); } export function SelectedDocuments(): Array<DocumentView> { diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts index 3e1039166..54effd512 100644 --- a/src/client/util/Transform.ts +++ b/src/client/util/Transform.ts @@ -3,7 +3,7 @@ export class Transform { private _translateY: number = 0; private _scale: number = 1; - static get Identity(): Transform { + static Identity(): Transform { return new Transform(0, 0, 1); } @@ -62,33 +62,19 @@ export class Transform { return this; } - translated = (x: number, y: number): Transform => { - return this.copy().translate(x, y); - } + translated = (x: number, y: number): Transform => this.copy().translate(x, y) - preTranslated = (x: number, y: number): Transform => { - return this.copy().preTranslate(x, y); - } + preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y) - scaled = (scale: number): Transform => { - return this.copy().scale(scale); - } + scaled = (scale: number): Transform => this.copy().scale(scale) - scaledAbout = (scale: number, x: number, y: number): Transform => { - return this.copy().scaleAbout(scale, x, y); - } + scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y) - preScaled = (scale: number): Transform => { - return this.copy().preScale(scale); - } + preScaled = (scale: number): Transform => this.copy().preScale(scale) - transformed = (transform: Transform): Transform => { - return this.copy().transform(transform); - } + transformed = (transform: Transform): Transform => this.copy().transform(transform) - preTransformed = (transform: Transform): Transform => { - return this.copy().preTransform(transform); - } + preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform) transformPoint = (x: number, y: number): [number, number] => { x *= this._scale; @@ -98,9 +84,7 @@ export class Transform { return [x, y]; } - transformDirection = (x: number, y: number): [number, number] => { - return [x * this._scale, y * this._scale]; - } + transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale] transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } { [x, y] = this.transformPoint(x, y); @@ -108,12 +92,8 @@ export class Transform { return { x, y, width, height }; } - inverse = () => { - return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale) - } + inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale) - copy = () => { - return new Transform(this._translateX, this._translateY, this._scale); - } + copy = () => new Transform(this._translateX, this._translateY, this._scale) }
\ No newline at end of file diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts index 0714a7f5c..1b251da25 100644 --- a/src/client/util/TypedEvent.ts +++ b/src/client/util/TypedEvent.ts @@ -36,7 +36,5 @@ export class TypedEvent<T> { this.listenersOncer = []; } - pipe = (te: TypedEvent<T>): Disposable => { - return this.on((e) => te.emit(e)); - } + pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e)) }
\ No newline at end of file diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 6d1b2f1b8..1e5028375 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -159,8 +159,8 @@ export namespace UndoManager { } undoing = true; - for (let i = 0; i < commands.length; i++) { - commands[i].redo(); + for (const command of commands) { + command.redo(); } undoing = false; diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 4f69053b1..47c3481b2 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -181,7 +181,7 @@ declare class Key extends Field { Copy(): Field; ToScriptString(): string; } -declare type FIELD_WAITING = "<Waiting>"; +declare type FIELD_WAITING = null; declare type Opt<T> = T | undefined; declare type FieldValue<T> = Opt<T> | FIELD_WAITING; // @ts-ignore diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8bf1a42d1..99588480d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -117,16 +117,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> dragData.aliasOnDrop = false; dragData.xOffset = e.x - left; dragData.yOffset = e.y - top; - dragData.removeDocument = (dropCollectionView: CollectionView) => - dragData.draggedDocuments.map(d => { - if (dragDocView.props.RemoveDocument && dragDocView.props.ContainingCollectionView !== dropCollectionView) { - dragDocView.props.RemoveDocument(d); - } - }); + let move = SelectionManager.SelectedDocuments()[0].props.moveDocument; + dragData.moveDocument = move; this._dragging = true; document.removeEventListener("pointermove", this.onBackgroundMove); document.removeEventListener("pointerup", this.onBackgroundUp); - DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => (docView as any)._mainCont!.current!), dragData, { + DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentRef.current!), dragData, { handlers: { dragComplete: action(() => this._dragging = false), }, diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 29bf6add7..982aacdea 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -38,7 +38,7 @@ export class EditableView extends React.Component<EditableProps> { @action onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key == "Enter") { + if (e.key === "Enter") { if (!e.ctrlKey) { if (this.props.SetValue(e.currentTarget.value)) { this.editing = false; @@ -47,7 +47,7 @@ export class EditableView extends React.Component<EditableProps> { this.props.OnFillDown(e.currentTarget.value); this.editing = false; } - } else if (e.key == "Escape") { + } else if (e.key === "Escape") { this.editing = false; } } diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 45ca52d00..4b3ea48c5 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -56,7 +56,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { @action onPointerDown = (e: React.PointerEvent): void => { - if (e.button != 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) { + if (e.button !== 0 || e.altKey || e.ctrlKey || InkingControl.Instance.selectedTool === InkTool.None) { return; } document.addEventListener("pointermove", this.onPointerMove, true); @@ -64,7 +64,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { e.stopPropagation(); e.preventDefault(); - if (InkingControl.Instance.selectedTool != InkTool.Eraser) { + if (InkingControl.Instance.selectedTool !== InkTool.Eraser) { // start the new line, saves a uuid to represent the field of the stroke this._currentStrokeId = Utils.GenerateGuid(); this.inkData.set(this._currentStrokeId, { @@ -94,7 +94,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { onPointerMove = (e: PointerEvent): void => { e.stopPropagation() e.preventDefault(); - if (InkingControl.Instance.selectedTool != InkTool.Eraser) { + if (InkingControl.Instance.selectedTool !== InkTool.Eraser) { let data = this.inkData; // add points to new line as it is being drawn let strokeData = data.get(this._currentStrokeId); if (strokeData) { @@ -121,7 +121,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { get drawnPaths() { let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1) let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { - if (strokeData.page == -1 || strokeData.page == curPage) + if (strokeData.page === -1 || strokeData.page === curPage) paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData} offsetX={this.maxCanvasDim - this.inkMidX} offsetY={this.maxCanvasDim - this.inkMidY} @@ -131,16 +131,16 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { }, [] as JSX.Element[]); return [<svg className={`inkingCanvas-paths-markers`} key="Markers" style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} > - {paths.filter(path => path.props.tool == InkTool.Highlighter)} + {paths.filter(path => path.props.tool === InkTool.Highlighter)} </svg>, <svg className={`inkingCanvas-paths-ink`} key="Pens" style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}> - {paths.filter(path => path.props.tool != InkTool.Highlighter)} + {paths.filter(path => path.props.tool !== InkTool.Highlighter)} </svg>]; } render() { - let svgCanvasStyle = InkingControl.Instance.selectedTool != InkTool.None ? "canSelect" : "noSelect"; + let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect"; return ( <div className="inkingCanvas" > <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} /> diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index c1519dff8..13f0a0acc 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -36,9 +36,9 @@ export class InkingControl extends React.Component { @action switchColor = (color: ColorResult): void => { this._selectedColor = color.hex; - if (SelectionManager.SelectedDocuments().length == 1) { + if (SelectionManager.SelectedDocuments().length === 1) { var sdoc = SelectionManager.SelectedDocuments()[0]; - if (sdoc.props.ContainingCollectionView && sdoc.props.ContainingCollectionView) { + if (sdoc.props.ContainingCollectionView) { sdoc.props.Document.SetDataOnPrototype(KeyStore.BackgroundColor, color.hex, TextField); } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 615f8af7e..12b15a3f0 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -30,7 +30,7 @@ export class InkingStroke extends React.Component<StrokeProps> { } parseData = (line: Array<{ x: number, y: number }>): string => { - return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L "); + return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ") } createStyle() { @@ -49,7 +49,7 @@ export class InkingStroke extends React.Component<StrokeProps> { let pathStyle = this.createStyle(); let pathData = this.parseData(this.props.line); - let pointerEvents: any = InkingControl.Instance.selectedTool == InkTool.Eraser ? "all" : "none"; + let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none"; return ( <path d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round" onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} /> diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 6f66f8f38..33759d38a 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -6,7 +6,7 @@ import { Document } from '../../fields/Document'; import { KeyStore } from '../../fields/KeyStore'; import "./Main.scss"; import { MessageStore } from '../../server/Message'; -import { Utils } from '../../Utils'; +import { Utils, returnTrue, emptyFunction } from '../../Utils'; import * as request from 'request' import * as rp from 'request-promise' import { Documents } from '../documents/Documents'; @@ -204,15 +204,12 @@ export class Main extends React.Component { @observable workspacesShown: boolean = false; - areWorkspacesShown = () => { - return this.workspacesShown; - } + areWorkspacesShown = () => this.workspacesShown @action toggleWorkspaces = () => { this.workspacesShown = !this.workspacesShown; } - screenToLocalTransform = () => Transform.Identity pwidthFunc = () => this.pwidth; pheightFunc = () => this.pheight; focusDocument = (doc: Document) => { } @@ -222,15 +219,17 @@ export class Main extends React.Component { get mainContent() { return !this.mainContainer ? (null) : <DocumentView Document={this.mainContainer} - AddDocument={undefined} - RemoveDocument={undefined} - ScreenToLocalTransform={this.screenToLocalTransform} + addDocument={undefined} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} ContentScaling={this.noScaling} PanelWidth={this.pwidthFunc} PanelHeight={this.pheightFunc} isTopMost={true} - SelectOnLoad={false} + selectOnLoad={false} focus={this.focusDocument} + parentActive={returnTrue} + onActiveChanged={emptyFunction} ContainingCollectionView={undefined} /> } @@ -372,8 +371,8 @@ export class Main extends React.Component { } } -Documents.initProtos().then(() => { - return CurrentUserUtils.loadCurrentUser() -}).then(() => { +(async () => { + await Documents.initProtos() + await CurrentUserUtils.loadCurrentUser() ReactDOM.render(<Main />, document.getElementById('root')); -}); +})() diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx new file mode 100644 index 000000000..ee752a2ca --- /dev/null +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -0,0 +1,166 @@ +import * as React from 'react'; +import { FieldViewProps } from '../nodes/FieldView'; +import { KeyStore } from '../../../fields/KeyStore'; +import { NumberField } from '../../../fields/NumberField'; +import { FieldWaiting, Field, FieldValue } from '../../../fields/Field'; +import { ContextMenu } from '../ContextMenu'; +import { SelectionManager } from '../../util/SelectionManager'; +import { Document } from '../../../fields/Document'; +import { ListField } from '../../../fields/ListField'; +import { action } from 'mobx'; +import { Transform } from '../../util/Transform'; +import { observer } from 'mobx-react'; + +export enum CollectionViewType { + Invalid, + Freeform, + Schema, + Docking, + Tree, +} + +export interface CollectionRenderProps { + addDocument: (document: Document, allowDuplicates?: boolean) => boolean; + removeDocument: (document: Document) => boolean; + moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; + active: () => boolean; + onActiveChanged: (isActive: boolean) => void; +} + +export interface CollectionViewProps extends FieldViewProps { + onContextMenu?: (e: React.MouseEvent) => void; + children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null; + className?: string; + contentRef?: React.Ref<HTMLDivElement>; +} + +export const COLLECTION_BORDER_WIDTH = 1; + +@observer +export class CollectionBaseView extends React.Component<CollectionViewProps> { + get collectionViewType(): CollectionViewType { + let Document = this.props.Document; + let viewField = Document.GetT(KeyStore.ViewType, NumberField); + if (viewField === FieldWaiting) { + return CollectionViewType.Invalid; + } else if (viewField) { + return viewField.Data; + } else { + return CollectionViewType.Freeform; + } + } + + active = (): boolean => { + var isSelected = this.props.isSelected(); + var topMost = this.props.isTopMost; + return isSelected || this._isChildActive || topMost; + } + + //TODO should this be observable? + private _isChildActive = false; + onActiveChanged = (isActive: boolean) => { + this._isChildActive = isActive; + this.props.onActiveChanged(isActive); + } + + createsCycle(documentToAdd: Document, containerDocument: Document): boolean { + let data = documentToAdd.GetList<Document>(KeyStore.Data, []); + for (let i = 0; i < data.length; i++) { + if (this.createsCycle(data[i], containerDocument)) + return true; + } + let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []); + for (let i = 0; i < annots.length; i++) { + if (this.createsCycle(annots[i], containerDocument)) + return true; + } + for (let containerProto: FieldValue<Document> = containerDocument; containerProto && containerProto !== FieldWaiting; containerProto = containerProto.GetPrototype()) { + if (containerProto.Id === documentToAdd.Id) + return true; + } + return false; + } + + @action.bound + addDocument(doc: Document, allowDuplicates: boolean = false): boolean { + let props = this.props; + var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); + doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); + if (curPage >= 0) { + doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document); + } + if (props.Document.Get(props.fieldKey) instanceof Field) { + //TODO This won't create the field if it doesn't already exist + const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) + if (!this.createsCycle(doc, props.Document)) { + if (!value.some(v => v.Id === doc.Id) || allowDuplicates) + value.push(doc); + } + else + return false; + } else { + let proto = props.Document.GetPrototype(); + if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) { + props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); + } + else + return false; + } + return true; + } + + @action.bound + removeDocument(doc: Document): boolean { + const props = this.props; + //TODO This won't create the field if it doesn't already exist + const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) + let index = -1; + for (let i = 0; i < value.length; i++) { + if (value[i].Id === doc.Id) { + index = i; + break; + } + } + doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => { + if (annotationOn === props.Document) { + doc.Set(KeyStore.AnnotationOn, undefined, true); + } + }) + + if (index !== -1) { + value.splice(index, 1) + + // SelectionManager.DeselectAll() + ContextMenu.Instance.clearItems() + return true; + } + return false + } + + @action.bound + moveDocument(doc: Document, targetCollection: Document, addDocument: (doc: Document) => boolean): boolean { + if (this.props.Document === targetCollection) { + return true; + } + if (this.removeDocument(doc)) { + return addDocument(doc); + } + return false; + } + + render() { + const props: CollectionRenderProps = { + addDocument: this.addDocument, + removeDocument: this.removeDocument, + moveDocument: this.moveDocument, + active: this.active, + onActiveChanged: this.onActiveChanged, + } + return ( + <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}> + {this.props.children(this.collectionViewType, props)} + </div> + ) + } + +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 39e0dd989..1b0ad0bee 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -8,12 +8,12 @@ import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import Measure from "react-measure"; import { FieldId, Opt, Field } from "../../../fields/Field"; -import { Utils } from "../../../Utils"; +import { Utils, returnTrue, emptyFunction } from "../../../Utils"; import { Server } from "../../Server"; import { undoBatch } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; import React = require("react"); import { SubCollectionViewProps } from "./CollectionViewBase"; import { ServerUtils } from "../../../server/ServerUtil"; @@ -311,14 +311,16 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { var content = <div className="collectionDockingView-content" ref={this._mainCont}> <DocumentView key={this._document.Id} Document={this._document} - AddDocument={undefined} - RemoveDocument={undefined} + addDocument={undefined} + removeDocument={undefined} ContentScaling={this._contentScaling} PanelWidth={this._nativeWidth} PanelHeight={this._nativeHeight} ScreenToLocalTransform={this.ScreenToLocalTransform} isTopMost={true} - SelectOnLoad={false} + selectOnLoad={false} + parentActive={returnTrue} + onActiveChanged={emptyFunction} focus={(doc: Document) => { }} ContainingCollectionView={undefined} /> </div> diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 4d2daf149..14ed70b8c 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,22 +1,20 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import { ContextMenu } from "../ContextMenu"; -import { CollectionView, CollectionViewType } from "./CollectionView"; import { CollectionViewProps } from "./CollectionViewBase"; import "./CollectionPDFView.scss" import React = require("react"); -import { FieldId } from "../../../fields/Field"; +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; +import { FieldView } from "../nodes/FieldView"; +import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView"; @observer export class CollectionPDFView extends React.Component<CollectionViewProps> { public static LayoutString(fieldKey: string = "DataKey") { - return `<${CollectionPDFView.name} Document={Document} - ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; + return FieldView.LayoutString(CollectionPDFView, fieldKey); } private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); } @@ -30,30 +28,31 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> { <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}> <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button> <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button> - </div>); + </div> + ); } - // "inherited" CollectionView API starts here... - @observable - public SelectedDocs: FieldId[] = [] - public active: () => boolean = () => CollectionView.Active(this); - - addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } - removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } - - specificContextMenu = (e: React.MouseEvent): void => { + onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "PDFOptions", event: () => { } }); } } - get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; } - get subView(): any { return CollectionView.SubView(this); } + private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { + let props = { ...renderProps, ...this.props }; + return ( + <> + <CollectionFreeFormView {...props} /> + {this.props.isSelected() ? this.uIButtons : (null)} + </> + ) + } render() { - return (<div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}> - {this.subView} - {this.props.isSelected() ? this.uIButtons : (null)} - </div>) + return ( + <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}> + {this.subView} + </CollectionBaseView> + ) } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 0ff6c3b40..9dade94f1 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -22,9 +22,11 @@ import { EditableView } from "../EditableView"; import { DocumentView } from "../nodes/DocumentView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; -import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { CollectionView } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; import { TextField } from "../../../fields/TextField"; +import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; +import { emptyFunction, returnFalse } from "../../../Utils"; // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 @@ -73,19 +75,22 @@ export class CollectionSchemaView extends CollectionViewBase { renderCell = (rowProps: CellInfo) => { let props: FieldViewProps = { - doc: rowProps.value[0], + Document: rowProps.value[0], fieldKey: rowProps.value[1], isSelected: () => false, select: () => { }, isTopMost: false, - bindings: {}, selectOnLoad: false, + ScreenToLocalTransform: Transform.Identity, + focus: emptyFunction, + active: returnFalse, + onActiveChanged: emptyFunction, } let contents = ( <FieldView {...props} /> ) let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc)); + let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument); let applyToDoc = (doc: Document, value: string) => { let script = CompileScript(value, { this: doc }, true); if (!script.compiled) { @@ -105,21 +110,19 @@ export class CollectionSchemaView extends CollectionViewBase { return false; } return ( - <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.doc.Id} ref={reference}> + <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}> <EditableView display={"inline"} contents={contents} height={56} GetValue={() => { - let field = props.doc.Get(props.fieldKey); + let field = props.Document.Get(props.fieldKey); if (field && field instanceof Field) { return field.ToScriptString(); } return field || ""; }} - SetValue={(value: string) => { - return applyToDoc(props.doc, value); - }} + SetValue={(value: string) => applyToDoc(props.Document, value)} OnFillDown={(value: string) => { this.props.Document.GetTAsync<ListField<Document>>(this.props.fieldKey, ListField).then((val) => { if (val) { @@ -147,7 +150,7 @@ export class CollectionSchemaView extends CollectionViewBase { } }), style: { - background: rowInfo.index == this._selectedIndex ? "lightGray" : "white", + background: rowInfo.index === this._selectedIndex ? "lightGray" : "white", //color: rowInfo.index == this._selectedIndex ? "white" : "black" } }; @@ -176,8 +179,8 @@ export class CollectionSchemaView extends CollectionViewBase { @action toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => { this._startSplitPercent = this.splitPercentage; - if (this._startSplitPercent == this.splitPercentage) { - this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0); + if (this._startSplitPercent === this.splitPercentage) { + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); } } @@ -206,8 +209,8 @@ export class CollectionSchemaView extends CollectionViewBase { onDividerUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onDividerMove); document.removeEventListener('pointerup', this.onDividerUp); - if (this._startSplitPercent == this.splitPercentage) { - this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0); + if (this._startSplitPercent === this.splitPercentage) { + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); } } onDividerDown = (e: React.PointerEvent) => { @@ -235,12 +238,8 @@ export class CollectionSchemaView extends CollectionViewBase { getContentScaling = (): number => this._contentScaling; getPanelWidth = (): number => this._panelWidth; getPanelHeight = (): number => this._panelHeight; - getTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - } - getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - } + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling) + getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling) focusDocument = (doc: Document) => { } @@ -291,28 +290,30 @@ export class CollectionSchemaView extends CollectionViewBase { let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined; // let doc = CompileScript(this.previewScript, { this: selected }, true)(); - let content = this._selectedIndex == -1 || !selected ? (null) : ( + let content = this._selectedIndex === -1 || !selected ? (null) : ( <Measure onResize={this.setScaling}> {({ measureRef }) => <div className="collectionSchemaView-content" ref={measureRef}> - {doc instanceof Document ? <DocumentView Document={doc} - AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument} - isTopMost={false} - SelectOnLoad={false} - ScreenToLocalTransform={this.getPreviewTransform} - ContentScaling={this.getContentScaling} - PanelWidth={this.getPanelWidth} - PanelHeight={this.getPanelHeight} - ContainingCollectionView={this.props.CollectionView} - focus={this.focusDocument} - /> : null} + {doc instanceof Document ? + <DocumentView Document={doc} + addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} + isTopMost={false} + selectOnLoad={false} + ScreenToLocalTransform={this.getPreviewTransform} + ContentScaling={this.getContentScaling} + PanelWidth={this.getPanelWidth} + PanelHeight={this.getPanelHeight} + ContainingCollectionView={undefined} + focus={this.focusDocument} + parentActive={this.props.active} + onActiveChanged={this.props.onActiveChanged} /> : null} <input value={this.previewScript} onChange={this.onPreviewScriptChange} style={{ position: 'absolute', bottom: '0px' }} /> </div> } </Measure> ) - let dividerDragger = this.splitPercentage == 0 ? (null) : + let dividerDragger = this.splitPercentage === 0 ? (null) : <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} /> //options button and menu @@ -322,12 +323,11 @@ export class CollectionSchemaView extends CollectionViewBase { <div id="schema-options-header"><h5><b>Options</b></h5></div> <div id="options-flyout-div"> <h6 className="schema-options-subHeader">Preview Window</h6> - <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage != 0} onChange={this.toggleExpander} /> Show Preview </div> + <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div> <h6 className="schema-options-subHeader" >Displayed Columns</h6> <ul id="schema-col-checklist" > - {Array.from(Object.keys(allKeys)).map(item => { - return (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />) - })} + {Array.from(Object.keys(allKeys)).map(item => + (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />))} </ul> <input value={this.newKeyName} onChange={this.newKeyChange} /> <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button> diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 70790af18..32d74164d 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -7,17 +7,19 @@ import { Document } from "../../../fields/Document"; import { FieldWaiting } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { setupDrag } from "../../util/DragManager"; +import { setupDrag, DragManager } from "../../util/DragManager"; import { EditableView } from "../EditableView"; import "./CollectionTreeView.scss"; -import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { CollectionView } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; import React = require("react") +import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView'; export interface TreeViewProps { document: Document; deleteDoc: (doc: Document) => void; + moveDocument: DragManager.MoveFunction; } export enum BulletType { @@ -48,6 +50,16 @@ class TreeView extends React.Component<TreeViewProps> { } } + @action + move: DragManager.MoveFunction = (document, target, addDoc) => { + if (this.props.document === target) { + return true; + } + //TODO This should check if it was removed + this.remove(document) + return addDoc(document); + } + renderBullet(type: BulletType) { let onClicked = action(() => this._collapsed = !this._collapsed); let bullet: IconProp | undefined = undefined; @@ -63,7 +75,7 @@ class TreeView extends React.Component<TreeViewProps> { */ renderTitle() { let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document)); + let onItemDown = setupDrag(reference, () => this.props.document, this.props.moveDocument); let editableView = (titleString: string) => (<EditableView display={"inline"} @@ -91,7 +103,7 @@ class TreeView extends React.Component<TreeViewProps> { if (!this._collapsed) { bulletType = BulletType.Collapsible; childElements = <ul> - {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)} + {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} moveDocument={this.move} />)} </ul> } else bulletType = BulletType.Collapsed; @@ -121,7 +133,7 @@ export class CollectionTreeView extends CollectionViewBase { var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); let childrenElement = !children || children === FieldWaiting ? (null) : (children.Data.map(value => - <TreeView document={value} key={value.Id} deleteDoc={this.remove} />) + <TreeView document={value} key={value.Id} deleteDoc={this.remove} moveDocument={this.props.moveDocument} />) ) return ( diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 470a853e3..3ab6db5ef 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -3,11 +3,14 @@ import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import { ContextMenu } from "../ContextMenu"; -import { CollectionView, CollectionViewType } from "./CollectionView"; +import { CollectionView } from "./CollectionView"; +import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView"; import { CollectionViewProps } from "./CollectionViewBase"; import React = require("react"); import { FieldId } from "../../../fields/Field"; import "./CollectionVideoView.scss" +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; +import { FieldView } from "../nodes/FieldView"; @observer @@ -19,9 +22,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> { @observable _isPlaying: boolean = false; public static LayoutString(fieldKey: string = "DataKey") { - return `<${CollectionVideoView.name} Document={Document} - ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; + return FieldView.LayoutString(CollectionVideoView, fieldKey); } private get uIButtons() { let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]); @@ -42,7 +43,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> { @action mainCont = (ele: HTMLDivElement | null) => { if (ele) { - this._player = ele!.getElementsByTagName("video")[0]; + this._player = ele.getElementsByTagName("video")[0]; if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) { this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1); } @@ -101,30 +102,27 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> { } - // "inherited" CollectionView API starts here... - - @observable - public SelectedDocs: FieldId[] = [] - public active: () => boolean = () => CollectionView.Active(this); - - addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } - removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } - - specificContextMenu = (e: React.MouseEvent): void => { + onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } }); } } - get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; } - get subView(): any { return CollectionView.SubView(this); } - + private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { + let props = { ...renderProps, ...this.props }; + return ( + <> + <CollectionFreeFormView {...props} /> + {this.props.isSelected() ? this.uIButtons : (null)} + </> + ) + } render() { trace(); - return (<div className="collectionVideoView-cont" ref={this.mainCont} onContextMenu={this.specificContextMenu}> - {this.subView} - {this.props.isSelected() ? this.uIButtons : (null)} - </div>) + return ( + <CollectionBaseView {...this.props} className="collectionVideoView-cont" contentRef={this.mainCont} onContextMenu={this.onContextMenu}> + {this.subView} + </CollectionBaseView>) } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 014aa1d8f..5b4caf58d 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,138 +1,33 @@ -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../../fields/Document"; -import { ListField } from "../../../fields/ListField"; -import { SelectionManager } from "../../util/SelectionManager"; -import { ContextMenu } from "../ContextMenu"; -import React = require("react"); -import { KeyStore } from "../../../fields/KeyStore"; -import { NumberField } from "../../../fields/NumberField"; -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import { CollectionDockingView } from "./CollectionDockingView"; -import { CollectionSchemaView } from "./CollectionSchemaView"; -import { CollectionViewProps } from "./CollectionViewBase"; -import { CollectionTreeView } from "./CollectionTreeView"; -import { Field, FieldId, FieldWaiting } from "../../../fields/Field"; -import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; - -export enum CollectionViewType { - Invalid, - Freeform, - Schema, - Docking, - Tree -} - -export const COLLECTION_BORDER_WIDTH = 1; +import * as React from 'react' +import { FieldViewProps, FieldView } from '../nodes/FieldView'; +import { CollectionBaseView, CollectionViewType, CollectionRenderProps } from './CollectionBaseView'; +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { CollectionSchemaView } from './CollectionSchemaView'; +import { CollectionDockingView } from './CollectionDockingView'; +import { CollectionTreeView } from './CollectionTreeView'; +import { ContextMenu } from '../ContextMenu'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { KeyStore } from '../../../fields/KeyStore'; +import { observer } from 'mobx-react'; @observer -export class CollectionView extends React.Component<CollectionViewProps> { - - public static LayoutString(fieldKey: string = "DataKey") { - return `<${CollectionView.name} Document={Document} - ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`; - } - - @observable - public SelectedDocs: FieldId[] = []; - public active: () => boolean = () => CollectionView.Active(this); - addDocument = (doc: Document, allowDuplicates: boolean): boolean => { return CollectionView.AddDocument(this.props, doc, allowDuplicates); } - removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); } - get subView() { return CollectionView.SubView(this); } - - public static Active(self: CollectionView): boolean { - var isSelected = self.props.isSelected(); - var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == self); - var topMost = self.props.isTopMost; - return isSelected || childSelected || topMost; - } - - static createsCycle(documentToAdd: Document, containerDocument: Document): boolean { - let data = documentToAdd.GetList<Document>(KeyStore.Data, []); - for (let i = 0; i < data.length; i++) { - if (CollectionView.createsCycle(data[i], containerDocument)) - return true; - } - let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []); - for (let i = 0; i < annots.length; i++) { - if (CollectionView.createsCycle(annots[i], containerDocument)) - return true; - } - for (let containerProto: any = containerDocument; containerProto && containerProto != FieldWaiting; containerProto = containerProto.GetPrototype()) { - if (containerProto.Id == documentToAdd.Id) - return true; - } - return false; - } - - @action - public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean): boolean { - var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); - doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); - if (curPage >= 0) { - doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document); - } - if (props.Document.Get(props.fieldKey) instanceof Field) { - //TODO This won't create the field if it doesn't already exist - const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) - if (!CollectionView.createsCycle(doc, props.Document)) { - if (!value.some(v => v.Id == doc.Id) || allowDuplicates) - value.push(doc); - } - else - return false; - } else { - let proto = props.Document.GetPrototype(); - if (!proto || proto == FieldWaiting || !CollectionView.createsCycle(proto, doc)) { - props.Document.SetOnPrototype(props.fieldKey, new ListField([doc])); - } - else - return false; - } - return true; - } - - @action - public static RemoveDocument(props: CollectionViewProps, doc: Document): boolean { - //TODO This won't create the field if it doesn't already exist - const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>()) - let index = -1; - for (let i = 0; i < value.length; i++) { - if (value[i].Id == doc.Id) { - index = i; - break; - } - } - doc.GetTAsync(KeyStore.AnnotationOn, Document).then((annotationOn) => { - if (annotationOn == props.Document) { - doc.Set(KeyStore.AnnotationOn, undefined, true); - } - }) - - if (index !== -1) { - value.splice(index, 1) - - //SelectionManager.DeselectAll() - ContextMenu.Instance.clearItems() - return true; - } - return false - } - - get collectionViewType(): CollectionViewType { - let Document = this.props.Document; - let viewField = Document.GetT(KeyStore.ViewType, NumberField); - if (viewField === FieldWaiting) { - return CollectionViewType.Invalid; - } else if (viewField) { - return viewField.Data; - } else { - return CollectionViewType.Freeform; +export class CollectionView extends React.Component<FieldViewProps> { + public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(CollectionView, fieldStr) } + + private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { + let props = { ...this.props, ...renderProps }; + switch (type) { + case CollectionViewType.Schema: return (<CollectionSchemaView {...props} />) + case CollectionViewType.Docking: return (<CollectionDockingView {...props} />) + case CollectionViewType.Tree: return (<CollectionTreeView {...props} />) + case CollectionViewType.Freeform: + default: + return (<CollectionFreeFormView {...props} />) } + return (null); } - specificContextMenu = (e: React.MouseEvent): void => { + onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document.Id != CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) }) ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) }) @@ -140,20 +35,11 @@ export class CollectionView extends React.Component<CollectionViewProps> { } } - public static SubView(self: CollectionView) { - let subProps = { ...self.props, addDocument: self.addDocument, removeDocument: self.removeDocument, active: self.active, CollectionView: self } - switch (self.collectionViewType) { - case CollectionViewType.Freeform: return (<CollectionFreeFormView {...subProps} />) - case CollectionViewType.Schema: return (<CollectionSchemaView {...subProps} />) - case CollectionViewType.Docking: return (<CollectionDockingView {...subProps} />) - case CollectionViewType.Tree: return (<CollectionTreeView {...subProps} />) - } - return (null); - } - render() { - return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}> - {this.subView} - </div>) + return ( + <CollectionBaseView {...this.props} onContextMenu={this.onContextMenu}> + {this.SubView} + </CollectionBaseView> + ) } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 458bae7ab..b4784ae9c 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -17,27 +17,15 @@ import { NumberField } from "../../../fields/NumberField"; import request = require("request"); import { ServerUtils } from "../../../server/ServerUtil"; import { Server } from "../../Server"; -import { CollectionDockingView } from "./CollectionDockingView"; -import { runReactions } from "mobx/lib/internal"; +import { FieldViewProps } from "../nodes/FieldView"; -export interface CollectionViewProps { - fieldKey: Key; - Document: Document; - ScreenToLocalTransform: () => Transform; - isSelected: () => boolean; - isTopMost: boolean; - select: (ctrlPressed: boolean) => void; - bindings: any; - panelWidth: () => number; - panelHeight: () => number; - focus: (doc: Document) => void; +export interface CollectionViewProps extends FieldViewProps { + addDocument: (document: Document, allowDuplicates?: boolean) => boolean; + removeDocument: (document: Document) => boolean; + moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; } export interface SubCollectionViewProps extends CollectionViewProps { - active: () => boolean; - addDocument: (doc: Document, allowDuplicates: boolean) => boolean; - removeDocument: (doc: Document) => boolean; - CollectionView: CollectionView; } export type CursorEntry = TupleField<[string, string], [number, number]>; @@ -87,9 +75,14 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> de.data.draggedDocuments.map((draggedDocument: Document, i: number) => draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null))); } - let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true); - if (added && de.data.removeDocument && !de.data.aliasOnDrop) { - de.data.removeDocument(this.props.CollectionView); + let added = false; + if (de.data.aliasOnDrop) { + added = de.data.droppedDocuments.reduce((added, d) => added || this.props.addDocument(d), false); + } else if (de.data.moveDocument) { + const move = de.data.moveDocument; + added = de.data.droppedDocuments.reduce((added, d) => added || move(d, this.props.Document, this.props.addDocument), false) + } else { + added = de.data.droppedDocuments.reduce((added, d) => added || this.props.addDocument(d), false) } e.stopPropagation(); return added; @@ -154,8 +147,6 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> @action protected onDrop(e: React.DragEvent, options: DocumentOptions): void { - let that = this; - let html = e.dataTransfer.getData("text/html"); let text = e.dataTransfer.getData("text/plain"); @@ -165,7 +156,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> e.stopPropagation() e.preventDefault() - if (html && html.indexOf("<img") != 0 && !html.startsWith("<a")) { + if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) { console.log("not good"); let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 }); htmlDoc.SetText(KeyStore.DocumentText, text); @@ -173,10 +164,11 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> return; } + // tslint:disable-next-line:prefer-for-of for (let i = 0; i < e.dataTransfer.items.length; i++) { const upload = window.location.origin + RouteStore.upload; let item = e.dataTransfer.items[i]; - if (item.kind === "string" && item.type.indexOf("uri") != -1) { + if (item.kind === "string" && item.type.indexOf("uri") !== -1) { e.dataTransfer.items[i].getAsString(action((s: string) => { request.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s), (err, res, body) => { let type = res.headers["content-type"]; @@ -191,7 +183,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> })) } let type = item.type - if (item.kind == "file") { + if (item.kind === "file") { let file = item.getAsFile(); let formData = new FormData() @@ -202,19 +194,18 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> fetch(upload, { method: 'POST', body: formData - }).then((res: Response) => { - return res.json() - }).then(json => { + }).then(async (res: Response) => { + const json = await res.json(); json.map((file: any) => { let path = window.location.origin + file runInAction(() => { let doc = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300 }) - let docs = that.props.Document.GetT(KeyStore.Data, ListField); - if (docs != FieldWaiting) { + let docs = this.props.Document.GetT(KeyStore.Data, ListField); + if (docs !== FieldWaiting) { if (!docs) { docs = new ListField<Document>(); - that.props.Document.Set(KeyStore.Data, docs) + this.props.Document.Set(KeyStore.Data, docs) } if (doc) { docs.Data.push(doc); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index eb20b3100..b682ab303 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -37,11 +37,11 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP let srcTarg = (protoSrc ? protoSrc : srcDoc); let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => { let bdocs = brush.GetList(KeyStore.BrushingDocs, [] as Document[]); - return (bdocs.length == 0 || (bdocs[0] == dstTarg && bdocs[1] == srcTarg) || (bdocs[0] == srcTarg && bdocs[1] == dstTarg)) + return (bdocs.length === 0 || (bdocs[0] === dstTarg && bdocs[1] === srcTarg) || (bdocs[0] === srcTarg && bdocs[1] === dstTarg)) }); let brushAction = (field: ListField<Document>) => { let found = findBrush(field); - if (found != -1) + if (found !== -1) field.Data.splice(found, 1); }; if (Math.abs(x1 + x1w - x2) < 20 || Math.abs(x2 + x2w - x1) < 20) { @@ -50,7 +50,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title); linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); - brushAction = brushAction = (field: ListField<Document>) => (findBrush(field) == -1) && field.Data.push(linkDoc); + brushAction = brushAction = (field: ListField<Document>) => (findBrush(field) === -1) && field.Data.push(linkDoc); } dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); @@ -63,10 +63,10 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP documentAnchors(view: DocumentView) { let equalViews = [view]; let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document); - if (containerDoc && containerDoc != FieldWaiting && containerDoc instanceof Document) { - equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype() as Document) + if (containerDoc && containerDoc instanceof Document) { + equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!) } - return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document == this.props.Document); + return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document); } @computed @@ -78,14 +78,14 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document }))); possiblePairs.map(possiblePair => { if (!drawnPairs.reduce((found, drawnPair) => { - let match = (possiblePair.a == drawnPair.a && possiblePair.b == drawnPair.b); + let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b); if (match) { - if (!drawnPair.l.reduce((found, link) => found || link.Id == connection.l.Id, false)) + if (!drawnPair.l.reduce((found, link) => found || link.Id === connection.l.Id, false)) drawnPair.l.push(connection.l); } return match || found; }, false)) { - drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] as Document[] }); + drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] }); } }) return drawnPairs diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 19382e66f..f664f4e65 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,21 +1,8 @@ -import { action, computed, observable } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { FieldWaiting } from "../../../../fields/Field"; import { KeyStore } from "../../../../fields/KeyStore"; -import { TextField } from "../../../../fields/TextField"; -import { DragManager } from "../../../util/DragManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; -import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { DocumentViewProps } from "../../nodes/DocumentView"; -import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; -import { CollectionViewBase, CollectionViewProps, CursorEntry } from "../CollectionViewBase"; -import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import { CollectionViewProps, CursorEntry } from "../CollectionViewBase"; import "./CollectionFreeFormView.scss"; -import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1ddb84a99..19227f5e0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -11,7 +11,7 @@ import { InkingCanvas } from "../../InkingCanvas"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps } from "../../nodes/DocumentView"; -import { COLLECTION_BORDER_WIDTH } from "../CollectionView"; +import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView"; import { CollectionViewBase } from "../CollectionViewBase"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import "./CollectionFreeFormView.scss"; @@ -20,6 +20,8 @@ import React = require("react"); import v5 = require("uuid/v5"); import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import { PreviewCursor } from "./PreviewCursor"; +import { DocumentManager } from "../../../util/DocumentManager"; +import { SelectionManager } from "../../../util/SelectionManager"; import { NumberField } from "../../../../fields/NumberField"; @observer @@ -40,15 +42,20 @@ export class CollectionFreeFormView extends CollectionViewBase { } public selectDocuments = (docs: Document[]) => { - this.props.CollectionView.SelectedDocs.length = 0; - docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id)); + SelectionManager.DeselectAll; + docs.map(doc => { + const dv = DocumentManager.Instance.getDocumentView(doc); + if (dv) { + SelectionManager.SelectDoc(dv, true); + } + }) } public getActiveDocuments = () => { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => { var page = doc.GetNumber(KeyStore.Page, -1); - if (page == curPage || page == -1) { + if (page === curPage || page === -1) { active.push(doc); } return active; @@ -60,6 +67,8 @@ export class CollectionFreeFormView extends CollectionViewBase { @observable private _lastX: number = 0; @observable private _lastY: number = 0; + private outerElement?: HTMLDivElement; + @computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) } @computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) } @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); } @@ -67,38 +76,40 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); } - @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this.props.panelWidth() / 2 : 0; } // shift so pan position is at center of window for non-overlay collections - @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this.props.panelHeight() / 2 : 0; }// shift so pan position is at center of window for non-overlay collections + @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) && this.outerElement ? this.outerElement.clientWidth / 2 : 0; } // shift so pan position is at center of window for non-overlay collections + @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) && this.outerElement ? this.outerElement.clientHeight / 2 : 0; }// shift so pan position is at center of window for non-overlay collections @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (super.drop(e, de)) { - let droppedDocs = de.data.droppedDocuments as Document[]; - let xoff = de.data.xOffset as number || 0; - let yoff = de.data.yOffset as number || 0; - if (droppedDocs && droppedDocs.length) { - let screenX = de.x - xoff; - let screenY = de.y - yoff; - const [x, y] = this.getTransform().transformPoint(screenX, screenY); - let dragDoc = de.data.droppedDocuments[0]; - let dragX = dragDoc.GetNumber(KeyStore.X, 0); - let dragY = dragDoc.GetNumber(KeyStore.Y, 0); - droppedDocs.map(async d => { - let docX = d.GetNumber(KeyStore.X, 0); - let docY = d.GetNumber(KeyStore.Y, 0); - d.SetNumber(KeyStore.X, x + (docX - dragX)); - d.SetNumber(KeyStore.Y, y + (docY - dragY)); - let docW = await d.GetTAsync(KeyStore.Width, NumberField); - let docH = await d.GetTAsync(KeyStore.Height, NumberField); - if (!docW) { - d.SetNumber(KeyStore.Width, 300); - } - if (!docH) { - d.SetNumber(KeyStore.Height, 300); - } - this.bringToFront(d); - }) + if (de.data instanceof DragManager.DocumentDragData) { + let droppedDocs = de.data.droppedDocuments; + let xoff = de.data.xOffset as number || 0; + let yoff = de.data.yOffset as number || 0; + if (droppedDocs.length) { + let screenX = de.x - xoff; + let screenY = de.y - yoff; + const [x, y] = this.getTransform().transformPoint(screenX, screenY); + let dragDoc = droppedDocs[0]; + let dragX = dragDoc.GetNumber(KeyStore.X, 0); + let dragY = dragDoc.GetNumber(KeyStore.Y, 0); + droppedDocs.map(async d => { + let docX = d.GetNumber(KeyStore.X, 0); + let docY = d.GetNumber(KeyStore.Y, 0); + d.SetNumber(KeyStore.X, x + (docX - dragX)); + d.SetNumber(KeyStore.Y, y + (docY - dragY)); + let docW = await d.GetTAsync(KeyStore.Width, NumberField); + let docH = await d.GetTAsync(KeyStore.Height, NumberField); + if (!docW) { + d.SetNumber(KeyStore.Width, 300); + } + if (!docH) { + d.SetNumber(KeyStore.Height, 300); + } + this.bringToFront(d); + }) + } } return true; } @@ -114,7 +125,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerDown = (e: React.PointerEvent): void => { - if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) && this.props.active()) { + if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling !== 1)) || e.button === 0) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -136,7 +147,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble && this.props.active()) { - if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) { + if ((!this.isAnnotationOverlay || this.zoomScaling !== 1) && !e.shiftKey) { let x = this.props.Document.GetNumber(KeyStore.PanX, 0); let y = this.props.Document.GetNumber(KeyStore.PanY, 0); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); @@ -242,16 +253,19 @@ export class CollectionFreeFormView extends CollectionViewBase { getDocumentViewProps(document: Document): DocumentViewProps { return { Document: document, - AddDocument: this.props.addDocument, - RemoveDocument: this.props.removeDocument, + addDocument: this.props.addDocument, + removeDocument: this.props.removeDocument, + moveDocument: this.props.moveDocument, ScreenToLocalTransform: this.getTransform, isTopMost: false, - SelectOnLoad: document.Id == this._selectOnLoaded, + selectOnLoad: document.Id === this._selectOnLoaded, PanelWidth: document.Width, PanelHeight: document.Height, ContentScaling: this.noScaling, - ContainingCollectionView: this.props.CollectionView, - focus: this.focusDocument + ContainingCollectionView: undefined, + focus: this.focusDocument, + parentActive: this.props.active, + onActiveChanged: this.props.active, } } @@ -260,7 +274,7 @@ export class CollectionFreeFormView extends CollectionViewBase { var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1); return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => { var page = doc.GetNumber(KeyStore.Page, -1); - if (page == curPage || page == -1) + if (page === curPage || page === -1) prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />); return prev; }, [] as JSX.Element[]) @@ -281,7 +295,7 @@ export class CollectionFreeFormView extends CollectionViewBase { getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()) getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH) - getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY); + getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; childViews = () => this.views; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index df150a045..7022fd972 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -147,7 +147,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { idata.set(key, { - pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }), + pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })), color: value.color, width: value.width, tool: value.tool, diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 6daf15f5f..0ce991485 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -28,7 +28,7 @@ export class AudioBox extends React.Component<FieldViewProps> { render() { - let field = this.props.doc.Get(this.props.fieldKey) + let field = this.props.Document.Get(this.props.fieldKey) let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" : field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3"; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 1a0f0cbbd..dcf82ccd4 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -52,14 +52,12 @@ export class CollectionFreeFormDocumentView extends React.Component<DocumentView this.props.Document.SetData(KeyStore.ZIndex, h, NumberField) } - contentScaling = () => { - return this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; - } + contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1 - getTransform = (): Transform => { - return this.props.ScreenToLocalTransform(). - translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)).scale(1 / this.contentScaling()); - } + getTransform = (): Transform => + this.props.ScreenToLocalTransform() + .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)) + .scale(1 / this.contentScaling()) @computed get docView() { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 77551649c..1c9155dce 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,6 +1,6 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { FieldWaiting } from "../../../fields/Field"; +import { FieldWaiting, Field } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; @@ -11,7 +11,7 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; -import { DocumentViewProps, JsxBindings } from "./DocumentView"; +import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; @@ -21,8 +21,16 @@ import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox"; import React = require("react"); +import { Document } from "../../../fields/Document"; +import { FieldViewProps } from "./FieldView"; +import { Without } from "../../../Utils"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? +type BindingProps = Without<FieldViewProps, 'fieldKey'> +export interface JsxBindings { + props: BindingProps; + [keyName: string]: BindingProps | Field; +} @observer export class DocumentContentsView extends React.Component<DocumentViewProps & { @@ -36,7 +44,34 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { CreateBindings(): JsxBindings { - let bindings: JsxBindings = { ...this.props, }; + let + { + Document, + isSelected, + select, + isTopMost, + selectOnLoad, + ScreenToLocalTransform, + addDocument, + removeDocument, + onActiveChanged, + parentActive: active, + } = this.props; + let bindings: JsxBindings = { + props: { + Document, + isSelected, + select, + isTopMost, + selectOnLoad, + ScreenToLocalTransform, + active, + onActiveChanged, + addDocument, + removeDocument, + focus, + } + }; for (const key of this.layoutKeys) { bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b9329f269..31adec0d7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -15,24 +15,28 @@ import { DragManager } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; import { Transform } from "../../util/Transform"; import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionView, CollectionViewType } from "../collections/CollectionView"; +import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import React = require("react"); + export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView>; Document: Document; - AddDocument?: (doc: Document, allowDuplicates: boolean) => boolean; - RemoveDocument?: (doc: Document) => boolean; + addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean; + removeDocument?: (doc: Document) => boolean; + moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; ScreenToLocalTransform: () => Transform; isTopMost: boolean; ContentScaling: () => number; PanelWidth: () => number; PanelHeight: () => number; focus: (doc: Document) => void; - SelectOnLoad: boolean; + selectOnLoad: boolean; + parentActive: () => boolean; + onActiveChanged: (isActive: boolean) => void; } export interface JsxArgs extends DocumentViewProps { Keys: { [name: string]: Key }; @@ -76,60 +80,20 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs { return args; } -export interface JsxBindings { - Document: Document; - isSelected: () => boolean; - select: (isCtrlPressed: boolean) => void; - isTopMost: boolean; - SelectOnLoad: boolean; - [prop: string]: any; -} - @observer export class DocumentView extends React.Component<DocumentViewProps> { private _mainCont = React.createRef<HTMLDivElement>(); + public get ContentRef() { + return this._mainCont; + } private _downX: number = 0; private _downY: number = 0; - - private _reactionDisposer: Opt<IReactionDisposer>; - @computed get active(): boolean { - return ( - SelectionManager.IsSelected(this) || - !this.props.ContainingCollectionView || - this.props.ContainingCollectionView.active() - ); - } - @computed get topMost(): boolean { - return ( - !this.props.ContainingCollectionView || - this.props.ContainingCollectionView.collectionViewType == - CollectionViewType.Docking - ); - } - @computed get layout(): string { - return this.props.Document.GetText( - KeyStore.Layout, - "<p>Error loading layout data</p>" - ); - } - @computed get layoutKeys(): Key[] { - return this.props.Document.GetData( - KeyStore.LayoutKeys, - ListField, - new Array<Key>() - ); - } - @computed get layoutFields(): Key[] { - return this.props.Document.GetData( - KeyStore.LayoutFields, - ListField, - new Array<Key>() - ); - } - screenRect = (): ClientRect | DOMRect => - this._mainCont.current - ? this._mainCont.current.getBoundingClientRect() - : new DOMRect(); + @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); } + @computed get topMost(): boolean { return this.props.isTopMost; } + @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); } + @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); } + @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); } + screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect(); onPointerDown = (e: React.PointerEvent): void => { this._downX = e.clientX; this._downY = e.clientY; @@ -159,20 +123,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { }); } runInAction(() => DocumentManager.Instance.DocumentViews.push(this)); - this._reactionDisposer = reaction( - () => - this.props.ContainingCollectionView && - this.props.ContainingCollectionView.SelectedDocs.slice(), - () => { - if ( - this.props.ContainingCollectionView && - this.props.ContainingCollectionView.SelectedDocs.indexOf( - this.props.Document.Id - ) != -1 - ) - SelectionManager.SelectDoc(this, true); - } - ); } componentDidUpdate() { @@ -190,15 +140,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { if (this.dropDisposer) { this.dropDisposer(); } - runInAction(() => - DocumentManager.Instance.DocumentViews.splice( - DocumentManager.Instance.DocumentViews.indexOf(this), - 1 - ) - ); - if (this._reactionDisposer) { - this._reactionDisposer(); - } + runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1)); } startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) { @@ -211,14 +153,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { dragData.aliasOnDrop = dropAliasOfDraggedDoc; dragData.xOffset = x - left; dragData.yOffset = y - top; - dragData.removeDocument = (dropCollectionView: CollectionView) => { - if ( - this.props.RemoveDocument && - this.props.ContainingCollectionView !== dropCollectionView - ) { - this.props.RemoveDocument(this.props.Document); - } - }; + dragData.moveDocument = this.props.moveDocument; DragManager.StartDocumentDrag([this._mainCont.current], dragData, { handlers: { dragComplete: action(() => { }) @@ -238,7 +173,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { ) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - if (!this.topMost || e.buttons == 2 || e.altKey) { + if (!this.topMost || e.buttons === 2 || e.altKey) { this.startDragging(e.x, e.y, e.ctrlKey || e.altKey); } } @@ -261,17 +196,14 @@ export class DocumentView extends React.Component<DocumentViewProps> { }; deleteClicked = (): void => { - if (this.props.RemoveDocument) { - this.props.RemoveDocument(this.props.Document); + if (this.props.removeDocument) { + this.props.removeDocument(this.props.Document); } }; fieldsClicked = (e: React.MouseEvent): void => { - if (this.props.AddDocument) { - this.props.AddDocument( - Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), - false - ); + if (this.props.addDocument) { + this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false); } }; fullScreenClicked = (e: React.MouseEvent): void => { @@ -438,9 +370,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { ); }; - isSelected = () => { - return SelectionManager.IsSelected(this); - }; + isSelected = () => SelectionManager.IsSelected(this) select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 4e83ec7b9..d9422ae9b 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -19,6 +19,7 @@ import { ListField } from "../../../fields/ListField"; import { DocumentContentsView } from "./DocumentContentsView"; import { Transform } from "../../util/Transform"; import { KeyStore } from "../../../fields/KeyStore"; +import { returnFalse } from "../../../Utils"; // @@ -28,23 +29,29 @@ import { KeyStore } from "../../../fields/KeyStore"; // export interface FieldViewProps { fieldKey: Key; - doc: Document; + Document: Document; isSelected: () => boolean; - select: () => void; + select: (isCtrlPressed: boolean) => void; isTopMost: boolean; selectOnLoad: boolean; - bindings: any; + addDocument?: (document: Document, allowDuplicates?: boolean) => boolean; + removeDocument?: (document: Document) => boolean; + moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; + ScreenToLocalTransform: () => Transform; + active: () => boolean; + onActiveChanged: (isActive: boolean) => void; + focus: (doc: Document) => void; } @observer export class FieldView extends React.Component<FieldViewProps> { public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") { - return `<${fieldType.name} doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={${fieldStr}} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost} />`; + return `<${fieldType.name} {...props} fieldKey={${fieldStr}} />`; } @computed get field(): FieldValue<Field> { - const { doc, fieldKey } = this.props; + const { Document: doc, fieldKey } = this.props; return doc.Get(fieldKey); } render() { @@ -68,26 +75,28 @@ export class FieldView extends React.Component<FieldViewProps> { return <AudioBox {...this.props} /> } else if (field instanceof Document) { - return (<DocumentContentsView Document={field} - AddDocument={undefined} - RemoveDocument={undefined} - ScreenToLocalTransform={() => Transform.Identity} - ContentScaling={() => 1} - PanelWidth={() => 100} - PanelHeight={() => 100} - isTopMost={true} - SelectOnLoad={false} - focus={() => { }} - isSelected={() => false} - select={() => false} - layoutKey={KeyStore.Layout} - ContainingCollectionView={undefined} />) + return ( + <DocumentContentsView Document={field} + addDocument={undefined} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} + ContentScaling={() => 1} + PanelWidth={() => 100} + PanelHeight={() => 100} + isTopMost={true} //TODO Why is this top most? + selectOnLoad={false} + focus={() => { }} + isSelected={() => false} + select={() => false} + layoutKey={KeyStore.Layout} + ContainingCollectionView={undefined} + parentActive={this.props.active} + onActiveChanged={this.props.onActiveChanged} /> + ) } else if (field instanceof ListField) { return (<div> - {(field as ListField<Field>).Data.map(f => { - return f instanceof Document ? f.Title : f.GetValue().toString(); - }).join(", ")} + {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")} </div>) } // bcz: this belongs here, but it doesn't render well so taking it out for now @@ -97,7 +106,7 @@ export class FieldView extends React.Component<FieldViewProps> { else if (field instanceof NumberField) { return <p>{field.Data}</p> } - else if (field != FieldWaiting) { + else if (field !== FieldWaiting) { return <p>{JSON.stringify(field.GetValue())}</p> } else diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0c0efc437..263343da0 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -35,156 +35,156 @@ const { menuBar } = require("prosemirror-menu"); // this will edit the document and assign the new value to that field. //] export class FormattedTextBox extends React.Component<FieldViewProps> { - public static LayoutString(fieldStr: string = "DataKey") { - return FieldView.LayoutString(FormattedTextBox, fieldStr); - } - private _ref: React.RefObject<HTMLDivElement>; - private _editorView: Opt<EditorView>; - private _reactionDisposer: Opt<IReactionDisposer>; - - constructor(props: FieldViewProps) { - super(props); + public static LayoutString(fieldStr: string = "DataKey") { + return FieldView.LayoutString(FormattedTextBox, fieldStr); + } + private _ref: React.RefObject<HTMLDivElement>; + private _editorView: Opt<EditorView>; + private _reactionDisposer: Opt<IReactionDisposer>; - this._ref = React.createRef(); - this.onChange = this.onChange.bind(this); - } + constructor(props: FieldViewProps) { + super(props); - dispatchTransaction = (tx: Transaction) => { - if (this._editorView) { - const state = this._editorView.state.apply(tx); - this._editorView.updateState(state); - const { doc, fieldKey } = this.props; - doc.SetDataOnPrototype( - fieldKey, - JSON.stringify(state.toJSON()), - RichTextField - ); - // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + this._ref = React.createRef(); + this.onChange = this.onChange.bind(this); } - }; - componentDidMount() { - let state: EditorState; - const config = { - schema, - inpRules, //these currently don't do anything, but could eventually be helpful - plugins: [ - history(), - keymap({ "Mod-z": undo, "Mod-y": redo }), - keymap(baseKeymap), - this.tooltipMenuPlugin() - ] + dispatchTransaction = (tx: Transaction) => { + if (this._editorView) { + const state = this._editorView.state.apply(tx); + this._editorView.updateState(state); + const { Document, fieldKey } = this.props; + Document.SetDataOnPrototype( + fieldKey, + JSON.stringify(state.toJSON()), + RichTextField + ); + // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + } }; - let field = this.props.doc.GetT(this.props.fieldKey, RichTextField); - if (field && field != FieldWaiting && field.Data) { - state = EditorState.fromJSON(config, JSON.parse(field.Data)); - } else { - state = EditorState.create(config); - } - if (this._ref.current) { - this._editorView = new EditorView(this._ref.current, { - state, - dispatchTransaction: this.dispatchTransaction - }); - } + componentDidMount() { + let state: EditorState; + const config = { + schema, + inpRules, //these currently don't do anything, but could eventually be helpful + plugins: [ + history(), + keymap({ "Mod-z": undo, "Mod-y": redo }), + keymap(baseKeymap), + this.tooltipMenuPlugin() + ] + }; - this._reactionDisposer = reaction( - () => { - const field = this.props.doc.GetT(this.props.fieldKey, RichTextField); - return field && field != FieldWaiting ? field.Data : undefined; - }, - field => { - if (field && this._editorView) { - this._editorView.updateState( - EditorState.fromJSON(config, JSON.parse(field)) - ); + let field = this.props.Document.GetT(this.props.fieldKey, RichTextField); + if (field && field !== FieldWaiting && field.Data) { + state = EditorState.fromJSON(config, JSON.parse(field.Data)); + } else { + state = EditorState.create(config); + } + if (this._ref.current) { + this._editorView = new EditorView(this._ref.current, { + state, + dispatchTransaction: this.dispatchTransaction + }); } - } - ); - if (this.props.selectOnLoad) { - this.props.select(); - this._editorView!.focus(); - } - } - componentWillUnmount() { - if (this._editorView) { - this._editorView.destroy(); + this._reactionDisposer = reaction( + () => { + const field = this.props.Document.GetT(this.props.fieldKey, RichTextField); + return field && field !== FieldWaiting ? field.Data : undefined; + }, + field => { + if (field && this._editorView) { + this._editorView.updateState( + EditorState.fromJSON(config, JSON.parse(field)) + ); + } + } + ); + if (this.props.selectOnLoad) { + this.props.select(false); + this._editorView!.focus(); + } } - if (this._reactionDisposer) { - this._reactionDisposer(); + + componentWillUnmount() { + if (this._editorView) { + this._editorView.destroy(); + } + if (this._reactionDisposer) { + this._reactionDisposer(); + } } - } - shouldComponentUpdate() { - return false; - } + shouldComponentUpdate() { + return false; + } - @action - onChange(e: React.ChangeEvent<HTMLInputElement>) { - const { fieldKey, doc } = this.props; - doc.SetOnPrototype(fieldKey, new RichTextField(e.target.value)); - // doc.SetData(fieldKey, e.target.value, RichTextField); - } - onPointerDown = (e: React.PointerEvent): void => { - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { - e.stopPropagation(); + @action + onChange(e: React.ChangeEvent<HTMLInputElement>) { + const { fieldKey, Document } = this.props; + Document.SetOnPrototype(fieldKey, new RichTextField(e.target.value)); + // doc.SetData(fieldKey, e.target.value, RichTextField); } - }; + onPointerDown = (e: React.PointerEvent): void => { + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + e.stopPropagation(); + } + }; - //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE - textCapability = (e: React.MouseEvent): void => {}; + //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE + textCapability = (e: React.MouseEvent): void => { }; - specificContextMenu = (e: React.MouseEvent): void => { - ContextMenu.Instance.addItem({ - description: "Text Capability", - event: this.textCapability - }); + specificContextMenu = (e: React.MouseEvent): void => { + ContextMenu.Instance.addItem({ + description: "Text Capability", + event: this.textCapability + }); - // ContextMenu.Instance.addItem({ - // description: "Submenu", - // items: [ - // { - // description: "item 1", event: - // }, - // { - // description: "item 2", event: - // } - // ] - // }) - // e.stopPropagation() - }; + // ContextMenu.Instance.addItem({ + // description: "Submenu", + // items: [ + // { + // description: "item 1", event: + // }, + // { + // description: "item 2", event: + // } + // ] + // }) + // e.stopPropagation() + }; - onPointerWheel = (e: React.WheelEvent): void => { - e.stopPropagation(); - }; + onPointerWheel = (e: React.WheelEvent): void => { + e.stopPropagation(); + }; - tooltipMenuPlugin() { - return new Plugin({ - view(_editorView) { - return new TooltipTextMenu(_editorView); - } - }); - } - onKeyPress(e: React.KeyboardEvent) { - e.stopPropagation(); - // stop propagation doesn't seem to stop propagation of native keyboard events. - // so we set a flag on the native event that marks that the event's been handled. - // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; - } - render() { - return ( - <div - className="formattedTextBox-cont" - onKeyDown={this.onKeyPress} - onKeyPress={this.onKeyPress} - onPointerDown={this.onPointerDown} - onContextMenu={this.specificContextMenu} - // tfs: do we need this event handler - onWheel={this.onPointerWheel} - ref={this._ref} - /> - ); - } + tooltipMenuPlugin() { + return new Plugin({ + view(_editorView) { + return new TooltipTextMenu(_editorView); + } + }); + } + onKeyPress(e: React.KeyboardEvent) { + e.stopPropagation(); + // stop propagation doesn't seem to stop propagation of native keyboard events. + // so we set a flag on the native event that marks that the event's been handled. + // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; + } + render() { + return ( + <div + className="formattedTextBox-cont" + onKeyDown={this.onKeyPress} + onKeyPress={this.onKeyPress} + onPointerDown={this.onPointerDown} + onContextMenu={this.specificContextMenu} + // tfs: do we need this event handler + onWheel={this.onPointerWheel} + ref={this._ref} + /> + ); + } } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 60d1f7214..c5f29f7b0 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -39,7 +39,7 @@ export class ImageBox extends React.Component<FieldViewProps> { onLoad = (target: any) => { var h = this._imgRef.current!.naturalHeight; var w = this._imgRef.current!.naturalWidth; - this.props.doc.SetNumber(KeyStore.NativeHeight, this.props.doc.GetNumber(KeyStore.NativeWidth, 0) * h / w) + this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w) } componentDidMount() { @@ -91,7 +91,7 @@ export class ImageBox extends React.Component<FieldViewProps> { } specificContextMenu = (e: React.MouseEvent): void => { - let field = this.props.doc.GetT(this.props.fieldKey, ImageField); + let field = this.props.Document.GetT(this.props.fieldKey, ImageField); if (field && field !== FieldWaiting) { let url = field.Data.href; ContextMenu.Instance.addItem({ @@ -103,10 +103,10 @@ export class ImageBox extends React.Component<FieldViewProps> { } render() { - let field = this.props.doc.Get(this.props.fieldKey); - let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : + let field = this.props.Document.Get(this.props.fieldKey); + let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif"; - let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1); + let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1); return ( <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}> <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} /> diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 283c1f732..9b067aeeb 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -34,7 +34,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { onEnterKey = (e: React.KeyboardEvent): void => { if (e.key == 'Enter') { if (this._keyInput && this._valueInput) { - let doc = this.props.doc.GetT(KeyStore.Data, Document); + let doc = this.props.Document.GetT(KeyStore.Data, Document); if (!doc || doc == FieldWaiting) { return } @@ -69,7 +69,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } createTable = () => { - let doc = this.props.doc.GetT(KeyStore.Data, Document); + let doc = this.props.Document.GetT(KeyStore.Data, Document); if (!doc || doc == FieldWaiting) { return <tr><td>Loading...</td></tr> } @@ -103,14 +103,13 @@ export class KeyValueBox extends React.Component<FieldViewProps> { this._valueInput = e.currentTarget.value; } - newKeyValue = () => { - return ( + newKeyValue = () => + ( <tr> <td><input type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} /></td> <td><input type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} /></td> </tr> ) - } render() { return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}> diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 7ed5ee272..a676844dc 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -11,6 +11,8 @@ import { Key } from '../../../fields/Key'; import { Server } from "../../Server" import { EditableView } from "../EditableView"; import { CompileScript, ToField } from "../../util/Scripting"; +import { Transform } from '../../util/Transform'; +import { returnFalse, emptyFunction } from '../../../Utils'; // Represents one row in a key value plane @@ -43,13 +45,16 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { } let props: FieldViewProps = { - doc: this.props.doc, + Document: this.props.doc, fieldKey: this.key, - isSelected: () => false, - select: () => { }, + isSelected: returnFalse, + select: emptyFunction, isTopMost: false, - bindings: {}, selectOnLoad: false, + active: returnFalse, + onActiveChanged: emptyFunction, + ScreenToLocalTransform: Transform.Identity, + focus: emptyFunction, } let contents = ( <FieldView {...props} /> @@ -61,15 +66,15 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { <div className="container"> <div>{this.key.Name}</div> <button className="delete" onClick={() => { - let field = props.doc.Get(props.fieldKey); + let field = props.Document.Get(props.fieldKey); if (field && field instanceof Field) { - props.doc.Set(props.fieldKey, undefined); + props.Document.Set(props.fieldKey, undefined); } }}>X</button> </div> </td> <td><EditableView contents={contents} height={36} GetValue={() => { - let field = props.doc.Get(props.fieldKey); + let field = props.Document.Get(props.fieldKey); if (field && field instanceof Field) { return field.ToScriptString(); } @@ -82,12 +87,12 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { } let field = script(); if (field instanceof Field) { - props.doc.Set(props.fieldKey, field); + props.Document.Set(props.fieldKey, field); return true; } else { let dataField = ToField(field); if (dataField) { - props.doc.Set(props.fieldKey, dataField); + props.Document.Set(props.fieldKey, dataField); return true; } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index f9f5bc8f8..d1fb06153 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -89,14 +89,14 @@ export class PDFBox extends React.Component<FieldViewProps> { @observable private _interactive: boolean = false; @observable private _loaded: boolean = false; - @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 1); } - @computed private get thumbnailPage() { return this.props.doc.GetNumber(KeyStore.ThumbnailPage, -1); } + @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 1); } + @computed private get thumbnailPage() { return this.props.Document.GetNumber(KeyStore.ThumbnailPage, -1); } componentDidMount() { this._reactionDisposer = reaction( () => [SelectionManager.SelectedDocuments().slice()], () => { - if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage != this.thumbnailPage && !this.props.isSelected()) { + if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage !== this.thumbnailPage && !this.props.isSelected()) { this.saveThumbnail(); this._interactive = true; } @@ -167,16 +167,16 @@ export class PDFBox extends React.Component<FieldViewProps> { let obj: Object = { parentDivs: [], spans: [] }; //@ts-ignore - if (range.commonAncestorContainer.className == 'react-pdf__Page__textContent') { //multiline highlighting case + if (range.commonAncestorContainer.className === 'react-pdf__Page__textContent') { //multiline highlighting case obj = this.highlightNodes(range.commonAncestorContainer.childNodes) } else { //single line highlighting case let parentDiv = range.commonAncestorContainer.parentElement if (parentDiv) { - if (parentDiv.className == 'react-pdf__Page__textContent') { //when highlight is overwritten + if (parentDiv.className === 'react-pdf__Page__textContent') { //when highlight is overwritten obj = this.highlightNodes(parentDiv.childNodes) } else { parentDiv.childNodes.forEach((child) => { - if (child.nodeName == 'SPAN') { + if (child.nodeName === 'SPAN') { //@ts-ignore obj.parentDivs.push(parentDiv) //@ts-ignore @@ -199,7 +199,7 @@ export class PDFBox extends React.Component<FieldViewProps> { let temp = { parentDivs: [], spans: [] } nodes.forEach((div) => { div.childNodes.forEach((child) => { - if (child.nodeName == 'SPAN') { + if (child.nodeName === 'SPAN') { //@ts-ignore temp.parentDivs.push(div) //@ts-ignore @@ -223,7 +223,7 @@ export class PDFBox extends React.Component<FieldViewProps> { let index: any; this._pageInfo.divs.forEach((obj: any) => { obj.spans.forEach((element: any) => { - if (element == span) { + if (element === span) { if (!index) { index = this._pageInfo.divs.indexOf(obj); } @@ -232,11 +232,11 @@ export class PDFBox extends React.Component<FieldViewProps> { }) if (this._pageInfo.anno.length >= index + 1) { - if (this._currAnno.length == 0) { + if (this._currAnno.length === 0) { this._currAnno.push(this._pageInfo.anno[index]); } } else { - if (this._currAnno.length == 0) { //if there are no current annotation + if (this._currAnno.length === 0) { //if there are no current annotation let div = span.offsetParent; //@ts-ignore let divX = div.style.left @@ -317,7 +317,7 @@ export class PDFBox extends React.Component<FieldViewProps> { * starts drawing the line when user presses down. */ onDraw = () => { - if (this._currTool != null) { + if (this._currTool !== null) { this._currTool.style.backgroundColor = "grey"; } @@ -342,13 +342,13 @@ export class PDFBox extends React.Component<FieldViewProps> { * for changing color (for ink/pen) */ onColorChange = (e: React.PointerEvent) => { - if (e.currentTarget.innerHTML == "Red") { + if (e.currentTarget.innerHTML === "Red") { this._currColor = "red"; - } else if (e.currentTarget.innerHTML == "Blue") { + } else if (e.currentTarget.innerHTML === "Blue") { this._currColor = "blue"; - } else if (e.currentTarget.innerHTML == "Green") { + } else if (e.currentTarget.innerHTML === "Green") { this._currColor = "green"; - } else if (e.currentTarget.innerHTML == "Black") { + } else if (e.currentTarget.innerHTML === "Black") { this._currColor = "black"; } @@ -360,7 +360,7 @@ export class PDFBox extends React.Component<FieldViewProps> { */ onHighlight = () => { this._drawToolOn = false; - if (this._currTool != null) { + if (this._currTool !== null) { this._currTool.style.backgroundColor = "grey"; } if (this._highlightTool.current) { @@ -381,12 +381,12 @@ export class PDFBox extends React.Component<FieldViewProps> { this._renderAsSvg = false; setTimeout(() => { var me = this; - let nwidth = me.props.doc.GetNumber(KeyStore.NativeWidth, 0); - let nheight = me.props.doc.GetNumber(KeyStore.NativeHeight, 0); + let nwidth = me.props.Document.GetNumber(KeyStore.NativeWidth, 0); + let nheight = me.props.Document.GetNumber(KeyStore.NativeHeight, 0); htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 }) .then(action((dataUrl: string) => { - me.props.doc.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField); - me.props.doc.SetNumber(KeyStore.ThumbnailPage, me.props.doc.GetNumber(KeyStore.CurPage, -1)); + me.props.Document.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField); + me.props.Document.SetNumber(KeyStore.ThumbnailPage, me.props.Document.GetNumber(KeyStore.CurPage, -1)); me._renderAsSvg = true; })) .catch(function (error: any) { @@ -399,7 +399,7 @@ export class PDFBox extends React.Component<FieldViewProps> { onLoaded = (page: any) => { if (this._mainDiv.current) { this._mainDiv.current.childNodes.forEach((element) => { - if (element.nodeName == "DIV") { + if (element.nodeName === "DIV") { element.childNodes[0].childNodes.forEach((e) => { if (e instanceof HTMLCanvasElement) { @@ -414,8 +414,8 @@ export class PDFBox extends React.Component<FieldViewProps> { } // bcz: the number of pages should really be set when the document is imported. - this.props.doc.SetNumber(KeyStore.NumPages, page._transport.numPages); - if (this._perPageInfo.length == 0) { //Makes sure it only runs once + this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages); + if (this._perPageInfo.length === 0) { //Makes sure it only runs once this._perPageInfo = [...Array(page._transport.numPages)] } this._loaded = true; @@ -426,11 +426,11 @@ export class PDFBox extends React.Component<FieldViewProps> { // bcz: the nativeHeight should really be set when the document is imported. // also, the native dimensions could be different for different pages of the PDF // so this design is flawed. - var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0); - if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) { + var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); + if (!this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) { var nativeHeight = nativeWidth * r.entry.height / r.entry.width; - this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0)); - this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight); + this.props.Document.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0)); + this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight); } } @@ -438,8 +438,8 @@ export class PDFBox extends React.Component<FieldViewProps> { get pdfContent() { let page = this.curPage; const renderHeight = 2400; - let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField); - let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight; + let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField); + let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / renderHeight; return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}> <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}> <Measure onResize={this.setScaling}> @@ -456,8 +456,8 @@ export class PDFBox extends React.Component<FieldViewProps> { @computed get pdfRenderer() { let proxy = this._loaded ? (null) : this.imageProxyRenderer; - let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField); - if ((!this._interactive && proxy) || !pdfUrl || pdfUrl == FieldWaiting) { + let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField); + if ((!this._interactive && proxy) || !pdfUrl || pdfUrl === FieldWaiting) { return proxy; } return [ @@ -470,9 +470,9 @@ export class PDFBox extends React.Component<FieldViewProps> { @computed get imageProxyRenderer() { - let thumbField = this.props.doc.Get(KeyStore.Thumbnail); + let thumbField = this.props.Document.Get(KeyStore.Thumbnail); if (thumbField) { - let path = thumbField == FieldWaiting || this.thumbnailPage != this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" : + let path = thumbField === FieldWaiting || this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" : thumbField instanceof ImageField ? thumbField.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg"; return <img src={path} width="100%" />; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 7c0db83a8..b4590df34 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -20,7 +20,7 @@ export class VideoBox extends React.Component<FieldViewProps> { super(props); } - @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); } + @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); } _loaded: boolean = false; @@ -31,12 +31,12 @@ export class VideoBox extends React.Component<FieldViewProps> { // bcz: the nativeHeight should really be set when the document is imported. // also, the native dimensions could be different for different pages of the PDF // so this design is flawed. - var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0); - var nativeHeight = this.props.doc.GetNumber(KeyStore.NativeHeight, 0); + var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0); + var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0); var newNativeHeight = nativeWidth * r.entry.height / r.entry.width; if (!nativeHeight && newNativeHeight != nativeHeight && !isNaN(newNativeHeight)) { - this.props.doc.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0)); - this.props.doc.SetNumber(KeyStore.NativeHeight, newNativeHeight); + this.props.Document.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0)); + this.props.Document.SetNumber(KeyStore.NativeHeight, newNativeHeight); } } else { this._loaded = true; @@ -50,13 +50,13 @@ export class VideoBox extends React.Component<FieldViewProps> { @action setVideoRef = (vref: HTMLVideoElement | null) => { if (this.curPage >= 0 && vref) { - vref!.currentTime = this.curPage; - (vref! as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage; + vref.currentTime = this.curPage; + (vref as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage; } } render() { - let field = this.props.doc.GetT(this.props.fieldKey, VideoField); + let field = this.props.Document.GetT(this.props.fieldKey, VideoField); if (!field || field === FieldWaiting) { return <div>Loading</div> } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 2ca8d49ce..92e2fabd4 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -16,10 +16,10 @@ export class WebBox extends React.Component<FieldViewProps> { super(props); } - @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); } + @computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); } render() { - let field = this.props.doc.Get(this.props.fieldKey); + let field = this.props.Document.Get(this.props.fieldKey); let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" : field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu"; diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx index 7fdd77bf3..f13cccd24 100644 --- a/src/debug/Viewer.tsx +++ b/src/debug/Viewer.tsx @@ -87,7 +87,7 @@ class DocumentViewer extends React.Component<{ field: Document }> { return ( <div key={kv[0]}> <b>({key ? key.Name : kv[0]}): </b> - <DebugViewer fieldId={kv[1]!}></DebugViewer> + <DebugViewer fieldId={kv[1]}></DebugViewer> </div> ) }) @@ -177,9 +177,7 @@ class Viewer extends React.Component { onChange={this.inputOnChange} onKeyDown={this.onKeyPress} /> <div> - {this.ids.map(id => { - return <DebugViewer fieldId={id} key={id}></DebugViewer> - })} + {this.ids.map(id => <DebugViewer fieldId={id} key={id}></DebugViewer>)} </div> </> ) diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 92d03d765..8eca5e30c 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -34,33 +34,20 @@ export class Document extends Field { } } - public Width = () => { - return this.GetNumber(KeyStore.Width, 0); - }; - public Height = () => { - return this.GetNumber( - KeyStore.Height, - this.GetNumber(KeyStore.NativeWidth, 0) - ? (this.GetNumber(KeyStore.NativeHeight, 0) / - this.GetNumber(KeyStore.NativeWidth, 0)) * - this.GetNumber(KeyStore.Width, 0) - : 0 - ); - }; - public Scale = () => { - return this.GetNumber(KeyStore.Scale, 1); - }; + public Width = () => this.GetNumber(KeyStore.Width, 0) + public Height = () => this.GetNumber(KeyStore.Height, this.GetNumber(KeyStore.NativeWidth, 0) ? (this.GetNumber(KeyStore.NativeHeight, 0) / this.GetNumber(KeyStore.NativeWidth, 0)) * this.GetNumber(KeyStore.Width, 0) : 0); + public Scale = () => this.GetNumber(KeyStore.Scale, 1) @computed public get Title(): string { let title = this.Get(KeyStore.Title, true); if (title) - if (title != FieldWaiting && title instanceof TextField) + if (title !== FieldWaiting && title instanceof TextField) return title.Data; else return "-waiting-"; let parTitle = this.GetT(KeyStore.Title, TextField); if (parTitle) - if (parTitle != FieldWaiting) return parTitle.Data + ".alias"; + if (parTitle !== FieldWaiting) return parTitle.Data + ".alias"; else return "-waiting-.alias"; return "-untitled-"; } @@ -106,7 +93,7 @@ export class Document extends Field { } } else { let doc: FieldValue<Document> = this; - while (doc && doc != FieldWaiting && field != FieldWaiting) { + while (doc && field !== FieldWaiting) { let curField = doc.fields.get(key.Id); let curProxy = doc._proxies.get(key.Id); if (!curField || (curProxy && curField.field.Id !== curProxy)) { @@ -137,7 +124,7 @@ export class Document extends Field { break; } } - if (doc == FieldWaiting) field = FieldWaiting; + if (doc === FieldWaiting) field = FieldWaiting; } return field; @@ -191,7 +178,7 @@ export class Document extends Field { if (callback) { fn(callback); } else { - return new Promise(res => fn(res)); + return new Promise(fn); } } @@ -237,7 +224,7 @@ export class Document extends Field { ignoreProto: boolean = false ): FieldValue<T> { var getfield = this.Get(key, ignoreProto); - if (getfield != FieldWaiting) { + if (getfield !== FieldWaiting) { return Cast(getfield, ctor); } return FieldWaiting; @@ -249,7 +236,7 @@ export class Document extends Field { ignoreProto: boolean = false ): T { const field = this.GetT(key, ctor, ignoreProto); - if (field && field != FieldWaiting) { + if (field && field !== FieldWaiting) { return field; } const newField = new ctor(); @@ -366,7 +353,7 @@ export class Document extends Field { GetAllPrototypes(): Document[] { let protos: Document[] = []; let doc: FieldValue<Document> = this; - while (doc && doc != FieldWaiting) { + while (doc && doc !== FieldWaiting) { protos.push(doc); doc = doc.GetPrototype(); } @@ -415,7 +402,7 @@ export class Document extends Field { let fields: [string, string][] = []; this._proxies.forEach((field, key) => { if (field) { - fields.push([key, field as string]); + fields.push([key, field]); } }); diff --git a/src/fields/Field.ts b/src/fields/Field.ts index d48509a47..0d0e56f77 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -12,8 +12,8 @@ export function Cast<T extends Field>(field: FieldValue<Field>, ctor: { new(): T return undefined; } -export const FieldWaiting: FIELD_WAITING = "<Waiting>"; -export type FIELD_WAITING = "<Waiting>"; +export const FieldWaiting: FIELD_WAITING = null; +export type FIELD_WAITING = null; export type FieldId = string; export type Opt<T> = T | undefined; export type FieldValue<T> = Opt<T> | FIELD_WAITING; diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts index 6c4de5000..0cbcc6d33 100644 --- a/src/fields/WebField.ts +++ b/src/fields/WebField.ts @@ -4,7 +4,7 @@ import { Types } from "../server/Message"; export class WebField extends BasicField<URL> { constructor(data: URL | undefined = undefined, id?: FieldId, save: boolean = true) { - super(data == undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id); + super(data === undefined ? new URL("https://crossorigin.me/" + "https://cs.brown.edu/") : data, save, id); } toString(): string { diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index ae48dd2c6..c3684a0eb 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -9,6 +9,7 @@ import { RouteStore } from '../server/RouteStore'; import { ServerUtils } from '../server/ServerUtil'; import "./ImageUpload.scss"; import React = require('react'); +import { Opt } from '../fields/Field'; @@ -20,48 +21,44 @@ import React = require('react'); // } // } -const onFileLoad = (file: any) => { +const onFileLoad = async (file: any) => { let imgPrev = document.getElementById("img_preview") if (imgPrev) { let files: File[] = file.target.files; - if (files.length != 0) { + if (files.length !== 0) { console.log(files[0]); let formData = new FormData(); formData.append("file", files[0]); const upload = window.location.origin + "/upload" - fetch(upload, { + const res = await fetch(upload, { method: 'POST', body: formData - }).then((res: Response) => { - return res.json() - }).then(json => { - json.map((file: any) => { - let path = window.location.origin + file - var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 }) + }); + const json = await res.json(); + json.map(async (file: any) => { + let path = window.location.origin + file + var doc: Document = Documents.ImageDocument(path, { nativeWidth: 200, width: 200 }) - rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)).then(res => { - if (res) { - return Server.GetField(res); - } - throw new Error("No user id returned"); - }).then(field => { - if (field instanceof Document) { - return field.GetTAsync(KeyStore.OptionalRightCollection, Document) - } - }).then(pending => { - if (pending) { - pending.GetOrCreateAsync(KeyStore.Data, ListField, list => { - list.Data.push(doc); - }) - } - }); + const res = await rp.get(ServerUtils.prepend(RouteStore.getUserDocumentId)); + if (!res) { + throw new Error("No user id returned"); + } + const field = await Server.GetField(res); + let pending: Opt<Document>; + if (field instanceof Document) { + pending = await field.GetTAsync(KeyStore.OptionalRightCollection, Document) + } + if (pending) { + pending.GetOrCreateAsync(KeyStore.Data, ListField, list => { + list.Data.push(doc); + }) + } + }); - // console.log(window.location.origin + file[0]) + // console.log(window.location.origin + file[0]) - //imgPrev.setAttribute("src", window.location.origin + files[0].name) - }) - }) + //imgPrev.setAttribute("src", window.location.origin + files[0].name) } } } diff --git a/src/server/authentication/controllers/WorkspacesMenu.tsx b/src/server/authentication/controllers/WorkspacesMenu.tsx index 8e14cf98e..835432c8e 100644 --- a/src/server/authentication/controllers/WorkspacesMenu.tsx +++ b/src/server/authentication/controllers/WorkspacesMenu.tsx @@ -73,7 +73,7 @@ export class WorkspacesMenu extends React.Component<WorkspaceMenuProps> { <span>{i + 1} - </span> <EditableView display={"inline"} - GetValue={() => { return s.Title }} + GetValue={() => s.Title} SetValue={(title: string): boolean => { s.SetText(KeyStore.Title, title); return true; diff --git a/src/server/authentication/controllers/user_controller.ts b/src/server/authentication/controllers/user_controller.ts index e365b8dce..2bbb334b5 100644 --- a/src/server/authentication/controllers/user_controller.ts +++ b/src/server/authentication/controllers/user_controller.ts @@ -4,7 +4,7 @@ import * as passport from "passport"; import { IVerifyOptions } from "passport-local"; import "../config/passport"; import * as request from "express-validator"; -const flash = require("express-flash"); +import flash = require("express-flash"); import * as session from "express-session"; import * as pug from 'pug'; import * as async from 'async'; @@ -109,12 +109,12 @@ export let postLogin = (req: Request, res: Response, next: NextFunction) => { } passport.authenticate("local", (err: Error, user: DashUserModel, info: IVerifyOptions) => { - if (err) { return next(err); } + if (err) { next(err); return } if (!user) { return res.redirect(RouteStore.signup); } req.logIn(user, (err) => { - if (err) { return next(err); } + if (err) { next(err); return } res.redirect(RouteStore.home); }); })(req, res, next); @@ -158,7 +158,8 @@ export let postForgot = function (req: Request, res: Response, next: NextFunctio User.findOne({ email }, function (err, user: DashUserModel) { if (!user) { // NO ACCOUNT WITH SUBMITTED EMAIL - return res.redirect(RouteStore.forgot); + res.redirect(RouteStore.forgot); + return } user.passwordResetToken = token; user.passwordResetExpires = new Date(Date.now() + 3600000); // 1 HOUR @@ -228,7 +229,8 @@ export let postReset = function (req: Request, res: Response) { user.save(function (err) { if (err) { - return res.redirect(RouteStore.login); + res.redirect(RouteStore.login); + return; } req.logIn(user, function (err) { if (err) { diff --git a/src/server/index.ts b/src/server/index.ts index 17d7432e0..ce8bbefe8 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -263,12 +263,10 @@ function deleteFields() { return Database.Instance.deleteAll(); } -function deleteAll() { - return Database.Instance.deleteAll().then(() => { - return Database.Instance.deleteAll('sessions') - }).then(() => { - return Database.Instance.deleteAll('users') - }); +async function deleteAll() { + await Database.Instance.deleteAll(); + await Database.Instance.deleteAll('sessions'); + await Database.Instance.deleteAll('users'); } function barReceived(guid: String) { diff --git a/src/server/public/files/upload_a6a70d84ebb65febf7900e29f52cc86d.pdf b/src/server/public/files/upload_a6a70d84ebb65febf7900e29f52cc86d.pdf Binary files differdeleted file mode 100644 index dfd6ab339..000000000 --- a/src/server/public/files/upload_a6a70d84ebb65febf7900e29f52cc86d.pdf +++ /dev/null |