From d2c9550f23c4e5654822ac01b973bb965e3f6dec Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 14 Jun 2019 20:49:12 -0400 Subject: cleaned up Docs namespace and thoroughly documented DocServer.GetRefFields --- src/client/DocServer.ts | 80 ++- src/client/documents/Documents.ts | 758 ++++++++++++--------- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/Main.tsx | 2 +- src/client/views/MainView.tsx | 24 +- src/client/views/SearchBox.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 386 ++++------- .../views/collections/CollectionSchemaView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 16 +- .../views/collections/CollectionTreeView.tsx | 2 +- .../views/collections/CollectionVideoView.tsx | 2 +- .../views/collections/DockedFrameRenderer.tsx | 116 ++++ .../collections/collectionFreeForm/MarqueeView.tsx | 18 +- src/client/views/nodes/DocumentView.tsx | 4 +- src/mobile/ImageUpload.tsx | 4 +- src/new_fields/Doc.ts | 12 + src/new_fields/util.ts | 1 + .../authentication/models/current_user_utils.ts | 8 +- 18 files changed, 833 insertions(+), 606 deletions(-) create mode 100644 src/client/views/collections/DockedFrameRenderer.tsx (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index cbcf751ee..d759b4757 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -47,36 +47,81 @@ export namespace DocServer { } } + /** + * Given a list of Doc GUIDs, this utility function will asynchronously attempt to fetch each document + * associated with a given input id, first looking in the RefField cache and then communicating with + * the server if the document was not found there. + * + * @param ids the ids that map to the reqested documents + */ export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt }> { const requestedIds: string[] = []; const waitingIds: string[] = []; const promises: Promise>[] = []; const map: { [id: string]: Opt } = {}; + + // 1) An initial pass through the cache to determine which documents need to be fetched, + // which are already in the process of being fetched and which already exist in the + // cache for (const id of ids) { const cached = _cache[id]; + if (cached === undefined) { + // NOT CACHED => we'll have to send a request to the server requestedIds.push(id); } else if (cached instanceof Promise) { + // BEING CACHED => someone else previously (likely recently) called GetRefFields, + // and requested one of the documents I'm looking for. Shouldn't fetch again, just + // wait until this promise is resolved (see the second to last line of the function) promises.push(cached); waitingIds.push(id); } else { + // CACHED => great, let's just add it to the field map map[id] = cached; } } - const prom = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds).then(fields => { + + // 2) Synchronously, we emit a single callback to the server requesting the documents for the given ids. + // This returns a promise, which, when resolved, indicates that all the JSON serialized versions of + // the fields have been returned from the server + const fieldsReceived: Promise = Utils.EmitCallback(_socket, MessageStore.GetRefFields, requestedIds); + + // 3) When the serialized RefFields have been received, go head and begin deserializing them into objects. + // Here, once deserialized, we also invoke .proto to 'load' the documents' prototypes, which ensures that all + // future .proto calls won't have to go farther than the cache to get their actual value. + const fieldsDeserialized = fieldsReceived.then(async fields => { const fieldMap: { [id: string]: RefField } = {}; + const deserializedFields: any = []; for (const field of fields) { if (field !== undefined) { - fieldMap[field.id] = SerializationHelper.Deserialize(field); + // deserialize + let deserialized: any = SerializationHelper.Deserialize(field); + fieldMap[field.id] = deserialized; + deserializedFields.push(deserialized.proto); } } - + // this actually handles the loeading of prototypes + await Promise.all(deserializedFields); return fieldMap; }); - requestedIds.forEach(id => _cache[id] = prom.then(fields => fields[id])); - const fields = await prom; + + // 4) Here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + requestedIds.forEach(id => _cache[id] = fieldsDeserialized.then(fields => fields[id])); + + // 5) At this point, all fields have a) been returned from the server and b) been deserialized into actual Field objects whose + // prototype documents, if any, have also been fetched and cached. + const fields = await fieldsDeserialized; + + // 6) With this confidence, we can now go through and update the cache at the ids of the fields that + // we explicitly had to fetch. To finish it off, we add whatever value we've come up with for a given + // id to the soon to be returned field mapping. requestedIds.forEach(id => { const field = fields[id]; + // either way, overwrite or delete any promises that we inserted as flags + // to indicate that the field was in the process of being fetched. Now everything + // should be an actual value within or entirely absent from the cache. if (field !== undefined) { _cache[id] = field; } else { @@ -84,14 +129,23 @@ export namespace DocServer { } map[id] = field; }); - await Promise.all(requestedIds.map(async id => { - const field = fields[id]; - if (field) { - await (field as any).proto; - } - })); - const otherFields = await Promise.all(promises); - waitingIds.forEach((id, index) => map[id] = otherFields[index]); + + // 7) Those promises we encountered in the else if of 1), which represent + // other callers having already submitted a request to the server for (a) document(s) + // in which we're interested, must still be awaited so that we can return the proper + // values for those as well. + // + // Fortunately, those other callers will also hit their own version of 6) and clean up + // the shared cache when these promises resolve, so all we have to do is... + const otherCallersFetching = await Promise.all(promises); + // ...extract the RefFields returned from the resolution of those promises and add them to our + // own map. + waitingIds.forEach((id, index) => map[id] = otherCallersFetching[index]); + + // Now, we return our completed mapping from all of the ids that were passed into the method + // to their actual RefField | undefined values. This return value either becomes the input + // argument to the caller's promise (i.e. GetRefFields.then(map => //do something with map...)) + // or it is the direct return result if the promise is awaited. return map; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b346e1570..b10954636 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -18,7 +18,6 @@ import { action } from "mobx"; import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel"; import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel"; import { AggregateFunction } from "../northstar/model/idea/idea"; -import { Template } from "../views/Templates"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { IconBox } from "../views/nodes/IconBox"; import { Field, Doc, Opt } from "../../new_fields/Doc"; @@ -30,7 +29,7 @@ import { Cast, NumCast } from "../../new_fields/Types"; import { IconField } from "../../new_fields/IconField"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; -import { StrokeData, InkField } from "../../new_fields/InkField"; +import { InkField } from "../../new_fields/InkField"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; import { UndoManager } from "../util/UndoManager"; @@ -67,345 +66,486 @@ export interface DocumentOptions { dbDoc?: Doc; // [key: string]: Opt; } -const delegateKeys = ["x", "y", "width", "height", "panX", "panY"]; -export namespace DocUtils { - export function MakeLink(source: Doc, target: Doc) { - let protoSrc = source.proto ? source.proto : source; - let protoTarg = target.proto ? target.proto : target; - UndoManager.RunInBatch(() => { - let linkDoc = Docs.TextDocument({ width: 100, height: 30, borderRounding: -1 }); - //let linkDoc = new Doc; - linkDoc.proto!.title = "-link name-"; - linkDoc.proto!.linkDescription = ""; - linkDoc.proto!.linkTags = "Default"; +export namespace Docs { - linkDoc.proto!.linkedTo = target; - linkDoc.proto!.linkedToPage = target.curPage; - linkDoc.proto!.linkedFrom = source; - linkDoc.proto!.linkedFromPage = source.curPage; + export namespace Prototypes { - let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc)); - if (!linkedFrom) { - protoTarg.linkedFromDocs = linkedFrom = new List(); - } - linkedFrom.push(linkDoc); + // the complete list of document prototypes and their ids + export let textProto: Doc; const textProtoId = "textProto"; + export let histoProto: Doc; const histoProtoId = "histoProto"; + export let imageProto: Doc; const imageProtoId = "imageProto"; + export let webProto: Doc; const webProtoId = "webProto"; + export let collProto: Doc; const collProtoId = "collectionProto"; + export let kvpProto: Doc; const kvpProtoId = "kvpProto"; + export let videoProto: Doc; const videoProtoId = "videoProto"; + export let audioProto: Doc; const audioProtoId = "audioProto"; + export let pdfProto: Doc; const pdfProtoId = "pdfProto"; + export let iconProto: Doc; const iconProtoId = "iconProto"; - let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc)); - if (!linkedTo) { - protoSrc.linkedToDocs = linkedTo = new List(); + /** + * This function loads or initializes the prototype for each docment type. + * + * This is an asynchronous function because it has to attempt + * to fetch the prototype documents from the server. + * + * Once we have this object that maps the prototype ids to a potentially + * undefined document, we either initialize our private prototype + * variables with the document returned from the server or, if prototypes + * haven't been initialized, the newly initialized prototype document. + */ + export async function initialize(): Promise { + // non-guid string ids for each document prototype + let protoIds = [textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId] + // fetch the actual prototype documents from the server + let actualProtos = await DocServer.GetRefFields(protoIds); + + // initialize prototype documents + textProto = actualProtos[textProtoId] as Doc || CreateTextProto(); + histoProto = actualProtos[histoProtoId] as Doc || CreateHistogramProto(); + collProto = actualProtos[collProtoId] as Doc || CreateCollectionProto(); + imageProto = actualProtos[imageProtoId] as Doc || CreateImageProto(); + webProto = actualProtos[webProtoId] as Doc || CreateWebProto(); + kvpProto = actualProtos[kvpProtoId] as Doc || CreateKVPProto(); + videoProto = actualProtos[videoProtoId] as Doc || CreateVideoProto(); + audioProto = actualProtos[audioProtoId] as Doc || CreateAudioProto(); + pdfProto = actualProtos[pdfProtoId] as Doc || CreatePdfProto(); + iconProto = actualProtos[iconProtoId] as Doc || CreateIconProto(); + } + + /** + * This is a convenience method that is used to initialize + * prototype documents for the first time. + * + * @param protoId the id of the prototype, indicating the specific prototype + * to initialize (see the *protoId list at the top of the namespace) + * @param title the prototype document's title, follows *-PROTO + * @param layout the layout key for this prototype and thus the + * layout key that all delegates will inherit + * @param options any value specified in the DocumentOptions object likewise + * becomes the default value for that key for all delegates + */ + function buildPrototype(protoId: string, title: string, layout: string, options: DocumentOptions): Doc { + return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout }); + } + + // INDIVIDUAL INITIALIZERS + + function CreateImageProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + nativeWidth: 600, + width: 300, + backgroundLayout: ImageBox.LayoutString(), + curPage: 0 + }; + return buildPrototype(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"), defaultAttrs); + } + + function CreateHistogramProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 300, + backgroundColor: "black", + backgroundLayout: + HistogramBox.LayoutString() + }; + return buildPrototype(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"), defaultAttrs); + } + + function CreateIconProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: Number(MINIMIZED_ICON_SIZE), + height: Number(MINIMIZED_ICON_SIZE) + }; + return buildPrototype(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), defaultAttrs); + } + + function CreateTextProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 150, + backgroundColor: "#f1efeb" + }; + return buildPrototype(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), defaultAttrs); + } + + function CreatePdfProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + nativeWidth: 1200, + width: 300, + backgroundLayout: PDFBox.LayoutString(), + curPage: 1 + }; + return buildPrototype(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"), defaultAttrs); + } + + function CreateWebProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 300 + }; + return buildPrototype(webProtoId, "WEB_PROTO", WebBox.LayoutString(), defaultAttrs); + } + + function CreateCollectionProto(): Doc { + let defaultAttrs = { + panX: 0, + panY: 0, + scale: 1, + width: 500, + height: 500 + }; + return buildPrototype(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"), defaultAttrs); + } + + function CreateKVPProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 150 + }; + return buildPrototype(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(), defaultAttrs); + } + + function CreateVideoProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + nativeWidth: 600, + width: 300, + backgroundLayout: VideoBox.LayoutString(), + curPage: 0 + }; + return buildPrototype(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"), defaultAttrs); + } + + function CreateAudioProto(): Doc { + let defaultAttrs = { + x: 0, + y: 0, + width: 300, + height: 150 } - linkedTo.push(linkDoc); - return linkDoc; - }, "make link"); + return buildPrototype(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(), defaultAttrs); + } } + /** + * Encapsulates the factory used to create new document instances + * delegated from top-level prototypes + */ + export namespace Create { -} + const delegateKeys = ["x", "y", "width", "height", "panX", "panY"]; -export namespace Docs { - let textProto: Doc; - let histoProto: Doc; - let imageProto: Doc; - let webProto: Doc; - let collProto: Doc; - let kvpProto: Doc; - let videoProto: Doc; - let audioProto: Doc; - let pdfProto: Doc; - let iconProto: Doc; - const textProtoId = "textProto"; - const histoProtoId = "histoProto"; - const pdfProtoId = "pdfProto"; - const imageProtoId = "imageProto"; - const webProtoId = "webProto"; - const collProtoId = "collectionProto"; - const kvpProtoId = "kvpProto"; - const videoProtoId = "videoProto"; - const audioProtoId = "audioProto"; - const iconProtoId = "iconProto"; - - export function initProtos(): Promise { - return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => { - textProto = fields[textProtoId] as Doc || CreateTextPrototype(); - histoProto = fields[histoProtoId] as Doc || CreateHistogramPrototype(); - collProto = fields[collProtoId] as Doc || CreateCollectionPrototype(); - imageProto = fields[imageProtoId] as Doc || CreateImagePrototype(); - webProto = fields[webProtoId] as Doc || CreateWebPrototype(); - kvpProto = fields[kvpProtoId] as Doc || CreateKVPPrototype(); - videoProto = fields[videoProtoId] as Doc || CreateVideoPrototype(); - audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype(); - pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype(); - iconProto = fields[iconProtoId] as Doc || CreateIconPrototype(); - }); - } + /** + * This function receives the relevant document prototype and uses + * it to create a new of that base-level prototype, or the + * underlying data document, which it then delegates again + * to create the view document. + * + * It also takes the opportunity to register the user + * that created the document and the time of creation. + * + * @param proto the specific document prototype off of which to model + * this new instance (textProto, imageProto, etc.) + * @param data the Field to store at this new instance's data key + * @param options any initial values to provide for this new instance + * @param delegId if applicable, an existing document id. If undefined, Doc's + * constructor just generates a new GUID. This is currently used + * only when creating a DockDocument from the current user's already existing + * main document. + */ + function CreateInstanceFromProto(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) { + const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); - function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc { - return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout }); - } - function SetInstanceOptions(doc: Doc, options: DocumentOptions, value: U) { - const deleg = Doc.MakeDelegate(doc); - deleg.data = value; - return Doc.assign(deleg, options); - } - function SetDelegateOptions(doc: Doc, options: DocumentOptions, id?: string) { - const deleg = Doc.MakeDelegate(doc, id); - return Doc.assign(deleg, options); - } + if (!("author" in protoProps)) { + protoProps.author = CurrentUserUtils.email; + } - function CreateImagePrototype(): Doc { - let imageProto = setupPrototypeOptions(imageProtoId, "IMAGE_PROTO", CollectionView.LayoutString("annotations"), - { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: ImageBox.LayoutString(), curPage: 0 }); - return imageProto; - } + if (!("creationDate" in protoProps)) { + protoProps.creationDate = new DateField; + } - function CreateHistogramPrototype(): Doc { - let histoProto = setupPrototypeOptions(histoProtoId, "HISTO PROTO", CollectionView.LayoutString("annotations"), - { x: 0, y: 0, width: 300, height: 300, backgroundColor: "black", backgroundLayout: HistogramBox.LayoutString() }); - return histoProto; - } - function CreateIconPrototype(): Doc { - let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), - { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) }); - return iconProto; - } - function CreateTextPrototype(): Doc { - let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" }); - return textProto; - } - function CreatePdfPrototype(): Doc { - let pdfProto = setupPrototypeOptions(pdfProtoId, "PDF_PROTO", CollectionPDFView.LayoutString("annotations"), - { x: 0, y: 0, nativeWidth: 1200, width: 300, backgroundLayout: PDFBox.LayoutString(), curPage: 1 }); - return pdfProto; - } - function CreateWebPrototype(): Doc { - let webProto = setupPrototypeOptions(webProtoId, "WEB_PROTO", WebBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 300 }); - return webProto; - } - function CreateCollectionPrototype(): Doc { - let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"), - { panX: 0, panY: 0, scale: 1, width: 500, height: 500 }); - return collProto; - } + protoProps.isPrototype = true; - function CreateKVPPrototype(): Doc { - let kvpProto = setupPrototypeOptions(kvpProtoId, "KVP_PROTO", KeyValueBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150 }); - return kvpProto; - } - function CreateVideoPrototype(): Doc { - let videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("annotations"), - { x: 0, y: 0, nativeWidth: 600, width: 300, backgroundLayout: VideoBox.LayoutString(), curPage: 0 }); - return videoProto; - } - function CreateAudioPrototype(): Doc { - let audioProto = setupPrototypeOptions(audioProtoId, "AUDIO_PROTO", AudioBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150 }); - return audioProto; - } + let dataDoc = MakeDataDelegate(proto, protoProps, data); + let viewDoc = Doc.MakeDelegate(dataDoc, delegId); - function CreateInstance(proto: Doc, data: Field, options: DocumentOptions, delegId?: string) { - const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); - if (!("author" in protoProps)) { - protoProps.author = CurrentUserUtils.email; + return Doc.assign(viewDoc, delegateProps); } - if (!("creationDate" in protoProps)) { - protoProps.creationDate = new DateField; + + /** + * This function receives the relevant top level document prototype + * and models a new instance by delegating from it. + * + * Note that it stores the data it recieves at the delegate's data key, + * and applies any document options to this new delegate / instance. + * @param proto the prototype from which to model this new delegate + * @param options initial values to apply to this new delegate + * @param value the data to store in this new delegate + */ + function MakeDataDelegate(proto: Doc, options: DocumentOptions, value: D) { + const deleg = Doc.MakeDelegate(proto); + deleg.data = value; + return Doc.assign(deleg, options); } - protoProps.isPrototype = true; - return SetDelegateOptions(SetInstanceOptions(proto, protoProps, data), delegateProps, delegId); - } + export function ImageDocument(url: string, options: DocumentOptions = {}) { + let inst = CreateInstanceFromProto(Prototypes.imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options }); + requestImageSize(window.origin + RouteStore.corsProxy + "/" + url) + .then((size: any) => { + let aspect = size.height / size.width; + if (!inst.proto!.nativeWidth) { + inst.proto!.nativeWidth = size.width; + } + inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect; + inst.proto!.height = NumCast(inst.proto!.width) * aspect; + }) + .catch((err: any) => console.log(err)); + return inst; - export function ImageDocument(url: string, options: DocumentOptions = {}) { - let inst = CreateInstance(imageProto, new ImageField(new URL(url)), { title: path.basename(url), ...options }); - requestImageSize(window.origin + RouteStore.corsProxy + "/" + url) - .then((size: any) => { - let aspect = size.height / size.width; - if (!inst.proto!.nativeWidth) { - inst.proto!.nativeWidth = size.width; + // 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 VideoDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.videoProto, new VideoField(new URL(url)), options); + } + + export function AudioDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.audioProto, new AudioField(new URL(url)), options); + } + + export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.histoProto, new HistogramField(histoOp), options); + } + + export function TextDocument(options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.textProto, "", options); + } + + export function IconDocument(icon: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.iconProto, new IconField(icon), options); + } + + export function PdfDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.pdfProto, new PdfField(new URL(url)), options); + } + + export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) { + let schemaName = options.title ? options.title : "-no schema-"; + let ctlog = await Gateway.Instance.GetSchema(url, schemaName); + if (ctlog && ctlog.schemas) { + let schema = ctlog.schemas[0]; + let schemaDoc = Docs.Create.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! }); + let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []); + if (!schemaDocuments) { + return; } - inst.proto!.nativeHeight = Number(inst.proto!.nativeWidth!) * aspect; - inst.proto!.height = NumCast(inst.proto!.width) * aspect; - }) - .catch((err: any) => console.log(err)); - return inst; - // 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 VideoDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(videoProto, new VideoField(new URL(url)), options); - } - export function AudioDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(audioProto, new AudioField(new URL(url)), options); - } + CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc); + const docs = schemaDocuments; + CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { + DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt) => { + if (field instanceof Doc) { + docs.push(field); + } else { + var atmod = new ColumnAttributeModel(attr); + let histoOp = new HistogramOperation(schema.displayName!, + new AttributeTransformationModel(atmod, AggregateFunction.None), + new AttributeTransformationModel(atmod, AggregateFunction.Count), + new AttributeTransformationModel(atmod, AggregateFunction.Count)); + docs.push(Docs.Create.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! })); + } + })); + }); + return schemaDoc; + } + return Docs.Create.TreeDocument([], { width: 50, height: 100, title: schemaName }); + } - export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) { - return CreateInstance(histoProto, new HistogramField(histoOp), options); - } - export function TextDocument(options: DocumentOptions = {}) { - return CreateInstance(textProto, "", options); - } - export function IconDocument(icon: string, options: DocumentOptions = {}) { - return CreateInstance(iconProto, new IconField(icon), options); - } - export function PdfDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(pdfProto, new PdfField(new URL(url)), options); - } + export function WebDocument(url: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.webProto, new WebField(new URL(url)), options); + } + + export function HtmlDocument(html: string, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.webProto, new HtmlField(html), options); + } - export async function DBDocument(url: string, options: DocumentOptions = {}, columnOptions: DocumentOptions = {}) { - let schemaName = options.title ? options.title : "-no schema-"; - let ctlog = await Gateway.Instance.GetSchema(url, schemaName); - if (ctlog && ctlog.schemas) { - let schema = ctlog.schemas[0]; - let schemaDoc = Docs.TreeDocument([], { ...options, nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: schema.displayName! }); - let schemaDocuments = Cast(schemaDoc.data, listSpec(Doc), []); - if (!schemaDocuments) { - return; + export function KVPDocument(document: Doc, options: DocumentOptions = {}) { + return CreateInstanceFromProto(Prototypes.kvpProto, document, { title: document.title + ".kvp", ...options }); + } + + export function FreeformDocument(documents: Array, options: DocumentOptions, makePrototype: boolean = true) { + if (!makePrototype) { + return MakeDataDelegate(Prototypes.collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents)); } - CurrentUserUtils.AddNorthstarSchema(schema, schemaDoc); - const docs = schemaDocuments; - CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt) => { - if (field instanceof Doc) { - docs.push(field); - } else { - var atmod = new ColumnAttributeModel(attr); - let histoOp = new HistogramOperation(schema.displayName!, - new AttributeTransformationModel(atmod, AggregateFunction.None), - new AttributeTransformationModel(atmod, AggregateFunction.Count), - new AttributeTransformationModel(atmod, AggregateFunction.Count)); - docs.push(Docs.HistogramDocument(histoOp, { ...columnOptions, width: 200, height: 200, title: attr.displayName! })); + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform }); + } + + export function SchemaDocument(schemaColumns: string[], documents: Array, options: DocumentOptions) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema }); + } + + export function TreeDocument(documents: Array, options: DocumentOptions) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree }); + } + + export function StackingDocument(documents: Array, options: DocumentOptions) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking }); + } + + export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { + return CreateInstanceFromProto(Prototypes.collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); + } + + export type DocConfig = { + doc: Doc, + initialWidth?: number + } + + export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = "row") { + let layoutConfig = { + content: [ + { + type: type, + content: [ + ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth)) + ] } - })); - }); - return schemaDoc; + ] + }; + return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id); } - return Docs.TreeDocument([], { width: 50, height: 100, title: schemaName }); - } - export function WebDocument(url: string, options: DocumentOptions = {}) { - return CreateInstance(webProto, new WebField(new URL(url)), options); - } - export function HtmlDocument(html: string, options: DocumentOptions = {}) { - return CreateInstance(webProto, new HtmlField(html), options); - } - export function KVPDocument(document: Doc, options: DocumentOptions = {}) { - return CreateInstance(kvpProto, document, { title: document.title + ".kvp", ...options }); - } - export function FreeformDocument(documents: Array, options: DocumentOptions, makePrototype: boolean = true) { - if (!makePrototype) { - return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents)); + + export function CaptionDocument(doc: Doc) { + const captionDoc = Doc.MakeAlias(doc); + captionDoc.overlayLayout = Templating.FixedCaption(); + captionDoc.width = Cast(doc.width, "number", 0); + captionDoc.height = Cast(doc.height, "number", 0); + return captionDoc; } - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Freeform }); - } - export function SchemaDocument(schemaColumns: string[], documents: Array, options: DocumentOptions) { - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema }); - } - export function TreeDocument(documents: Array, options: DocumentOptions) { - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree }); - } - export function StackingDocument(documents: Array, options: DocumentOptions) { - return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking }); - } - export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Docking, dockingConfig: config }, id); - } - export type DocConfig = { - doc: Doc, - initialWidth?: number - } - export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = "row") { - let layoutConfig = { - content: [ - { - type: type, - content: [ - ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth)) - ] - } - ] - }; - return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id); } - export function CaptionDocument(doc: Doc) { - const captionDoc = Doc.MakeAlias(doc); - captionDoc.overlayLayout = FixedCaption(); - captionDoc.width = Cast(doc.width, "number", 0); - captionDoc.height = Cast(doc.height, "number", 0); - return captionDoc; - } + export namespace Templating { - // example of custom display string for an image that shows a caption. - function EmbeddedCaption() { - return `
-
` - + ImageBox.LayoutString() + - `
-
` - + FormattedTextBox.LayoutString("caption") + - `
-
`; - } - export function FixedCaption(fieldName: string = "caption") { - return `
-
` - + FormattedTextBox.LayoutString(fieldName) + - `
-
`; - } + // example of custom display string for an image that shows a caption. + export function EmbeddedCaption() { + return `
+
` + + ImageBox.LayoutString() + + `
+
` + + FormattedTextBox.LayoutString("caption") + + `
+
`; + } - function OuterCaption() { - return (` -
-
- {layout} -
-
- -
-
- `); - } - function InnerCaption() { - return (` -
-
- {layout} -
-
- -
-
+ export function FixedCaption(fieldName: string = "caption") { + return `
+
` + + FormattedTextBox.LayoutString(fieldName) + + `
+
`; + } + + export function OuterCaption() { + return (` +
+
+ {layout} +
+
+ +
+
`); - } + } - /* + export function InnerCaption() { + return (` +
+
+ {layout} +
+
+ +
+
+ `); + } - this template requires an additional style setting on the collectionView-cont to make the layout relative - -.collectionView-cont { - position: relative; - width: 100%; - height: 100%; -} - */ - function Percentaption() { - return (` -
-
- {layout} -
-
- -
-
+ /* + this template requires an additional style setting on the collectionView-cont to make the layout relative + .collectionView-cont { + position: relative; + width: 100%; + height: 100%; + } + */ + export function PercentCaption() { + return (` +
+
+ {layout} +
+
+ +
+
`); + } + + } +} + +export namespace DocUtils { + + export function MakeLink(source: Doc, target: Doc) { + let protoSrc = source.proto ? source.proto : source; + let protoTarg = target.proto ? target.proto : target; + UndoManager.RunInBatch(() => { + let linkDoc = Docs.Create.TextDocument({ width: 100, height: 30, borderRounding: -1 }); + //let linkDoc = new Doc; + linkDoc.proto!.title = "-link name-"; + linkDoc.proto!.linkDescription = ""; + linkDoc.proto!.linkTags = "Default"; + + linkDoc.proto!.linkedTo = target; + linkDoc.proto!.linkedToPage = target.curPage; + linkDoc.proto!.linkedFrom = source; + linkDoc.proto!.linkedFromPage = source.curPage; + + let linkedFrom = Cast(protoTarg.linkedFromDocs, listSpec(Doc)); + if (!linkedFrom) { + protoTarg.linkedFromDocs = linkedFrom = new List(); + } + linkedFrom.push(linkDoc); + + let linkedTo = Cast(protoSrc.linkedToDocs, listSpec(Doc)); + if (!linkedTo) { + protoSrc.linkedToDocs = linkedTo = new List(); + } + linkedTo.push(linkDoc); + return linkDoc; + }, "make link"); } + } \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index da9b1253e..787033455 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -274,7 +274,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @undoBatch @action createIcon = (selected: DocumentView[], layoutString: string): Doc => { let doc = selected[0].props.Document; - let iconDoc = Docs.IconDocument(layoutString); + let iconDoc = Docs.Create.IconDocument(layoutString); iconDoc.isButton = true; iconDoc.proto!.title = selected.length > 1 ? "-multiple-.icon" : StrCast(doc.title) + ".icon"; iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 3d9750a85..98b14f9c8 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom'; import * as React from 'react'; (async () => { - await Docs.initProtos(); + await Docs.Prototypes.initialize(); await CurrentUserUtils.loadCurrentUser(); ReactDOM.render(, document.getElementById('root')); })(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 426e2440a..984db0426 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -55,7 +55,7 @@ export class MainView extends React.Component { private set mainContainer(doc: Opt) { if (doc) { if (!("presentationView" in doc)) { - doc.presentationView = Docs.TreeDocument([], { title: "Presentation" }); + doc.presentationView = Docs.Create.TreeDocument([], { title: "Presentation" }); } CurrentUserUtils.UserDocument.activeWorkspace = doc; } @@ -151,12 +151,12 @@ export class MainView extends React.Component { createNewWorkspace = async (id?: string) => { const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc)); if (list) { - let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` }); + let freeformDoc = Docs.Create.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` }); let configs = [ { doc: CurrentUserUtils.UserDocument, initialWidth: 150 }, { doc: freeformDoc, initialWidth: 600 } ] - let mainDoc = Docs.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id); + let mainDoc = Docs.Create.StandardCollectionDockingDocument(configs, { title: `Workspace ${list.length + 1}` }, id); list.push(mainDoc); // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) setTimeout(() => { @@ -242,18 +242,18 @@ export class MainView extends React.Component { let audiourl = "http://techslides.com/demos/samples/sample.mp3"; let videourl = "http://techslides.com/demos/sample-videos/small.mp4"; - let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" })); - let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); - let addDockingNode = action(() => Docs.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" })); - let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" })); + let addTextNode = action(() => Docs.Create.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" })); + let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); + let addDockingNode = action(() => Docs.Create.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" })); + let addSchemaNode = action(() => Docs.Create.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" })); let addTreeNode = action(() => CurrentUserUtils.UserDocument); //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" })); // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" })); - let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" })); - let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" })); - let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); - let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); - let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" })); + let addVideoNode = action(() => Docs.Create.VideoDocument(videourl, { width: 200, title: "video node" })); + let addPDFNode = action(() => Docs.Create.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" })); + let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); + let addWebNode = action(() => Docs.Create.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); + let addAudioNode = action(() => Docs.Create.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" })); let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "font", "Add Textbox", addTextNode], diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx index 63d2065e2..7164d98a4 100644 --- a/src/client/views/SearchBox.tsx +++ b/src/client/views/SearchBox.tsx @@ -166,7 +166,7 @@ export class SearchBox extends React.Component { y += 300; } } - return Docs.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` }); + return Docs.Create.FreeformDocument(docs, { width: 400, height: 400, panX: 175, panY: 175, backgroundColor: "grey", title: `Search Docs: "${this.searchString}"` }); } // Useful queries: diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index dfb8fac35..e2bcb10ec 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -6,50 +6,32 @@ import * as ReactDOM from 'react-dom'; import Measure, { ContentRect } from "react-measure"; import * as GoldenLayout from "../../../client/goldenLayout"; import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc"; -import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnTrue, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; -import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; import React = require("react"); import { ParentDocSelector } from './ParentDocumentSelector'; import { DocumentManager } from '../../util/DocumentManager'; -import { CollectionViewType } from './CollectionBaseView'; import { Id } from '../../../new_fields/FieldSymbols'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { DockedFrameRenderer } from './DockedFrameRenderer'; @observer export class CollectionDockingView extends React.Component { public static TopLevel: CollectionDockingView; - public static makeDocumentConfig(document: Doc, width?: number) { - return { - type: 'react-component', - component: 'DocumentFrameRenderer', - title: document.title, - width: width, - props: { - documentId: document[Id], - } - }; - } - - private makeDocConfig = (document: Doc, width?: number) => { - const config = CollectionDockingView.makeDocumentConfig(document, width); - (config.props as any).parent = this; - return config; - } - private _goldenLayout: any = null; private _containerRef = React.createRef(); + reactionDisposer?: IReactionDisposer; + _removedDocs: Doc[] = []; private _flush: boolean = false; private _ignoreStateChange = ""; private _isPointerDown = false; + hack: boolean = false; + undohack: any = null; constructor(props: SubCollectionViewProps) { super(props); @@ -57,32 +39,93 @@ export class CollectionDockingView extends React.Component - CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. - onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); + + componentDidMount: () => void = () => { + if (this._containerRef.current) { + this.reactionDisposer = reaction( + () => StrCast(this.props.Document.dockingConfig), + () => { + if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) { + // Because this is in a set timeout, if this component unmounts right after mounting, + // we will leak a GoldenLayout, because we try to destroy it before we ever create it + setTimeout(() => this.setupGoldenLayout(), 1); + } + this._ignoreStateChange = ""; + }, { fireImmediately: true }); + + // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + } } - private openFullScreen = (document: Doc) => { - let newItemStackConfig = { - type: 'stack', - content: [this.makeDocConfig(document)] - }; - var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); - this._goldenLayout.root.contentItems[0].addChild(docconfig); - docconfig.callDownwards('_$init'); - this._goldenLayout._$maximiseItem(docconfig); - this._ignoreStateChange = this.retrieveConfiguration(); - this.stateChanged(); + componentWillUnmount: () => void = () => { + try { + this._goldenLayout.unbind('itemDropped', this.itemDropped); + this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); + } catch (e) { + console.log("Unable to unbind Golden Layout event listener...", e); + } + if (this._goldenLayout) this._goldenLayout.destroy(); + this._goldenLayout = null; + + if (this.reactionDisposer) { + this.reactionDisposer(); + } } - @action - public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) { - dockingView.openFullScreen(document); + setupGoldenLayout() { + var config = StrCast(this.props.Document.dockingConfig); + if (config) { + if (!this._goldenLayout) { + this.initializeConfiguration(config); + } + else { + if (config === this.retrieveConfiguration()) { + return; + } + try { + this._goldenLayout.unbind('itemDropped', this.itemDropped); + this._goldenLayout.unbind('tabCreated', this.tabCreated); + this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); + this._goldenLayout.unbind('stackCreated', this.stackCreated); + } catch (e) { } + this._goldenLayout.destroy(); + this.initializeConfiguration(config); + } + this._goldenLayout.on('itemDropped', this.itemDropped); + this._goldenLayout.on('tabCreated', this.tabCreated); + this._goldenLayout.on('tabDestroyed', this.tabDestroyed); + this._goldenLayout.on('stackCreated', this.stackCreated); + this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer); + this._goldenLayout.container = this._containerRef.current; + if (this._goldenLayout.config.maximisedItemId === '__glMaximised') { + try { + this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise(); + } catch (e) { + this._goldenLayout.config.maximisedItemId = null; + } + } + this._goldenLayout.init(); + } + } + + private makeDocConfig = (document: Doc, width?: number) => { + const config = CollectionDockingView.makeDocumentConfig(document, width); + (config.props as any).parent = this; + return config; + } + + public static makeDocumentConfig(document: Doc, width?: number) { + return { + type: 'react-component', + component: 'DocumentFrameRenderer', + title: document.title, + width: width, + props: { + documentId: document[Id], + } + }; } initializeConfiguration = (configText: string) => { @@ -109,44 +152,29 @@ export class CollectionDockingView extends React.Component { - let retVal = false; - if (dockingView._goldenLayout.root.contentItems[0].isRow) { - retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { - if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && - Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { - child.contentItems[0].remove(); - dockingView.layoutChanged(document); - return true; - } else { - Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { - if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { - child.contentItems[j].remove(); - child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); - let docs = Cast(dockingView.props.Document.data, listSpec(Doc)); - docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); - return true; - } - return false; - }); - } - return false; - }); - } - if (retVal) { - dockingView.stateChanged(); - } - return retVal; + public StartOtherDrag(dragDocs: Doc[], e: any) { + this.hack = true; + this.undohack = UndoManager.StartBatch("goldenDrag"); + dragDocs.map(dragDoc => + CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener. + onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 })); } @action - layoutChanged(removed?: Doc) { - this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); - this._goldenLayout.emit('stateChanged'); + public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) { + dockingView.openFullScreen(document); + } + + private openFullScreen = (document: Doc) => { + let newItemStackConfig = { + type: 'stack', + content: [this.makeDocConfig(document)] + }; + var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout); + this._goldenLayout.root.contentItems[0].addChild(docconfig); + docconfig.callDownwards('_$init'); + this._goldenLayout._$maximiseItem(docconfig); this._ignoreStateChange = this.retrieveConfiguration(); - if (removed) CollectionDockingView.TopLevel._removedDocs.push(removed); this.stateChanged(); } @@ -209,75 +237,47 @@ export class CollectionDockingView extends React.Component { + let retVal = false; + if (dockingView._goldenLayout.root.contentItems[0].isRow) { + retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => { + if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" && + Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) { + child.contentItems[0].remove(); + dockingView.layoutChanged(document); + return true; + } else { + Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => { + if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) { + child.contentItems[j].remove(); + child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0); + let docs = Cast(dockingView.props.Document.data, listSpec(Doc)); + docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1); + return true; + } + return false; + }); } - } - this._goldenLayout.init(); + return false; + }); } - } - reactionDisposer?: IReactionDisposer; - componentDidMount: () => void = () => { - if (this._containerRef.current) { - this.reactionDisposer = reaction( - () => StrCast(this.props.Document.dockingConfig), - () => { - if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) { - // Because this is in a set timeout, if this component unmounts right after mounting, - // we will leak a GoldenLayout, because we try to destroy it before we ever create it - setTimeout(() => this.setupGoldenLayout(), 1); - } - this._ignoreStateChange = ""; - }, { fireImmediately: true }); - - // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + if (retVal) { + dockingView.stateChanged(); } + return retVal; } - componentWillUnmount: () => void = () => { - try { - this._goldenLayout.unbind('itemDropped', this.itemDropped); - this._goldenLayout.unbind('tabCreated', this.tabCreated); - this._goldenLayout.unbind('stackCreated', this.stackCreated); - this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed); - } catch (e) { - } - if (this._goldenLayout) this._goldenLayout.destroy(); - this._goldenLayout = null; - // window.removeEventListener('resize', this.onResize); - - if (this.reactionDisposer) { - this.reactionDisposer(); - } + @action + layoutChanged(removed?: Doc) { + this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); + this._goldenLayout.emit('stateChanged'); + this._ignoreStateChange = this.retrieveConfiguration(); + if (removed) CollectionDockingView.TopLevel._removedDocs.push(removed); + this.stateChanged(); } + @action onResize = (size: ContentRect) => { // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed @@ -387,6 +387,9 @@ export class CollectionDockingView extends React.Component { + console.log("DROPPPP THE BASS!", e); + } ReactDOM.render( CollectionDockingView.AddTab(stack, doc)} />, upDiv); tab.reactComponents = [upDiv]; tab.element.append(upDiv); @@ -422,7 +425,6 @@ export class CollectionDockingView extends React.Component { //stack.header.controlsContainer.find('.lm_popout').hide(); @@ -462,102 +464,4 @@ export class CollectionDockingView extends React.Component { - _mainCont = React.createRef(); - @observable private _panelWidth = 0; - @observable private _panelHeight = 0; - @observable private _document: Opt; - private get parentProps(): SubCollectionViewProps { - return this.props.parent.props; - } - - get _stack(): any { - let parent = this.props.glContainer.parent.parent; - if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) - return parent.parent.contentItems[1]; - return parent; - } - constructor(props: any) { - super(props); - DocServer.GetRefField(this.props.documentId).then(action((f: Opt) => this._document = f as Doc)); - } - - nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth); - nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight); - contentScaling = () => { - const nativeH = this.nativeHeight(); - const nativeW = this.nativeWidth(); - let wscale = this._panelWidth / nativeW; - return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale; - } - - ScreenToLocalTransform = () => { - if (this._mainCont.current && this._mainCont.current.children) { - let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement); - scale = Utils.GetScreenTransform(this._mainCont.current).scale; - return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); - } - return Transform.Identity(); - } - get scaleToFitMultiplier() { - let docWidth = NumCast(this._document!.width); - let docHeight = NumCast(this._document!.height); - if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1; - if (StrCast(this._document!.layout).indexOf("Collection") === -1 || - NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1; - let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ? - this._panelHeight / docHeight : this._panelWidth / docWidth); - return scaling; - } - get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } - - addDocTab = (doc: Doc, location: string) => { - if (location === "onRight") { - CollectionDockingView.AddRightSplit(doc); - } else { - CollectionDockingView.AddTab(this._stack, doc); - } - } - get content() { - if (!this._document) { - return (null); - } - return ( -
- -
); - } - - render() { - let theContent = this.content; - return !this._document ? (null) : - { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}> - {({ measureRef }) =>
{theContent}
} -
; - } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 11d71d023..477879b79 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -261,7 +261,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { let dbName = StrCast(this.props.Document.title); let res = await Gateway.Instance.PostSchema(csv, dbName); if (self.props.CollectionView.props.addDocument) { - let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document }); + let schemaDoc = await Docs.Create.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document }); if (schemaDoc) { //self.props.CollectionView.props.addDocument(schemaDoc, false); self.props.Document.schemaDoc = schemaDoc; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index be37efd3d..440a2410b 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -113,20 +113,20 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise> { let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise)) | undefined = undefined; if (type.indexOf("image") !== -1) { - ctor = Docs.ImageDocument; + ctor = Docs.Create.ImageDocument; } if (type.indexOf("video") !== -1) { - ctor = Docs.VideoDocument; + ctor = Docs.Create.VideoDocument; } if (type.indexOf("audio") !== -1) { - ctor = Docs.AudioDocument; + ctor = Docs.Create.AudioDocument; } if (type.indexOf("pdf") !== -1) { - ctor = Docs.PdfDocument; + ctor = Docs.Create.PdfDocument; options.nativeWidth = 1200; } if (type.indexOf("excel") !== -1) { - ctor = Docs.DBDocument; + ctor = Docs.Create.DBDocument; options.dropAction = "copy"; } if (type.indexOf("html") !== -1) { @@ -145,7 +145,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { }); return undefined; } - ctor = Docs.WebDocument; + ctor = Docs.Create.WebDocument; options = { height: options.width, ...options, title: path, nativeWidth: undefined }; } return ctor ? ctor(path, options) : undefined; @@ -175,13 +175,13 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { return; } if (html && html.indexOf(" { onWorkspaceContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped()) { // 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: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) }); - ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" }); + ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" }); if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" }); ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" }); diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 7853544d5..bd5cd5450 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -98,7 +98,7 @@ export class CollectionVideoView extends React.Component { SearchBox.convertDataUri(dataUrl, filename).then((returnedFilename) => { if (returnedFilename) { let url = DocServer.prepend(returnedFilename); - let imageSummary = Docs.ImageDocument(url, { + let imageSummary = Docs.Create.ImageDocument(url, { x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" }); diff --git a/src/client/views/collections/DockedFrameRenderer.tsx b/src/client/views/collections/DockedFrameRenderer.tsx new file mode 100644 index 000000000..25d4b2a49 --- /dev/null +++ b/src/client/views/collections/DockedFrameRenderer.tsx @@ -0,0 +1,116 @@ +import 'golden-layout/src/css/goldenlayout-base.css'; +import 'golden-layout/src/css/goldenlayout-dark-theme.css'; +import { action, observable, reaction, Lambda, IReactionDisposer } from "mobx"; +import { observer } from "mobx-react"; +import Measure, { ContentRect } from "react-measure"; +import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc"; +import { FieldId } from "../../../new_fields/RefField"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction, returnTrue, Utils } from "../../../Utils"; +import { DocServer } from "../../DocServer"; +import { Transform } from '../../util/Transform'; +import { DocumentView } from "../nodes/DocumentView"; +import "./CollectionDockingView.scss"; +import { SubCollectionViewProps } from "./CollectionSubView"; +import React = require("react"); +import { CollectionViewType } from './CollectionBaseView'; +import { Id } from '../../../new_fields/FieldSymbols'; +import { CollectionDockingView } from './CollectionDockingView'; + +interface DockedFrameProps { + documentId: FieldId; + glContainer: any; + glEventHub: any; + parent: CollectionDockingView; +} + +@observer +export class DockedFrameRenderer extends React.Component { + _mainCont = React.createRef(); + @observable private _panelWidth = 0; + @observable private _panelHeight = 0; + @observable private _document: Opt; + private get parentProps(): SubCollectionViewProps { + return this.props.parent.props; + } + + get _stack(): any { + let parent = this.props.glContainer.parent.parent; + if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1) + return parent.parent.contentItems[1]; + return parent; + } + constructor(props: any) { + super(props); + DocServer.GetRefField(this.props.documentId).then(action((f: Opt) => this._document = f as Doc)); + } + + nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth); + nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight); + contentScaling = () => { + const nativeH = this.nativeHeight(); + const nativeW = this.nativeWidth(); + let wscale = this._panelWidth / nativeW; + return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale; + } + + ScreenToLocalTransform = () => { + if (this._mainCont.current && this._mainCont.current.children) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement); + scale = Utils.GetScreenTransform(this._mainCont.current).scale; + return this.parentProps.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); + } + return Transform.Identity(); + } + get scaleToFitMultiplier() { + let docWidth = NumCast(this._document!.width); + let docHeight = NumCast(this._document!.height); + if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1; + if (StrCast(this._document!.layout).indexOf("Collection") === -1 || + NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1; + let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ? + this._panelHeight / docHeight : this._panelWidth / docWidth); + return scaling; + } + get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } + + addDocTab = (doc: Doc, location: string) => { + if (location === "onRight") { + CollectionDockingView.AddRightSplit(doc); + } else { + CollectionDockingView.AddTab(this._stack, doc); + } + } + get content() { + if (!this._document) { + return (null); + } + return ( +
+ +
); + } + + render() { + let theContent = this.content; + return !this._document ? (null) : + { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}> + {({ measureRef }) =>
{theContent}
} +
; + } +} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 29734fa19..cd386abfa 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -79,7 +79,7 @@ export class MarqueeView extends React.Component } ns.map(line => { let indent = line.search(/\S|$/); - let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); + let newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); this.props.addDocument(newBox, false); y += 40 * this.props.getTransform().Scale; }); @@ -89,13 +89,13 @@ export class MarqueeView extends React.Component navigator.clipboard.readText().then(text => { let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== ""); if (ns.length === 1 && text.startsWith("http")) { - this.props.addDocument(Docs.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer + this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer } else { this.pasteTable(ns, x, y); } }); } else { - let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); + let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); this.props.addLiveTextDocument(newBox); } e.stopPropagation(); @@ -136,7 +136,7 @@ export class MarqueeView extends React.Component doc.width = 200; docList.push(doc); } - let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 }); + let newCol = Docs.Create.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 }); this.props.addDocument(newCol, false); } @@ -259,7 +259,7 @@ export class MarqueeView extends React.Component let ink = Cast(this.props.container.props.Document.ink, InkField); let inkData = ink ? ink.inkData : undefined; let zoomBasis = NumCast(this.props.container.props.Document.scale, 1); - let newCollection = Docs.FreeformDocument(selected, { + let newCollection = Docs.Create.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panX: 0, @@ -283,14 +283,14 @@ export class MarqueeView extends React.Component d.page = -1; return d; }); - let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); newCollection.proto!.summaryDoc = summary; selected = [newCollection]; newCollection.x = bounds.left + bounds.width; summary.proto!.subBulletDocs = new List(selected); //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" summary.templates = new List([Templates.Bullet.Layout]); - let container = Docs.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" }); + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" }); container.viewType = CollectionViewType.Stacking; this.props.addLiveTextDocument(container); // }); @@ -303,11 +303,11 @@ export class MarqueeView extends React.Component d.page = -1; return d; }); - let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => { if (returnedFilename) { let url = DocServer.prepend(returnedFilename); - let imageSummary = Docs.ImageDocument(url, { + let imageSummary = Docs.Create.ImageDocument(url, { x: bounds.left, y: bounds.top + 100 / zoomBasis, width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-" }); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index efba26c2c..16e40000d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -302,7 +302,7 @@ export class DocumentView extends DocComponent(Docu } deleteClicked = (): void => { this.props.removeDocument && this.props.removeDocument(this.props.Document); } - fieldsClicked = (): void => { this.props.addDocTab(Docs.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") }; + fieldsClicked = (): void => { this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }), "onRight") }; makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); doc.isButton = !BoolCast(doc.isButton, false); @@ -418,7 +418,7 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: "Find aliases", event: async () => { const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document); - this.props.addDocTab && this.props.addDocTab(Docs.SchemaDocument(["title"], aliases, {}), "onRight"); + this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), "onRight"); }, icon: "search" }); cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document), icon: "crosshairs" }); diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index bfc1738fc..a8f94b746 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -33,7 +33,7 @@ class Uploader extends React.Component { onClick = async () => { try { this.status = "initializing protos"; - await Docs.initProtos(); + await Docs.Prototypes.initialize(); let imgPrev = document.getElementById("img_preview"); if (imgPrev) { let files: FileList | null = inputRef.current!.files; @@ -53,7 +53,7 @@ class Uploader extends React.Component { const json = await res.json(); json.map(async (file: any) => { let path = window.location.origin + file; - var doc = Docs.ImageDocument(path, { nativeWidth: 200, width: 200, title: name }); + var doc = Docs.Create.ImageDocument(path, { nativeWidth: 200, width: 200, title: name }); this.status = "getting user document"; diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 7f7263cf1..af65f5482 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -172,6 +172,18 @@ export namespace Doc { } return protos; } + + /** + * This function is intended to model Object.assign({}, {}) [https://mzl.la/1Mo3l21], which copies + * the values of the properties of a source object into the target. + * + * This is just a specific, Dash-authored version that serves the same role for our + * Doc class. + * + * @param doc the target document into which you'd like to insert the new fields + * @param fields the fields to project onto the target. Its type signature defines a mapping from some string key + * to a potentially undefined field, where each entry in this mapping is optional. + */ export function assign(doc: Doc, fields: Partial>>) { for (const key in fields) { if (fields.hasOwnProperty(key)) { diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 2b304c373..8caceb063 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -60,6 +60,7 @@ export function getter(target: any, prop: string | symbol | number, receiver: an } return getField(target, prop); } + function getProtoField(protoField: Doc | undefined, prop: string | number, cb?: (field: Field | undefined) => void) { if (!protoField) return undefined; let field = protoField[prop]; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index e5b7a025b..169be3b99 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -33,8 +33,8 @@ export class CurrentUserUtils { doc.title = this.email; doc.data = new List(); doc.excludeFromLibrary = true; - doc.optionalRightCollection = Docs.StackingDocument([], { title: "New mobile uploads" }); - // doc.library = Docs.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` }); + doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" }); + // doc.library = Docs.Create.TreeDocument([doc], { title: `Library: ${CurrentUserUtils.email}` }); // (doc.library as Doc).excludeFromLibrary = true; return doc; } @@ -94,12 +94,12 @@ export class CurrentUserUtils { // new AttributeTransformationModel(atmod, AggregateFunction.None), // new AttributeTransformationModel(atmod, AggregateFunction.Count), // new AttributeTransformationModel(atmod, AggregateFunction.Count)); - // schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! })); + // schemaDocuments.push(Docs.Create.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! })); // } // }))); // return promises; // }, [] as Promise[])); - // return CurrentUserUtils._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })); + // return CurrentUserUtils._northstarSchemas.push(Docs.Create.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })); // }); // } } -- cgit v1.2.3-70-g09d2