diff options
Diffstat (limited to 'src/client/documents/Documents.ts')
-rw-r--r-- | src/client/documents/Documents.ts | 319 |
1 files changed, 218 insertions, 101 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 948433bd1..da8efe745 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -16,20 +16,16 @@ 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 { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; -import { IconBox } from "../views/nodes/IconBox"; -import { OmitKeys, JSONUtils } from "../../Utils"; +import { OmitKeys, JSONUtils, Utils } from "../../Utils"; import { Field, Doc, Opt, DocListCastAsync, FieldResult, DocListCast } from "../../new_fields/Doc"; import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; -import { Cast, NumCast } from "../../new_fields/Types"; -import { IconField } from "../../new_fields/IconField"; +import { Cast, NumCast, StrCast } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { DocServer } from "../DocServer"; import { dropActionType } from "../util/DragManager"; import { DateField } from "../../new_fields/DateField"; -import { UndoManager, undoBatch } from "../util/UndoManager"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { LinkManager } from "../util/LinkManager"; @@ -37,6 +33,7 @@ import { DocumentManager } from "../util/DocumentManager"; import DirectoryImportBox from "../util/Import & Export/DirectoryImportBox"; import { Scripting } from "../util/Scripting"; import { ButtonBox } from "../views/nodes/ButtonBox"; +import { SliderBox } from "../views/nodes/SliderBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField"; import { PresBox } from "../views/nodes/PresBox"; @@ -48,8 +45,8 @@ import { SearchBox } from "../views/search/SearchBox"; //import { PresBox } from "../views/nodes/PresBox"; //import { PresField } from "../../new_fields/PresField"; -import { LinkFollowBox } from "../views/linking/LinkFollowBox"; import { PresElementBox } from "../views/presentationview/PresElementBox"; +import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { QueryBox } from "../views/nodes/QueryBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { DocuLinkBox } from "../views/nodes/DocuLinkBox"; @@ -58,6 +55,12 @@ import { InkingStroke } from "../views/InkingStroke"; import { InkField } from "../../new_fields/InkField"; import { InkingControl } from "../views/InkingControl"; import { RichTextField } from "../../new_fields/RichTextField"; +import { extname } from "path"; +import { MessageStore } from "../../server/Message"; +import { ContextMenuProps } from "../views/ContextMenuItem"; +import { ContextMenu } from "../views/ContextMenu"; +import { LinkBox } from "../views/nodes/LinkBox"; +import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; const requestImageSize = require('../util/request-image-size'); const path = require('path'); @@ -72,74 +75,89 @@ export interface DocumentOptions { _fitWidth?: boolean; _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents _LODdisable?: boolean; - _dropAction?: dropActionType; + _showTitleHover?: string; // + _showTitle?: string; // which field to display in the title area. leave empty to have no title + _showCaption?: string; // which field to display in the caption area. leave empty to have no caption + _scrollTop?: number; // scroll location for pdfs _chromeStatus?: string; _viewType?: number; _gridGap?: number; // gap between items in masonry view _xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts _yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts - _textTemplate?: RichTextField; // template used by a formattedTextBox to create a text box to render + _xPadding?: number; + _yPadding?: number; _itemIndex?: number; // which item index the carousel viewer is showing - _hideSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts + _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts + _singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document) x?: number; y?: number; z?: number; + dropAction?: dropActionType; + childDropAction?: dropActionType; layoutKey?: string; type?: string; title?: string; + style?: string; page?: number; scale?: number; isDisplayPanel?: boolean; // whether the panel functions as GoldenLayout "stack" used to display documents forceActive?: boolean; - preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view layout?: string | Doc; hideHeadings?: boolean; // whether stacking view column headings should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; templates?: List<string>; - backgroundColor?: string | ScriptField; + backgroundColor?: string | ScriptField; // background color for data doc + _backgroundColor?: string | ScriptField; // background color for each template layout doc ( overrides backgroundColor ) + color?: string; // foreground color data doc + _color?: string; // foreground color for each template layout doc (overrides color) + caption?: RichTextField; ignoreClick?: boolean; lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed opacity?: number; defaultBackgroundColor?: string; + dontSelect?: boolean; // whether document decorations should be displayed when the document is selected + isBackground?: boolean; + isButton?: boolean; columnWidth?: number; fontSize?: number; curPage?: number; currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) value is in seconds displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) - documentText?: string; borderRounding?: string; boxShadow?: string; - sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views + dontRegisterChildren?: boolean; + _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views schemaColumns?: List<SchemaHeaderField>; dockingConfig?: string; annotationOn?: Doc; removeDropProperties?: List<string>; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document dbDoc?: Doc; + linkRelationship?: string; // type of relatinoship a link represents ischecked?: ScriptField; // returns whether a font icon box is checked activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) onClick?: ScriptField; onChildClick?: ScriptField; // script given to children of a collection to execute when they are clicked onPointerDown?: ScriptField; onPointerUp?: ScriptField; + dropConverter?: ScriptField; // script to run when documents are dropped on this Document. dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop - clipboard?: Doc; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop + clipboard?: Doc; icon?: string; sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script - dropConverter?: ScriptField; // script to run when documents are dropped on this Document. strokeWidth?: number; - color?: string; + treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view treeViewHideTitle?: boolean; // whether to hide the title of a tree view + treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewOpen?: boolean; // whether this document is expanded in a tree view treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked isFacetFilter?: boolean; // whether document functions as a facet filter in a tree view limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents // [key: string]: Opt<Field>; pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown - isExpanded?: boolean; // is linear view expanded textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; @@ -148,6 +166,7 @@ export interface DocumentOptions { searchText?: string, //for searchbox sq?: string, fq?: string, + linearViewIsExpanded?: boolean; // is linear view expanded } class EmptyBox { @@ -174,8 +193,8 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.TEXT, { - layout: { view: FormattedTextBox, dataField: data }, - options: { _height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" } + layout: { view: FormattedTextBox, dataField: "text" }, + options: { _height: 150, _xMargin: 10, _yMargin: 10 } }], [DocumentType.HIST, { layout: { view: HistogramBox, dataField: data }, @@ -199,7 +218,7 @@ export namespace Docs { }], [DocumentType.COL, { layout: { view: CollectionView, dataField: data }, - options: { _panX: 0, _panY: 0, scale: 1, _width: 500, _height: 500 } + options: { _panX: 0, _panY: 0, scale: 1 } // , _width: 500, _height: 500 } }], [DocumentType.KVP, { layout: { view: KeyValueBox, dataField: data }, @@ -221,17 +240,18 @@ export namespace Docs { layout: { view: PDFBox, dataField: data }, options: { curPage: 1 } }], - [DocumentType.ICON, { - layout: { view: IconBox, dataField: data }, - options: { _width: Number(MINIMIZED_ICON_SIZE), _height: Number(MINIMIZED_ICON_SIZE) }, - }], [DocumentType.IMPORT, { layout: { view: DirectoryImportBox, dataField: data }, options: { _height: 150 } }], - [DocumentType.LINKDOC, { + [DocumentType.LINK, { + layout: { view: LinkBox, dataField: data }, + options: { _height: 150 } + }], + [DocumentType.LINKDB, { data: new List<Doc>(), layout: { view: EmptyBox, dataField: data }, + options: { childDropAction: "alias", title: "LINK DB" } }], [DocumentType.YOUTUBE, { layout: { view: YoutubeBox, dataField: data } @@ -239,6 +259,9 @@ export namespace Docs { [DocumentType.BUTTON, { layout: { view: ButtonBox, dataField: data }, }], + [DocumentType.SLIDER, { + layout: { view: SliderBox, dataField: data }, + }], [DocumentType.PRES, { layout: { view: PresBox, dataField: data }, options: {} @@ -248,11 +271,11 @@ export namespace Docs { options: { _width: 40, _height: 40, borderRounding: "100%" }, }], [DocumentType.RECOMMENDATION, { - layout: { view: RecommendationsBox }, + layout: { view: RecommendationsBox, dataField: data }, options: { width: 200, height: 200 }, }], - [DocumentType.LINKFOLLOW, { - layout: { view: LinkFollowBox, dataField: data } + [DocumentType.WEBCAM, { + layout: { view: DashWebRTCVideo, dataField: data } }], [DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: data } @@ -261,14 +284,14 @@ export namespace Docs { layout: { view: InkingStroke, dataField: data }, options: { backgroundColor: "transparent" } }], - [DocumentType.SEARCHBOX, { - layout: { view: SearchBox, dataField:data}, - options: { width: 200, height: 200 }, + [DocumentType.SCREENSHOT, { + layout: { view: ScreenshotBox, dataField: data }, + options: {} }] ]); // All document prototypes are initialized with at least these values - const defaultOptions: DocumentOptions = { x: 0, y: 0, _width: 300 }; + const defaultOptions: DocumentOptions = { x: 0, y: 0, _width: 300 }; // bcz: do we really want to set anything here? could also try to set in render() methods for types that need a default const suffix = "Proto"; /** @@ -317,7 +340,7 @@ export namespace Docs { * A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here. */ export function MainLinkDocument() { - return Prototypes.get(DocumentType.LINKDOC); + return Prototypes.get(DocumentType.LINKDB); } /** @@ -347,6 +370,7 @@ export namespace Docs { const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; options.layout = layout.view.LayoutString(layout.dataField); const doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options }); + doc.layout_keyValue = KeyValueBox.LayoutString(""); return doc; } @@ -358,8 +382,62 @@ export namespace Docs { */ export namespace Create { - const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "_dropAction", "_annotationOn", - "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_hideSidebar"]; + export function Buxton() { + let responded = false; + const loading = new Doc; + loading.title = "Please wait for the import script..."; + const parent = TreeDocument([loading], { + title: "The Buxton Collection", + _width: 400, + _height: 400, + _LODdisable: true + }); + const parentProto = Doc.GetProto(parent); + const { _socket } = DocServer; + _socket.off(MessageStore.BuxtonDocumentResult.Message); + _socket.off(MessageStore.BuxtonImportComplete.Message); + Utils.AddServerHandler(_socket, MessageStore.BuxtonDocumentResult, ({ device, errors }) => { + if (!responded) { + responded = true; + parentProto.data = new List<Doc>(); + } + if (device) { + const { __images } = device; + delete device.__images; + const { ImageDocument, StackingDocument } = Docs.Create; + const constructed = __images.map(({ url, nativeWidth, nativeHeight }) => ({ url: Utils.prepend(url), nativeWidth, nativeHeight })); + const deviceImages = constructed.map(({ url, nativeWidth, nativeHeight }, i) => ImageDocument(url, { + title: `image${i}.${extname(url)}`, + _nativeWidth: nativeWidth, + _nativeHeight: nativeHeight + })); + const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true }); + const deviceProto = Doc.GetProto(doc); + deviceProto.hero = new ImageField(constructed[0].url); + Docs.Get.DocumentHierarchyFromJson(device, undefined, deviceProto); + Doc.AddDocToList(parentProto, "data", doc); + } else if (errors) { + console.log(errors); + } else { + alert("A Buxton document import was completely empty (??)"); + } + }); + Utils.AddServerHandler(_socket, MessageStore.BuxtonImportComplete, ({ deviceCount, errorCount }) => { + _socket.off(MessageStore.BuxtonDocumentResult.Message); + _socket.off(MessageStore.BuxtonImportComplete.Message); + alert(`Successfully imported ${deviceCount} device${deviceCount === 1 ? "" : "s"}, with ${errorCount} error${errorCount === 1 ? "" : "s"}, in ${(Date.now() - startTime) / 1000} seconds.`); + }); + const startTime = Date.now(); + Utils.Emit(_socket, MessageStore.BeginBuxtonImport, ""); + return parent; + } + + Scripting.addGlobal(Buxton); + + const delegateKeys = ["x", "y", "layoutKey", "_width", "_height", "_panX", "_panY", "_viewType", "_nativeWidth", "_nativeHeight", "dropAction", "childDropAction", "_annotationOn", + "_chromeStatus", "_forceActive", "_autoHeight", "_fitWidth", "_LODdisable", "_itemIndex", "_showSidebar", "_showTitle", "_showCaption", "_showTitleHover", "_backgroundColor", + "_xMargin", "_yMargin", "_xPadding", "_yPadding", "_singleLine", "_scrollTop", + "_color", "isButton", "isBackground", "removeDropProperties", "treeViewOpen"]; /** * This function receives the relevant document prototype and uses @@ -379,7 +457,7 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string) { + export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data") { const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys); if (!("author" in protoProps)) { @@ -392,10 +470,10 @@ export namespace Docs { protoProps.isPrototype = true; - const dataDoc = MakeDataDelegate(proto, protoProps, data); + const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey); const viewDoc = Doc.MakeDelegate(dataDoc, delegId); - AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "link to audio: " + d.title)); + viewDoc.type !== DocumentType.LINK && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: viewDoc }, { doc: d }, "audio link", "audio timeline")); return Doc.assign(viewDoc, delegateProps, true); } @@ -410,10 +488,10 @@ export namespace Docs { * @param options initial values to apply to this new delegate * @param value the data to store in this new delegate */ - function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D) { + function MakeDataDelegate<D extends Field>(proto: Doc, options: DocumentOptions, value?: D, fieldKey: string = "data") { const deleg = Doc.MakeDelegate(proto); if (value !== undefined) { - deleg.data = value; + deleg[fieldKey] = value; } return Doc.assign(deleg, options); } @@ -451,8 +529,18 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(new URL(url)), options); } + export function WebCamDocument(url: string, options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options); + } + + export function ScreenshotDocument(url: string, options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options); + } + export function AudioDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options); + const instance = InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options); + Doc.GetProto(instance).backgroundColor = ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'"); + return instance; } export function HistogramDocument(histoOp: HistogramOperation, options: DocumentOptions = {}) { @@ -469,7 +557,29 @@ export namespace Docs { } export function TextDocument(text: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options); + return InstanceFromProto(Prototypes.get(DocumentType.TEXT), text, options, undefined, "text"); + } + + export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) { + const doc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { isButton: true, treeViewHideTitle: true, treeViewOpen: false, removeDropProperties: new List(["isBackground", "isButton"]), ...options }); + const linkDocProto = Doc.GetProto(doc); + linkDocProto.anchor1 = source.doc; + linkDocProto.anchor2 = target.doc; + linkDocProto.anchor1_timecode = source.doc.currentTimecode || source.doc.displayTimecode; + linkDocProto.anchor2_timecode = target.doc.currentTimecode || source.doc.displayTimecode; + + if (linkDocProto.layout_key1 === undefined) { + Cast(linkDocProto.proto, Doc, null).layout_key1 = DocuLinkBox.LayoutString("anchor1"); + Cast(linkDocProto.proto, Doc, null).layout_key2 = DocuLinkBox.LayoutString("anchor2"); + Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "lastOpened", "creationDate", "author"]); + Cast(linkDocProto.proto, Doc, null).layoutKey = undefined; + } + + LinkManager.Instance.addLink(doc); + + Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)"); + Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)"); + return doc; } export function InkDocument(color: string, tool: number, strokeWidth: number, points: { X: number, Y: number }[], options: DocumentOptions = {}) { @@ -480,10 +590,6 @@ export namespace Docs { return doc; } - export function IconDocument(icon: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.ICON), new IconField(icon), options); - } - export function PdfDocument(url: string, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), options); } @@ -551,17 +657,21 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, _viewType: CollectionViewType.Schema }); } - export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Tree }); + export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Tree }, id); } - export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Stacking }); + export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Stacking }, id); } export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multicolumn }); } + export function MultirowDocument(documents: Array<Doc>, options: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Multirow }); + } + export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, _viewType: CollectionViewType.Masonry }); @@ -571,15 +681,15 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}) }); } + export function SliderDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.SLIDER), undefined, { ...(options || {}) }); + } + export function FontIconDocument(options?: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) }); } - export function LinkFollowBoxDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) }); - } - export function PresElementBoxDocument(options?: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } @@ -614,7 +724,7 @@ export namespace Docs { { type: type, content: [ - ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth, config.path)) + ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, config.initialWidth, config.path)) ] } ] @@ -648,19 +758,16 @@ export namespace Docs { * or the result of any JSON.parse() call. * @param title an optional title to give to the highest parent document in the hierarchy */ - export function DocumentHierarchyFromJson(input: any, title?: string): Opt<Doc> { + export function DocumentHierarchyFromJson(input: any, title?: string, appendToTarget?: Doc): Opt<Doc> { if (input === undefined || input === null || ![...primitives, "object"].includes(typeof input)) { return undefined; } - let parsed = input; - if (typeof input === "string") { - parsed = JSONUtils.tryParse(input); - } + input = JSON.parse(typeof input === "string" ? input : JSON.stringify(input)); let converted: Doc; - if (typeof parsed === "object" && !(parsed instanceof Array)) { - converted = convertObject(parsed, title); + if (typeof input === "object" && !(input instanceof Array)) { + converted = convertObject(input, title, appendToTarget); } else { - (converted = new Doc).json = toField(parsed); + (converted = new Doc).json = toField(input); } title && (converted.title = title); return converted; @@ -673,12 +780,12 @@ export namespace Docs { * @returns the object mapped from JSON to field values, where each mapping * might involve arbitrary recursion (since toField might itself call convertObject) */ - const convertObject = (object: any, title?: string): Doc => { - const target = new Doc(); + const convertObject = (object: any, title?: string, target?: Doc): Doc => { + const resolved = target ?? new Doc; let result: Opt<Field>; - Object.keys(object).map(key => (result = toField(object[key], key)) && (target[key] = result)); - title && !target.title && (target.title = title); - return target; + Object.keys(object).map(key => (result = toField(object[key], key)) && (resolved[key] = result)); + title && !resolved.title && (resolved.title = title); + return resolved; }; /** @@ -725,9 +832,6 @@ export namespace Docs { } else if (field instanceof PdfField) { created = Docs.Create.PdfDocument((field).url.href, resolved); layout = PDFBox.LayoutString; - } else if (field instanceof IconField) { - created = Docs.Create.IconDocument((field).icon, resolved); - layout = IconBox.LayoutString; } else if (field instanceof AudioField) { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; @@ -772,7 +876,7 @@ export namespace Docs { } if (type.indexOf("excel") !== -1) { ctor = Docs.Create.DBDocument; - options._dropAction = "copy"; + options.dropAction = "copy"; } if (type.indexOf("html") !== -1) { if (path.includes(window.location.hostname)) { @@ -831,41 +935,54 @@ export namespace DocUtils { }); } - export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) { + export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", id?: string) { const sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; if (target.doc === CurrentUserUtils.UserDocument) return undefined; - const linkDocProto = new Doc(id, true); - UndoManager.RunInBatch(() => { - linkDocProto.type = DocumentType.LINK; + const linkDoc = Docs.Create.LinkDocument(source, target, { linkRelationship }, id); + Doc.GetProto(linkDoc).title = ComputedField.MakeFunction('this.anchor1.title +" (" + (this.linkRelationship||"to") +") " + this.anchor2.title'); - linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title; - linkDocProto.linkDescription = description; - linkDocProto.isPrototype = true; - - linkDocProto.anchor1 = source.doc; - linkDocProto.anchor2 = target.doc; - linkDocProto.anchor1Context = source.ctx; - linkDocProto.anchor2Context = target.ctx; - linkDocProto.anchor1Groups = new List<Doc>([]); - linkDocProto.anchor2Groups = new List<Doc>([]); - linkDocProto.anchor1Timecode = source.doc.currentTimecode; - linkDocProto.anchor2Timecode = target.doc.currentTimecode; - linkDocProto.layout_key1 = DocuLinkBox.LayoutString("anchor1"); - linkDocProto.layout_key2 = DocuLinkBox.LayoutString("anchor2"); - linkDocProto.width = linkDocProto.height = 0; - linkDocProto.isBackground = true; - linkDocProto.isButton = true; - - LinkManager.Instance.addLink(linkDocProto); - - Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)"); - Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)"); - }, "make link"); - return linkDocProto; + Doc.GetProto(source.doc).links = ComputedField.MakeFunction("links(this)"); + Doc.GetProto(target.doc).links = ComputedField.MakeFunction("links(this)"); + return linkDoc; } + export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number): void { + ContextMenu.Instance.addItem({ + description: "Add Note ...", + subitems: DocListCast((Doc.UserDoc().noteTypes as Doc).data).map((note, i) => ({ + description: ":" + StrCast(note.title), + event: (args: { x: number, y: number }) => { + const textDoc = Docs.Create.TextDocument("", { + _width: 200, x, y, _autoHeight: note._autoHeight !== false, + title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) + }); + textDoc.layoutKey = "layout_" + note.title; + textDoc[textDoc.layoutKey] = note; + docTextAdder(textDoc); + }, + icon: "eye" + })) as ContextMenuProps[], + icon: "eye" + }); + ContextMenu.Instance.addItem({ + description: "Add Template Doc ...", + subitems: DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc).map((dragDoc, i) => ({ + description: ":" + StrCast(dragDoc.title), + event: (args: { x: number, y: number }) => { + const newDoc = Doc.ApplyTemplate(dragDoc); + if (newDoc) { + newDoc.x = x; + newDoc.y = y; + docAdder(newDoc); + } + }, + icon: "eye" + })) as ContextMenuProps[], + icon: "eye" + }); + } } Scripting.addGlobal("Docs", Docs); |