diff options
Diffstat (limited to 'src')
25 files changed, 351 insertions, 409 deletions
diff --git a/src/.DS_Store b/src/.DS_Store Binary files differnew file mode 100644 index 000000000..4d6acb95a --- /dev/null +++ b/src/.DS_Store diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9770e5cdc..ba13cc31b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -7,10 +7,12 @@ import { ListField } from "../../fields/ListField"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { ImageField } from "../../fields/ImageField"; import { ImageBox } from "../views/nodes/ImageBox"; +import { WebField } from "../../fields/WebField"; +import { WebBox } from "../views/nodes/WebBox"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; -import { FieldView } from "../views/nodes/FieldView"; import { HtmlField } from "../../fields/HtmlField"; -import { WebView } from "../views/nodes/WebView"; +import { Key } from "../../fields/Key" +import { Field } from "../../fields/Field"; export interface DocumentOptions { x?: number; @@ -20,109 +22,110 @@ export interface DocumentOptions { nativeWidth?: number; nativeHeight?: number; title?: string; + panx?: number; + pany?: number; + scale?: number; + layout?: string; + layoutKeys?: Key[]; + viewType?: number; } export namespace Documents { - export function initProtos(callback: () => void) { - Server.GetFields([collectionProtoId, textProtoId, imageProtoId], (fields) => { - collectionProto = fields[collectionProtoId] as Document; + let textProto: Document; + let imageProto: Document; + let webProto: Document; + let collProto: Document; + const textProtoId = "textProto"; + const imageProtoId = "imageProto"; + const webProtoId = "webProto"; + const collProtoId = "collectionProto"; + + export function initProtos(mainDocId: string, callback: (mainDoc?: Document) => void) { + Server.GetFields([collProtoId, textProtoId, imageProtoId, mainDocId], (fields) => { + collProto = fields[collProtoId] as Document; imageProto = fields[imageProtoId] as Document; textProto = fields[textProtoId] as Document; - callback() + webProto = fields[webProtoId] as Document; + callback(fields[mainDocId] as Document) }); } + function assignOptions(doc: Document, options: DocumentOptions): Document { + if (options.x !== undefined) { doc.SetNumber(KeyStore.X, options.x); } + if (options.y !== undefined) { doc.SetNumber(KeyStore.Y, options.y); } + if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); } + if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); } + if (options.nativeWidth !== undefined) { doc.SetNumber(KeyStore.NativeWidth, options.nativeWidth); } + if (options.nativeHeight !== undefined) { doc.SetNumber(KeyStore.NativeHeight, options.nativeHeight); } + if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); } + if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); } + if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); } + if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); } + if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); } + if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); } + if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); } + return doc; + } + function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document { + return assignOptions(new Document(protoId), { ...options, title: title, layout: layout }); + } + function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: T, ctor: { new(): U }, id?: string) { + var deleg = doc.MakeDelegate(id); + deleg.SetData(KeyStore.Data, value, ctor); + return assignOptions(deleg, options); + } - function setupOptions(doc: Document, options: DocumentOptions): void { - if (options.x !== undefined) { - doc.SetData(KeyStore.X, options.x, NumberField); - } - if (options.y !== undefined) { - doc.SetData(KeyStore.Y, options.y, NumberField); - } - if (options.width !== undefined) { - doc.SetData(KeyStore.Width, options.width, NumberField); - } - if (options.height !== undefined) { - doc.SetData(KeyStore.Height, options.height, NumberField); - } - if (options.nativeWidth !== undefined) { - doc.SetData(KeyStore.NativeWidth, options.nativeWidth, NumberField); - } - if (options.nativeHeight !== undefined) { - doc.SetData(KeyStore.NativeHeight, options.nativeHeight, NumberField); - } - if (options.title !== undefined) { - doc.SetData(KeyStore.Title, options.title, TextField); + function GetImagePrototype(): Document { + if (!imageProto) { + imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("AnnotationsKey"), + { x: 0, y: 0, nativeWidth: 300, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] }); + imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString()); } - doc.SetData(KeyStore.Scale, 1, NumberField); - doc.SetData(KeyStore.PanX, 0, NumberField); - doc.SetData(KeyStore.PanY, 0, NumberField); + return imageProto; } - - let textProto: Document; - const textProtoId = "textProto"; function GetTextPrototype(): Document { - if (!textProto) { - textProto = new Document(textProtoId); - textProto.Set(KeyStore.X, new NumberField(0)); - textProto.Set(KeyStore.Y, new NumberField(0)); - textProto.Set(KeyStore.Width, new NumberField(300)); - textProto.Set(KeyStore.Height, new NumberField(150)); - textProto.Set(KeyStore.Layout, new TextField(FormattedTextBox.LayoutString())); - textProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - } - return textProto; + return textProto ? textProto : + textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), + { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] }); } - - export function TextDocument(options: DocumentOptions = {}): Document { - let doc = GetTextPrototype().MakeDelegate(); - setupOptions(doc, options); - // doc.SetField(KeyStore.Data, new RichTextField()); - return doc; + function GetWebPrototype(): Document { + return webProto ? webProto : + webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(), + { x: 0, y: 0, width: 300, height: 300, layoutKeys: [KeyStore.Data] }); } - - let htmlProto: Document; - const htmlProtoId = "htmlProto"; - function GetHtmlPrototype(): Document { - if (!htmlProto) { - htmlProto = new Document(htmlProtoId); - htmlProto.Set(KeyStore.X, new NumberField(0)); - htmlProto.Set(KeyStore.Y, new NumberField(0)); - htmlProto.Set(KeyStore.Width, new NumberField(300)); - htmlProto.Set(KeyStore.Height, new NumberField(150)); - htmlProto.Set(KeyStore.Layout, new TextField(WebView.LayoutString())); - htmlProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - } - return htmlProto; + function GetCollectionPrototype(): Document { + return collProto ? collProto : + collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"), + { panx: 0, pany: 0, scale: 1, layoutKeys: [KeyStore.Data] }); } - export function HtmlDocument(html: string, options: DocumentOptions = {}): Document { - let doc = GetHtmlPrototype().MakeDelegate(); - setupOptions(doc, options); - doc.Set(KeyStore.Data, new HtmlField(html)); + export function ImageDocument(url: string, options: DocumentOptions = {}) { + let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] }, + new URL(url), ImageField); + doc.SetText(KeyStore.Caption, "my caption..."); + doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption()); + doc.SetText(KeyStore.OverlayLayout, FixedCaption()); return doc; } + export function TextDocument(options: DocumentOptions = {}) { + return SetInstanceOptions(GetTextPrototype(), options, "", TextField); + } + export function WebDocument(url: string, options: DocumentOptions = {}) { + return SetInstanceOptions(GetWebPrototype(), options, new URL(url), WebField); + } + export function HtmlDocument(html: string, options: DocumentOptions = {}) { + return SetInstanceOptions(GetWebPrototype(), options, html, HtmlField); + } + export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string) { + return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Freeform }, documents, ListField, id) + } + export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) { + return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Schema }, documents, ListField, id) + } + export function DockDocument(config: string, options: DocumentOptions, id?: string) { + return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Docking }, config, TextField, id) + } - let imageProto: Document; - const imageProtoId = "imageProto"; - function GetImagePrototype(): Document { - if (!imageProto) { - imageProto = new Document(imageProtoId); - imageProto.Set(KeyStore.Title, new TextField("IMAGE PROTO")); - imageProto.Set(KeyStore.X, new NumberField(0)); - imageProto.Set(KeyStore.Y, new NumberField(0)); - imageProto.Set(KeyStore.NativeWidth, new NumberField(300)); - imageProto.Set(KeyStore.Width, new NumberField(300)); - imageProto.Set(KeyStore.Layout, new TextField(CollectionView.LayoutString("AnnotationsKey"))); - imageProto.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) - imageProto.Set(KeyStore.BackgroundLayout, new TextField(ImageBox.LayoutString())); - // imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />')); - imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations])); - return imageProto; - } - return imageProto; - } // example of custom display string for an image that shows a caption. function EmbeddedCaption() { @@ -140,54 +143,4 @@ export namespace Documents { + FormattedTextBox.LayoutString("CaptionKey") + `</div> </div>` }; - - export function ImageDocument(url: string, options: DocumentOptions = {}): Document { - let doc = GetImagePrototype().MakeDelegate(); - setupOptions(doc, options); - doc.Set(KeyStore.Data, new ImageField(new URL(url))); - doc.Set(KeyStore.Caption, new TextField("my caption...")); - doc.Set(KeyStore.BackgroundLayout, new TextField(EmbeddedCaption())); - doc.Set(KeyStore.OverlayLayout, new TextField(FixedCaption())); - doc.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations, KeyStore.Caption])); - console.log("" + doc.GetNumber(KeyStore.Height, 311)); - return doc; - } - - let collectionProto: Document; - const collectionProtoId = "collectionProto"; - function GetCollectionPrototype(): Document { - if (!collectionProto) { - collectionProto = new Document(collectionProtoId); - collectionProto.Set(KeyStore.Scale, new NumberField(1)); - collectionProto.Set(KeyStore.PanX, new NumberField(0)); - collectionProto.Set(KeyStore.PanY, new NumberField(0)); - collectionProto.Set(KeyStore.Layout, new TextField(CollectionView.LayoutString("DataKey"))); - collectionProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - } - return collectionProto; - } - - export function CollectionDocument(data: Array<Document> | string, viewType: CollectionViewType, options: DocumentOptions = {}, id?: string): Document { - let doc = GetCollectionPrototype().MakeDelegate(id); - setupOptions(doc, options); - if (typeof data === "string") { - doc.SetText(KeyStore.Data, data); - } else { - doc.SetData(KeyStore.Data, data, ListField); - } - doc.SetNumber(KeyStore.ViewType, viewType); - return doc; - } - - export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string) { - return CollectionDocument(documents, CollectionViewType.Freeform, options, id) - } - - export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) { - return CollectionDocument(documents, CollectionViewType.Schema, options, id) - } - - export function DockDocument(config: string, options: DocumentOptions, id?: string) { - return CollectionDocument(config, CollectionViewType.Docking, options, id) - } }
\ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 8adadee0f..60910a40b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -2,15 +2,16 @@ import { DocumentDecorations } from "../views/DocumentDecorations"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { Document } from "../../fields/Document" import { action } from "mobx"; +import { DocumentView } from "../views/nodes/DocumentView"; -export function setupDrag(_reference: React.RefObject<HTMLDivElement>, _dragDocument: Document) { +export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document) { let onRowMove = action((e: PointerEvent): void => { e.stopPropagation(); e.preventDefault(); document.removeEventListener("pointermove", onRowMove); document.removeEventListener('pointerup', onRowUp); - DragManager.StartDrag(_reference.current!, { document: _dragDocument }); + DragManager.StartDrag(_reference.current!, { document: docFunc() }); }); let onRowUp = action((e: PointerEvent): void => { document.removeEventListener("pointermove", onRowMove); @@ -20,9 +21,8 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, _dragDocu // if (this.props.isSelected() || this.props.isTopMost) { if (e.button == 0) { e.stopPropagation(); - e.preventDefault(); if (e.shiftKey) { - CollectionDockingView.Instance.StartOtherDrag(_reference.current!, _dragDocument); + CollectionDockingView.Instance.StartOtherDrag(docFunc(), e); } else { document.addEventListener("pointermove", onRowMove); document.addEventListener('pointerup', onRowUp); @@ -133,29 +133,43 @@ export namespace DragManager { if (hideSource) { ele.hidden = true; } - const moveHandler = (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); x += e.movementX; y += e.movementY; + if (e.shiftKey) { + abortDrag(); + const docView: DocumentView = dragData["documentView"]; + const doc: Document = docView ? docView.props.Document : dragData["document"]; + CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); + } dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; }; - const upHandler = (e: PointerEvent) => { + + const abortDrag = () => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - FinishDrag(dragElement, e, dragData, options); + dragDiv.removeChild(dragElement); if (hideSource && !wasHidden) { ele.hidden = false; } + } + const upHandler = (e: PointerEvent) => { + abortDrag(); + FinishDrag(ele, e, dragData, options); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); } function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions) { - dragDiv.removeChild(dragEle); + let parent = dragEle.parentElement; + if (parent) + parent.removeChild(dragEle); const target = document.elementFromPoint(e.x, e.y); + if (parent) + parent.appendChild(dragEle); if (!target) { return; } diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index e73f62904..4334ed299 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -28,4 +28,24 @@ h1 { p { margin: 0px; padding: 0px; -}
\ No newline at end of file +} +::-webkit-scrollbar { + -webkit-appearance: none; + height:5px; + width:5px; +} +::-webkit-scrollbar-thumb { + border-radius: 2px; + background-color: rgba(0,0,0,.5); +} + +.main-buttonDiv { + position: absolute; + width: 150px; + left: 0px; +} +.main-undoButtons { + position: absolute; + width: 150px; + right: 0px; +} diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 17dda899d..16e95ad82 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,167 +1,101 @@ -import { action, configure, reaction, computed } from 'mobx'; +import { action, configure } from 'mobx'; import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { DocumentDecorations } from './DocumentDecorations'; -import { Documents } from '../documents/Documents'; import { Document } from '../../fields/Document'; import { KeyStore } from '../../fields/KeyStore'; -import "./Main.scss"; -import { ContextMenu } from './ContextMenu'; -import { DocumentView } from './nodes/DocumentView'; -import { Server } from '../Server'; +import { DocumentTransfer, MessageStore } from '../../server/Message'; import { Utils } from '../../Utils'; -import { ServerUtils } from '../../server/ServerUtil'; -import { MessageStore, DocumentTransfer } from '../../server/Message'; +import { Documents } from '../documents/Documents'; +import { Server } from '../Server'; +import { setupDrag } from '../util/DragManager'; import { Transform } from '../util/Transform'; -import { CollectionDockingView } from './collections/CollectionDockingView'; -import { FieldWaiting } from '../../fields/Field'; import { UndoManager } from '../util/UndoManager'; -import { DragManager } from '../util/DragManager'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +import { ContextMenu } from './ContextMenu'; +import { DocumentDecorations } from './DocumentDecorations'; +import { DocumentView } from './nodes/DocumentView'; +import "./Main.scss"; -configure({ - enforceActions: "observed" -}); -window.addEventListener("drop", function (e) { - e.preventDefault(); -}, false) -window.addEventListener("dragover", function (e) { - e.preventDefault(); -}, false) +configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action +window.addEventListener("drop", (e) => e.preventDefault(), false) +window.addEventListener("dragover", (e) => e.preventDefault(), false) document.addEventListener("pointerdown", action(function (e: PointerEvent) { - console.log(ContextMenu); if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) { ContextMenu.Instance.clearItems() } }), true) -//runInAction(() => -// let doc1 = Documents.TextDocument({ title: "hello" }); -// let doc2 = doc1.MakeDelegate(); -// doc2.Set(KS.X, new NumberField(150)); -// doc2.Set(KS.Y, new NumberField(20)); -// let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { -// x: 450, y: 100, title: "cat 1" -// }); -// doc3.Set(KeyStore.Data, new ImageField); -// const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { -// x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v -// })); -// schemaDocs[0].SetData(KS.Author, "Tyler", TextField); -// schemaDocs[4].SetData(KS.Author, "Bob", TextField); -// schemaDocs.push(doc2); -// const doc7 = Documents.SchemaDocument(schemaDocs) - const mainDocId = "mainDoc"; -Documents.initProtos(() => { - Utils.EmitCallback(Server.Socket, MessageStore.GetField, mainDocId, (res: any) => { - console.log("HELLO WORLD") - console.log("RESPONSE: " + res) - let mainContainer: Document; - let mainfreeform: Document; - if (res) { - mainContainer = ServerUtils.FromJson(res) as Document; - mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document); - } - else { - mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId); - Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainContainer.ToJson())) +let mainContainer: Document; +let mainfreeform: Document; +console.log("HELLO WORLD") +Documents.initProtos(mainDocId, (res?: Document) => { + console.log("Response => " + JSON.stringify(res as Document)) + if (res instanceof Document) { + mainContainer = res; + mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document); + } + else { + mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId); + Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainContainer.ToJson())) - setTimeout(() => { - mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" }); - Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainfreeform.ToJson())); + // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) + setTimeout(() => { + mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" }); + Utils.Emit(Server.Socket, MessageStore.AddDocument, new DocumentTransfer(mainfreeform.ToJson())); - var docs = [mainfreeform].map(doc => CollectionDockingView.makeDocumentConfig(doc)); - mainContainer.SetText(KeyStore.Data, JSON.stringify({ content: [{ type: 'row', content: docs }] })); - mainContainer.Set(KeyStore.ActiveFrame, mainfreeform); - }, 0); - } + var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(mainfreeform)] }] }; + mainContainer.SetText(KeyStore.Data, JSON.stringify(dockingLayout)); + mainContainer.Set(KeyStore.ActiveFrame, mainfreeform); + }, 0); + } - let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {})) - let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) - let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a feeform collection" })); - let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" })); - let addImageNode = action(() => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { - width: 200, height: 200, title: "an image of a cat" - })); + let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; + let weburl = "https://cs.brown.edu/courses/cs166/"; + let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {})) + let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) + let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a feeform collection" })); + let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" })); + let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" })); + let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); - let addClick = (creator: any) => action(() => { - var img = creator(); - img.SetNumber(KeyStore.X, 0); - img.SetNumber(KeyStore.Y, 0); - mainfreeform.GetList<Document>(KeyStore.Data, []).push(img); - }); + let addClick = (creator: () => Document) => action(() => mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator())); - let imgRef = React.createRef<HTMLDivElement>(); - let textRef = React.createRef<HTMLDivElement>(); - let schemaRef = React.createRef<HTMLDivElement>(); - let colRef = React.createRef<HTMLDivElement>(); - let curMoveListener: any = null - let onRowMove = (creator: any, dragRef: any) => action((e: PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); + let imgRef = React.createRef<HTMLDivElement>(); + let webRef = React.createRef<HTMLDivElement>(); + let textRef = React.createRef<HTMLDivElement>(); + let schemaRef = React.createRef<HTMLDivElement>(); + let colRef = React.createRef<HTMLDivElement>(); - document.removeEventListener("pointermove", curMoveListener); - document.removeEventListener('pointerup', onRowUp); - DragManager.StartDrag(dragRef.current!, { document: creator() }); - }); - let onRowUp = action((e: PointerEvent): void => { - document.removeEventListener("pointermove", curMoveListener); - document.removeEventListener('pointerup', onRowUp); - }); - let onRowDown = (creator: any, dragRef: any) => (e: React.PointerEvent) => { - if (e.shiftKey) { - CollectionDockingView.Instance.StartOtherDrag(dragRef.current!, creator()); - e.stopPropagation(); - } else { - document.addEventListener("pointermove", curMoveListener = onRowMove(creator, dragRef)); - document.addEventListener('pointerup', onRowUp); - } - } - ReactDOM.render(( - <div style={{ position: "absolute", width: "100%", height: "100%" }}> - <DocumentView Document={mainContainer} - AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity} - ContentScaling={() => 1} - PanelWidth={() => 0} - PanelHeight={() => 0} - isTopMost={true} - ContainingCollectionView={undefined} /> - <DocumentDecorations /> - <ContextMenu /> - <div style={{ position: 'absolute', bottom: '0px', left: '0px', width: '150px' }} ref={imgRef} > - <button onPointerDown={onRowDown(addImageNode, imgRef)} onClick={addClick(addImageNode)}>Add Image</button></div> - <div style={{ position: 'absolute', bottom: '25px', left: '0px', width: '150px' }} ref={textRef}> - <button onPointerDown={onRowDown(addTextNode, textRef)} onClick={addClick(addTextNode)}>Add Text</button></div> - <div style={{ position: 'absolute', bottom: '50px', left: '0px', width: '150px' }} ref={colRef}> - <button onPointerDown={onRowDown(addColNode, colRef)} onClick={addClick(addColNode)}>Add Collection</button></div> - <div style={{ position: 'absolute', bottom: '75px', left: '0px', width: '150px' }} ref={schemaRef}> - <button onPointerDown={onRowDown(addSchemaNode, schemaRef)} onClick={addClick(addSchemaNode)}>Add Schema</button></div> - <button style={{ position: 'absolute', bottom: '100px', left: '0px', width: '150px' }} onClick={clearDatabase}>Clear Database</button> - <button style={{ position: 'absolute', bottom: '25', right: '0px', width: '150px' }} onClick={() => UndoManager.Undo()}>Undo</button> - <button style={{ position: 'absolute', bottom: '0', right: '0px', width: '150px' }} onClick={() => UndoManager.Redo()}>Redo</button> - </div>), - document.getElementById('root')); - }) -}); -// let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { -// x: 650, y: 500, width: 600, height: 600, title: "cat 2" -// }); -// let docset2 = new Array<Document>(doc4);//, doc1, doc3); -// let doc6 = Documents.CollectionDocument(docset2, { -// x: 350, y: 100, width: 600, height: 600, title: "docking collection" -// }); -// let mainNodes = mainContainer.GetOrCreate(KeyStore.Data, ListField); -// mainNodes.Data.push(doc6); -// mainNodes.Data.push(doc2); -// mainNodes.Data.push(doc4); -// mainNodes.Data.push(doc3); -// mainNodes.Data.push(doc5); -// mainNodes.Data.push(doc1); -//mainNodes.Data.push(doc2); -//mainNodes.Data.push(doc6); -// mainContainer.Set(KeyStore.Data, mainNodes); -//} -//); + ReactDOM.render(( + <div style={{ position: "absolute", width: "100%", height: "100%" }}> + <DocumentView Document={mainContainer} + AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity} + ContentScaling={() => 1} + PanelWidth={() => 0} + PanelHeight={() => 0} + isTopMost={true} + SelectOnLoad={false} + ContainingCollectionView={undefined} /> + <DocumentDecorations /> + <ContextMenu /> + <div className="main-buttonDiv" style={{ bottom: '0px' }} ref={imgRef} > + <button onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>Add Image</button></div> + <div className="main-buttonDiv" style={{ bottom: '25px' }} ref={webRef} > + <button onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>Add Web</button></div> + <div className="main-buttonDiv" style={{ bottom: '50px' }} ref={textRef}> + <button onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>Add Text</button></div> + <div className="main-buttonDiv" style={{ bottom: '75px' }} ref={colRef}> + <button onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>Add Collection</button></div> + <div className="main-buttonDiv" style={{ bottom: '100px' }} ref={schemaRef}> + <button onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>Add Schema</button></div> + <div className="main-buttonDiv" style={{ bottom: '125px' }} > + <button onClick={clearDatabase}>Clear Database</button></div> + <button className="main-undoButtons" style={{ bottom: '25px' }} onClick={() => UndoManager.Undo()}>Undo</button> + <button className="main-undoButtons" style={{ bottom: '0px' }} onClick={() => UndoManager.Redo()}>Redo</button> + </div>), + document.getElementById('root')); +}) diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 5fb632469..5df44cd0c 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -10,7 +10,6 @@ import { FieldId, Opt, Field } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { Utils } from "../../../Utils"; import { Server } from "../../Server"; -import { DragManager } from "../../util/DragManager"; import { undoBatch } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; @@ -34,10 +33,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } private _goldenLayout: any = null; - private _dragDiv: any = null; - private _dragParent: HTMLElement | null = null; - private _dragElement: HTMLElement | undefined; - private _dragFakeElement: HTMLElement | undefined; private _containerRef = React.createRef<HTMLDivElement>(); private _fullScreen: any = null; @@ -47,28 +42,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp (window as any).React = React; (window as any).ReactDOM = ReactDOM; } - - public StartOtherDrag(dragElement: HTMLElement, dragDoc: Document) { - this._dragElement = dragElement; - this._dragParent = dragElement.parentElement; - // bcz: we want to copy this document into the header, not move it there. - // However, GoldenLayout is setup to move things, so we have to do some kludgy stuff: - - // - create a temporary invisible div and register that as a DragSource with GoldenLayout - this._dragDiv = document.createElement("div"); - this._dragDiv.style.opacity = 0; - DragManager.Root().appendChild(this._dragDiv); - this._goldenLayout.createDragSource(this._dragDiv, CollectionDockingView.makeDocumentConfig(dragDoc)); - - // - add our document to that div so that GoldenLayout will get the move events its listening for - this._dragDiv.appendChild(this._dragElement); - - // - add a duplicate of our document to the original document's container - // (GoldenLayout will be removing our original one) - this._dragFakeElement = dragElement.cloneNode(true) as HTMLElement; - this._dragParent!.appendChild(this._dragFakeElement); - - // all of this must be undone when the document has been dropped (see tabCreated) + public StartOtherDrag(dragDoc: Document, e: any) { + this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: e.button }) } @action @@ -98,7 +73,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp // Creates a vertical split on the right side of the docking view, and then adds the Document to that split // @action - public AddRightSplit(document: Document) { + public AddRightSplit(document: Document, minimize: boolean = false) { this._goldenLayout.emit('stateChanged'); let newItemStackConfig = { type: 'stack', @@ -121,10 +96,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp collayout.config["width"] = 50; newContentItem.config["width"] = 50; } + if (minimize) { + newContentItem.config["width"] = 10; + newContentItem.config["height"] = 10; + } newContentItem.callDownwards('_$init'); this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); this._goldenLayout.emit('stateChanged'); this.stateChanged(); + return newContentItem; } setupGoldenLayout() { @@ -218,13 +198,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this.stateChanged(); } tabCreated = (tab: any) => { - if (this._dragDiv) { - this._dragDiv.removeChild(this._dragElement); - this._dragParent!.removeChild(this._dragFakeElement!); - this._dragParent!.appendChild(this._dragElement!); - DragManager.Root().removeChild(this._dragDiv); - this._dragDiv = null; - } tab.closeElement.off('click') //unbind the current click handler .click(function () { tab.contentItem.remove(); @@ -295,6 +268,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { PanelHeight={this._nativeHeight} ScreenToLocalTransform={this.ScreenToLocalTransform} isTopMost={true} + SelectOnLoad={false} ContainingCollectionView={undefined} /> </div> diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index f7b2e6449..f432e8cc3 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -1,15 +1,5 @@ .collectionfreeformview-container { - ::-webkit-scrollbar { - -webkit-appearance: none; - height:5px; - width:5px; - } - ::-webkit-scrollbar-thumb { - border-radius: 2px; - background-color: rgba(0,0,0,.5); - } - .collectionfreeformview > .jsx-parser{ position:absolute; height: 100%; diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 63ecec0df..33d687628 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -13,9 +13,9 @@ import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView } from "../collections/CollectionView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocumentView } from "../nodes/DocumentView"; -import { WebView } from "../nodes/WebView"; import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; +import { WebBox } from "../nodes/WebBox"; import "./CollectionFreeFormView.scss"; import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; import { CollectionViewBase } from "./CollectionViewBase"; @@ -28,6 +28,7 @@ export class CollectionFreeFormView extends CollectionViewBase { private _canvasRef = React.createRef<HTMLDivElement>(); private _lastX: number = 0; private _lastY: number = 0; + private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) @observable private _downX: number = 0; @@ -166,9 +167,10 @@ export class CollectionFreeFormView extends CollectionViewBase { //make textbox and add it to this collection let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); (this._downX, this._downY); let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" }); + // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself + this._selectOnLoaded = newBox.Id; //set text to be the typed key and get focus on text box this.props.CollectionView.addDocument(newBox); - newBox.SetNumber(KeyStore.SelectOnLoaded, 1); //remove cursor from screen this._previewCursorVisible = false; } @@ -208,15 +210,15 @@ export class CollectionFreeFormView extends CollectionViewBase { } @computed get views() { - const { fieldKey, Document } = this.props; - const lvalue = Document.GetT<ListField<Document>>(fieldKey, ListField); + const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField); if (lvalue && lvalue != FieldWaiting) { return lvalue.Data.map(doc => { - return (<CollectionFreeFormDocumentView key={doc.Id} Document={doc} + return (<CollectionFreeFormDocumentView key={doc.Id} Document={doc} ref={focus} AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument} ScreenToLocalTransform={this.getTransform} isTopMost={false} + SelectOnLoad={doc.Id === this._selectOnLoaded} ContentScaling={this.noScaling} PanelWidth={doc.Width} PanelHeight={doc.Height} @@ -230,7 +232,7 @@ export class CollectionFreeFormView extends CollectionViewBase { get backgroundView() { return !this.backgroundLayout ? (null) : (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox }} bindings={this.props.bindings} jsx={this.backgroundLayout} showWarnings={true} @@ -241,7 +243,7 @@ export class CollectionFreeFormView extends CollectionViewBase { get overlayView() { return !this.overlayLayout ? (null) : (<JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox }} bindings={this.props.bindings} jsx={this.overlayLayout} showWarnings={true} diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 995d60f74..d40e6d314 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -6,15 +6,6 @@ position: absolute; width: 100%; height: 100%; - ::-webkit-scrollbar { - -webkit-appearance: none; - height:5px; - width:5px; - } - ::-webkit-scrollbar-thumb { - border-radius: 2px; - background-color: rgba(0,0,0,.5); - } .collectionSchemaView-previewRegion { position: relative; background: black; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 106a5c4f5..5bcd501cc 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -39,15 +39,16 @@ export class CollectionSchemaView extends CollectionViewBase { isSelected: () => false, select: () => { }, isTopMost: false, - bindings: {} + bindings: {}, + selectOnLoad: false, } let contents = ( <FieldView {...props} /> ) let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, props.doc); + let onItemDown = setupDrag(reference, () => props.doc); return ( - <div onPointerDown={onItemDown} ref={reference}> + <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}> <EditableView contents={contents} height={36} GetValue={() => { let field = props.doc.Get(props.fieldKey); @@ -185,6 +186,7 @@ export class CollectionSchemaView extends CollectionViewBase { <DocumentView Document={selected} AddDocument={this.props.addDocument} RemoveDocument={this.props.removeDocument} isTopMost={false} + SelectOnLoad={false} ScreenToLocalTransform={this.getTransform} ContentScaling={this.getContentScaling} PanelWidth={this.getPanelWidth} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 64f4c0970..55c804337 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -33,7 +33,7 @@ class TreeView extends React.Component<TreeViewProps> { var children = childDocument.GetT<ListField<Document>>(KeyStore.Data, ListField); let title = childDocument.GetT<TextField>(KeyStore.Title, TextField); - let onItemDown = setupDrag(reference, childDocument); + let onItemDown = setupDrag(reference, () => childDocument); if (title && title != FieldWaiting) { let subView = !children || this.collapsed || children === FieldWaiting ? (null) : diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 35ac48177..8332249c9 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -30,7 +30,7 @@ export class CollectionView extends React.Component<CollectionViewProps> { public static LayoutString(fieldKey: string = "DataKey") { return `<CollectionView Document={Document} ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings} - isTopMost={isTopMost} BackgroundView={BackgroundView} />`; + isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} />`; } public active = () => { var isSelected = this.props.isSelected(); diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 217536e2b..7067724c8 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -67,7 +67,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> let html = e.dataTransfer.getData("text/html"); let text = e.dataTransfer.getData("text/plain"); if (html && html.indexOf("<img") != 0) { - let htmlDoc = Documents.HtmlDocument(html, { ...options }); + let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 }); htmlDoc.SetText(KeyStore.DocumentText, text); this.props.addDocument(htmlDoc); return; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 50dc9ddc1..002d6b29b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -12,10 +12,10 @@ import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; -import { WebView } from "./WebView"; import { ContextMenu } from "../ContextMenu"; import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; +import { WebBox } from "../nodes/WebBox"; import "./DocumentView.scss"; import React = require("react"); const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? @@ -32,6 +32,7 @@ export interface DocumentViewProps { ContentScaling: () => number; PanelWidth: () => number; PanelHeight: () => number; + SelectOnLoad: boolean; } export interface JsxArgs extends DocumentViewProps { Keys: { [name: string]: Key } @@ -96,7 +97,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { this._downX = e.clientX; this._downY = e.clientY; if (e.shiftKey && e.buttons === 1) { - CollectionDockingView.Instance.StartOtherDrag(this._mainCont.current!, this.props.Document); + CollectionDockingView.Instance.StartOtherDrag(this.props.Document, e); e.stopPropagation(); } else { this._contextMenuCanOpen = true; @@ -119,6 +120,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { return; } if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { + document.removeEventListener("pointermove", this.onPointerMove) + document.removeEventListener("pointerup", this.onPointerUp) this._contextMenuCanOpen = false; if (this._mainCont.current != null && !this.topMost) { this._contextMenuCanOpen = false; @@ -195,7 +198,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { @computed get mainContent() { var val = this.props.Document.Id; return <JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebView }} + components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox }} bindings={this._documentBindings} jsx={this.layout} showWarnings={true} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index b71309bf5..f372258f8 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -7,11 +7,11 @@ import { TextField } from "../../../fields/TextField"; import { NumberField } from "../../../fields/NumberField"; import { RichTextField } from "../../../fields/RichTextField"; import { ImageField } from "../../../fields/ImageField"; +import { WebField } from "../../../fields/WebField"; import { Key } from "../../../fields/Key"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; -import { HtmlField } from "../../../fields/HtmlField"; -import { WebView } from "./WebView"; +import { WebBox } from "./WebBox"; // // these properties get assigned through the render() method of the DocumentView when it creates this node. @@ -24,12 +24,15 @@ export interface FieldViewProps { isSelected: () => boolean; select: () => void; isTopMost: boolean; + selectOnLoad: boolean; bindings: any; } @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} isTopMost={isTopMost} />`; } + 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} />`; + } @computed get field(): FieldValue<Field> { @@ -50,12 +53,16 @@ export class FieldView extends React.Component<FieldViewProps> { else if (field instanceof ImageField) { return <ImageBox {...this.props} /> } + else if (field instanceof WebField) { + return <WebBox {...this.props} /> + } + // bcz: this belongs here, but it doesn't render well so taking it out for now + // else if (field instanceof HtmlField) { + // return <WebBox {...this.props} /> + // } else if (field instanceof NumberField) { return <p>{field.Data}</p> } - else if (field instanceof HtmlField) { - return <WebView {...this.props} /> - } else if (field != FieldWaiting) { return <p>{JSON.stringify(field.GetValue())}</p> } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index a92821ed6..e65615af4 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -2,18 +2,14 @@ import { action, IReactionDisposer, reaction } from "mobx"; import { baseKeymap } from "prosemirror-commands"; import { history, redo, undo } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; -const { exampleSetup } = require("prosemirror-example-setup") import { schema } from "prosemirror-schema-basic"; import { EditorState, Transaction, } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Opt, FieldWaiting, FieldValue } from "../../../fields/Field"; +import { Opt, FieldWaiting } from "../../../fields/Field"; import "./FormattedTextBox.scss"; -import { KeyStore } from "../../../fields/KeyStore"; import React = require("react") import { RichTextField } from "../../../fields/RichTextField"; import { FieldViewProps, FieldView } from "./FieldView"; -import { Plugin } from 'prosemirror-state' -import { Decoration, DecorationSet } from 'prosemirror-view' @@ -45,7 +41,6 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { super(props); this._ref = React.createRef(); - this.onChange = this.onChange.bind(this); } @@ -53,14 +48,12 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - const { doc, fieldKey } = this.props; - doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + this.props.doc.SetData(this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField); } } componentDidMount() { let state: EditorState; - const { doc, fieldKey } = this.props; const config = { schema, plugins: [ @@ -68,11 +61,10 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { keymap({ "Mod-z": undo, "Mod-y": redo }), keymap(baseKeymap), ] - }; - let field = doc.GetT(fieldKey, RichTextField); - if (field && field != FieldWaiting) { // bcz: don't think this works + let field = this.props.doc.GetT(this.props.fieldKey, RichTextField); + if (field && field != FieldWaiting) { state = EditorState.fromJSON(config, JSON.parse(field.Data)); } else { state = EditorState.create(config); @@ -92,9 +84,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))); } }) - - //if tagged to be selected when created, then select & focus - if (this.props.doc.GetNumber(KeyStore.SelectOnLoaded, 0)) { + if (this.props.selectOnLoad) { this.props.select(); this._editorView!.focus(); } @@ -115,11 +105,9 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { @action onChange(e: React.ChangeEvent<HTMLInputElement>) { - const { fieldKey, doc } = this.props; - doc.SetData(fieldKey, e.target.value, RichTextField); + this.props.doc.SetData(this.props.fieldKey, e.target.value, RichTextField); } onPointerDown = (e: React.PointerEvent): void => { - let me = this; if (e.buttons === 1 && this.props.isSelected()) { e.stopPropagation(); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4fe73fb8d..e206bf8d5 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -9,7 +9,6 @@ import { FieldWaiting } from '../../../fields/Field'; import { observer } from "mobx-react" import { observable, action } from 'mobx'; import { KeyStore } from '../../../fields/KeyStore'; -import { element } from 'prop-types'; @observer export class ImageBox extends React.Component<FieldViewProps> { diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss new file mode 100644 index 000000000..e72b3c4da --- /dev/null +++ b/src/client/views/nodes/WebBox.scss @@ -0,0 +1,14 @@ + +.webBox-cont { + padding: 0vw; + position: absolute; + width: 100%; + height: 100%; +} + +.webBox-button { + padding : 0vw; + border: none; + width : 100%; + height: 100%; +}
\ No newline at end of file diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx new file mode 100644 index 000000000..2ca8d49ce --- /dev/null +++ b/src/client/views/nodes/WebBox.tsx @@ -0,0 +1,38 @@ +import "./WebBox.scss"; +import React = require("react") +import { WebField } from '../../../fields/WebField'; +import { FieldViewProps, FieldView } from './FieldView'; +import { FieldWaiting } from '../../../fields/Field'; +import { observer } from "mobx-react" +import { computed } from 'mobx'; +import { KeyStore } from '../../../fields/KeyStore'; + +@observer +export class WebBox extends React.Component<FieldViewProps> { + + public static LayoutString() { return FieldView.LayoutString(WebBox); } + + constructor(props: FieldViewProps) { + super(props); + } + + @computed get html(): string { return this.props.doc.GetHtml(KeyStore.Data, ""); } + + render() { + let field = this.props.doc.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"; + + let content = this.html ? + <span dangerouslySetInnerHTML={{ __html: this.html }}></span> : + <div style={{ width: "100%", height: "100%", position: "absolute" }}> + <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe> + {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />} + </div>; + + return ( + <div className="webBox-cont" > + {content} + </div>) + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/WebView.tsx b/src/client/views/nodes/WebView.tsx deleted file mode 100644 index 717aa8bf5..000000000 --- a/src/client/views/nodes/WebView.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { FieldViewProps, FieldView } from "./FieldView"; -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import { KeyStore } from "../../../fields/KeyStore"; -import React = require('react') -import { TextField } from "../../../fields/TextField"; -import { HtmlField } from "../../../fields/HtmlField"; -import { RichTextField } from "../../../fields/RichTextField"; - -@observer -export class WebView extends React.Component<FieldViewProps> { - public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(WebView, fieldStr) } - - @computed - get html(): string { - return this.props.doc.GetData(KeyStore.Data, HtmlField, "" as string); - } - - render() { - return <span dangerouslySetInnerHTML={{ __html: this.html }}></span> - } -}
\ No newline at end of file diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 6193ea56c..5b91de6ed 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -8,6 +8,7 @@ import { ListField } from "./ListField"; import { Server } from "../client/Server"; import { Types } from "../server/Message"; import { UndoManager } from "../client/util/UndoManager"; +import { HtmlField } from "./HtmlField"; export class Document extends Field { public fields: ObservableMap<string, { key: Key, field: Field }> = new ObservableMap(); @@ -125,6 +126,10 @@ export class Document extends Field { return vval; } + GetHtml(key: Key, defaultVal: string): string { + return this.GetData(key, HtmlField, defaultVal); + } + GetNumber(key: Key, defaultVal: number): number { return this.GetData(key, NumberField, defaultVal); } diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index 662574396..a3b39735d 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -26,7 +26,4 @@ export namespace KeyStore { export const Caption = new Key("Caption"); export const ActiveFrame = new Key("ActiveFrame"); export const DocumentText = new Key("DocumentText"); - //determines whether doc views will be selected when they are first loaded - //currently only implemented for FormattedTextView - export const SelectOnLoaded = new Key("SelectOnLoaded"); } diff --git a/src/fields/WebField.ts b/src/fields/WebField.ts new file mode 100644 index 000000000..8f945d686 --- /dev/null +++ b/src/fields/WebField.ts @@ -0,0 +1,30 @@ +import { BasicField } from "./BasicField"; +import { Field, FieldId } from "./Field"; +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); + } + + toString(): string { + return this.Data.href; + } + + ToScriptString(): string { + return `new WebField("${this.Data}")`; + } + + Copy(): Field { + return new WebField(this.Data); + } + + ToJson(): { type: Types, data: URL, _id: string } { + return { + type: Types.Web, + data: this.Data, + _id: this.Id + } + } + +}
\ No newline at end of file diff --git a/src/server/Message.ts b/src/server/Message.ts index 80fc9a80d..148e6e723 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -45,7 +45,7 @@ export class GetFieldArgs { } export enum Types { - Number, List, Key, Image, Document, Text, RichText, DocumentReference, Html + Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, Html } export class DocumentTransfer implements Transferable { diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 08e72fdae..a53fb5d2b 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -10,6 +10,7 @@ import { Server } from './../client/Server'; import { Types } from './Message'; import { Utils } from '../Utils'; import { HtmlField } from '../fields/HtmlField'; +import { WebField } from '../fields/WebField'; export class ServerUtils { public static FromJson(json: any): Field { @@ -30,6 +31,8 @@ export class ServerUtils { return new TextField(data, id, false) case Types.Html: return new HtmlField(data, id, false) + case Types.Web: + return new WebField(new URL(data), id, false) case Types.RichText: return new RichTextField(data, id, false) case Types.Key: |