diff options
Diffstat (limited to 'src/client/documents/Documents.ts')
-rw-r--r-- | src/client/documents/Documents.ts | 1266 |
1 files changed, 77 insertions, 1189 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b160379df..1c18c0d84 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,68 +1,26 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { action, reaction, runInAction } from 'mobx'; +/* eslint-disable prefer-destructuring */ +/* eslint-disable default-param-last */ +/* eslint-disable no-use-before-define */ +import { reaction } from 'mobx'; import { basename } from 'path'; -import { OmitKeys, Utils } from '../../Utils'; +import { ClientUtils, OmitKeys } from '../../ClientUtils'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc'; -import { DocData, Initializing } from '../../fields/DocSymbols'; -import { Id } from '../../fields/FieldSymbols'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, CreateLinkToActiveAudio, Doc, FieldType, Opt, updateCachedAcls } from '../../fields/Doc'; +import { Initializing } from '../../fields/DocSymbols'; import { HtmlField } from '../../fields/HtmlField'; -import { InkField, PointData } from '../../fields/InkField'; +import { InkField } from '../../fields/InkField'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; -import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; -import { SharingPermissions, inheritParentAcls } from '../../fields/util'; -import { Upload } from '../../server/SharedMediaTypes'; +import { ScriptCast, StrCast } from '../../fields/Types'; +import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField'; +import { SharingPermissions } from '../../fields/util'; +import { PointData } from '../../pen-gestures/GestureTypes'; import { DocServer } from '../DocServer'; -import { Networking } from '../Network'; -import { YoutubeBox } from '../apis/youtube/YoutubeBox'; -import { DragManager, dropActionType } from '../util/DragManager'; -import { FollowLinkScript } from '../util/LinkFollower'; -import { LinkManager } from '../util/LinkManager'; -import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { UndoManager, undoable } from '../util/UndoManager'; -import { ContextMenu } from '../views/ContextMenu'; -import { ContextMenuProps } from '../views/ContextMenuItem'; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; -import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { CollectionView } from '../views/collections/CollectionView'; -import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; -import { AudioBox, media_state } from '../views/nodes/AudioBox'; -import { ComparisonBox } from '../views/nodes/ComparisonBox'; -import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; -import { OpenWhere } from '../views/nodes/DocumentView'; -import { EquationBox } from '../views/nodes/EquationBox'; -import { FieldViewProps } from '../views/nodes/FieldView'; -import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; -import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox'; -import { ImageBox } from '../views/nodes/ImageBox'; -import { KeyValueBox } from '../views/nodes/KeyValueBox'; -import { LabelBox } from '../views/nodes/LabelBox'; -import { LinkBox } from '../views/nodes/LinkBox'; -import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup'; -import { LoadingBox } from '../views/nodes/LoadingBox'; -import { MapBox } from '../views/nodes/MapBox/MapBox'; -import { MapPushpinBox } from '../views/nodes/MapBox/MapPushpinBox'; -import { PDFBox } from '../views/nodes/PDFBox'; -import { PhysicsSimulationBox } from '../views/nodes/PhysicsBox/PhysicsSimulationBox'; -import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox'; -import { ScreenshotBox } from '../views/nodes/ScreenshotBox'; -import { ScriptingBox } from '../views/nodes/ScriptingBox'; -import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; -import { VideoBox } from '../views/nodes/VideoBox'; -import { WebBox } from '../views/nodes/WebBox'; -import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox'; -import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; -import { PresBox } from '../views/nodes/trails/PresBox'; -import { PresElementBox } from '../views/nodes/trails/PresElementBox'; -import { SearchBox } from '../views/search/SearchBox'; +import { dropActionType } from '../util/DropActionTypes'; import { CollectionViewType, DocumentType } from './DocumentTypes'; -const { DFLT_IMAGE_NATIVE_DIM } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore -const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); class EmptyBox { public static LayoutString() { return ''; @@ -73,6 +31,7 @@ export enum FInfoFieldType { string = 'string', boolean = 'boolean', number = 'number', + // eslint-disable-next-line @typescript-eslint/no-shadow Doc = 'Doc', enumeration = 'enum', date = 'date', @@ -83,7 +42,7 @@ export class FInfo { description: string = ''; readOnly: boolean = false; fieldType?: FInfoFieldType; - values?: Field[]; + values?: FieldType[]; filterable?: boolean = true; // can be used as a Filter in FilterPanel // format?: string; // format to display values (e.g, decimal places, $, etc) @@ -134,7 +93,7 @@ class DocInfo extends FInfo { } class DimInfo extends FInfo { fieldType? = FInfoFieldType.enumeration; - values? = [DimUnit.Pixel, DimUnit.Ratio]; + values? = []; // DimUnit.Pixel, DimUnit.Ratio]; readOnly = false; filterable = false; override searchable = () => false; @@ -190,7 +149,7 @@ type STRt = StrInfo | string; type LISTt = ListInfo | List<any>; type DOCt = DocInfo | Doc; type RTFt = RtfInfo | RichTextField; -type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; +type DIMt = DimInfo; // | typeof DimUnit.Pixel | typeof DimUnit.Ratio; type PEVt = PEInfo | 'none' | 'all'; type COLLt = CTypeInfo | CollectionViewType; type DROPt = DAInfo | dropActionType; @@ -233,14 +192,16 @@ export class DocumentOptions { _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)', false); _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)', false); - 'acl-Guest'?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions - '_acl-Guest'?: string; // public permissions + acl?: STRt = new StrInfo('unused except as a display category in KeyValueBox'); + acl_Guest?: STRt = new StrInfo("permissions granted to users logged in as 'guest' (either view, or private)"); // public permissions + _acl_Guest?: string; // public permissions type?: DTYPEt = new DTypeInfo('type of document', true); type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection _type_collection?: COLLt = new CTypeInfo('how collection is rendered'); // sub type of a collection title?: STRt = new StrInfo('title of document', true); title_custom?: BOOLt = new BoolInfo('whether title is a default or has been intentionally set'); caption?: RichTextField; + systemIcon?: STRt = new StrInfo("name of icon to use to represent document's type"); author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); annotationOn?: DOCt = new DocInfo('document annotated by this document', false); @@ -252,14 +213,20 @@ export class DocumentOptions { opacity?: NUMt = new NumInfo('document opacity', false); viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters', false); dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false); - _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' - undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' + _undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' + undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' _header_height?: NUMt = new NumInfo('height of document header used for displaying title', false); _header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false); _header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume'); _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged"); _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed'); + dataViz_title?: string; + dataViz_line?: string; + dataViz_pie?: string; + dataViz_histogram?: string; + dataViz?: string; + layout?: string | Doc; // default layout string or template document layout_isSvg?: BOOLt = new BoolInfo('whether document decorations and other selections should handle pointerEvents for svg content or use doc bounding box'); layout_keyValue?: STRt = new StrInfo('layout definition for showing keyValue view of document', false); @@ -313,6 +280,7 @@ export class DocumentOptions { _text_fontSize?: string; _text_fontFamily?: string; _text_fontWeight?: string; + fontSize?: string; _pivotField?: string; // field key used to determine headings for sections in stacking, masonry, pivot views infoWindowOpen?: BOOLt = new BoolInfo('whether info window corresponding to pin is open (on MapDocuments)'); @@ -321,7 +289,7 @@ export class DocumentOptions { _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false); stroke_width?: NUMt = new NumInfo('width of an ink stroke', false); stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke'); - mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false); + mediaState?: STRt = new StrInfo(`status of audio/video media document:`); // ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false); recording?: BOOLt = new BoolInfo('whether WebCam is recording or not'); slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)'); autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.'); @@ -391,6 +359,7 @@ export class DocumentOptions { // STOPPING HERE // freeform properties + freeform?: STRt = new StrInfo(''); _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections'); _freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped'); _freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views'); @@ -401,7 +370,7 @@ export class DocumentOptions { _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)'); _freeform_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content'); - //BUTTONS + // BUTTONS buttonText?: string; btnType?: string; btnList?: List<string>; @@ -413,7 +382,7 @@ export class DocumentOptions { switchToggle?: boolean; badgeValue?: ScriptField; - //LINEAR VIEW + // LINEAR VIEW linearView_IsOpen?: BOOLt = new BoolInfo('is linear view open'); linearView_Expandable?: BOOLt = new BoolInfo('can linear view be expanded'); linearView_Dropdown?: BOOLt = new BoolInfo('can linear view be opened as a dropdown'); @@ -421,9 +390,9 @@ export class DocumentOptions { flexGap?: NUMt = new NumInfo('Linear view flex gap'); flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse'; + link?: string; link_description?: string; // added for links link_relationship?: string; // type of relatinoship a link represents - link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors'); link_displayArrow?: BOOLt = new BoolInfo("whether to display link's directional arrowhead"); link_anchor_1?: DOCt = new DocInfo('start anchor of a link'); link_anchor_2?: DOCt = new DocInfo('end anchor of a link'); @@ -431,7 +400,7 @@ export class DocumentOptions { link_anchor_1_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)'); link_anchor_2_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)'); link_relationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor) - link_relationshipSizes?: List<number>; //stores number of links contained in each relationship + link_relationshipSizes?: List<number>; // stores number of links contained in each relationship link_colorList?: List<string>; // colors of links corresponding to specific link relationships followLinkZoom?: BOOLt = new BoolInfo('whether to zoom to the target of a link'); followLinkToggle?: BOOLt = new BoolInfo('whether target of link should be toggled on and off when following a link to it'); @@ -463,7 +432,7 @@ export class DocumentOptions { dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', false, true); dragFactory?: DOCt = new DocInfo('document to create when dragging with a suitable onDragStart script', false); clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false); - 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 + 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 target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script) tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true); treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view'); @@ -502,8 +471,6 @@ export class DocumentOptions { export const DocOptions = new DocumentOptions(); export namespace Docs { - export let newAccount: boolean = false; - export namespace Prototypes { type LayoutSource = { LayoutString: (key: string) => string }; type PrototypeTemplate = { @@ -518,117 +485,12 @@ export namespace Docs { type PrototypeMap = Map<DocumentType, Doc>; const defaultDataKey = 'data'; - const TemplateMap: TemplateMap = new Map([ - [ - DocumentType.RTF, - { - layout: { view: FormattedTextBox, dataField: 'text' }, - options: { - _height: 35, - _xMargin: 10, - _yMargin: 10, - layout_nativeDimEditable: true, - layout_reflowVertical: true, - layout_reflowHorizontal: true, - defaultDoubleClick: 'ignore', - systemIcon: 'BsFileEarmarkTextFill', - }, - }, - ], - [ - DocumentType.SEARCH, - { - layout: { view: SearchBox, dataField: defaultDataKey }, - options: { _width: 400 }, - }, - ], - [ - DocumentType.IMG, - { - layout: { view: ImageBox, dataField: defaultDataKey }, - options: { freeform: '', systemIcon: 'BsFileEarmarkImageFill' }, - }, - ], - [ - DocumentType.WEB, - { - layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, waitForDoubleClickToClick: 'always', systemIcon: 'BsGlobe' }, - }, - ], + export const TemplateMap: TemplateMap = new Map([ [ - DocumentType.COL, - { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { - _layout_fitWidth: true, - freeform: '', - _freeform_panX: 0, - _freeform_panY: 0, - _freeform_scale: 1, - layout_nativeDimEditable: true, - layout_reflowHorizontal: true, - layout_reflowVertical: true, - systemIcon: 'BsFillCollectionFill', - }, - }, - ], - [ - DocumentType.KVP, - { - layout: { view: KeyValueBox, dataField: defaultDataKey }, - options: { _layout_fitWidth: true, _height: 150 }, - }, - ], - [ - DocumentType.VID, - { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' }, - }, - ], - [ - DocumentType.AUDIO, - { - layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' }, - }, - ], - [ - DocumentType.REC, - { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' }, - }, - ], - [ - DocumentType.PDF, - { - layout: { view: PDFBox, dataField: defaultDataKey }, - options: { _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' }, - }, - ], - [ - DocumentType.MAP, - { - layout: { view: MapBox, dataField: defaultDataKey }, - options: { map: '', _height: 600, _width: 800, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' }, - }, - ], - [ - DocumentType.LINK, + DocumentType.GROUPDB, { - layout: { view: LinkBox, dataField: 'link' }, - options: { - childDontRegisterViews: true, - layout_hideLinkAnchors: true, - _height: 1, - _width: 1, - link: '', - link_description: '', - color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area - _dropPropertiesToRemove: new List(['onClick']), - }, + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { acl: '', title: 'Global Group Database' }, }, ], [ @@ -636,195 +498,22 @@ export namespace Docs { { data: new List<Doc>(), layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { title: 'Global Script Database' }, - }, - ], - [ - DocumentType.SCRIPTING, - { - layout: { view: ScriptingBox, dataField: defaultDataKey }, - options: { systemIcon: 'BsFileEarmarkCodeFill' }, - }, - ], - [ - DocumentType.YOUTUBE, - { - layout: { view: YoutubeBox, dataField: defaultDataKey }, - }, - ], - [ - DocumentType.LABEL, - { - layout: { view: LabelBox, dataField: 'title' }, - options: { _singleLine: true, layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true }, - }, - ], - [ - DocumentType.EQUATION, - { - layout: { view: EquationBox, dataField: 'text' }, - options: { - fontSize: '14px', - layout_reflowHorizontal: true, - layout_reflowVertical: true, - layout_nativeDimEditable: true, - layout_hideDecorationTitle: true, - systemIcon: 'BsCalculatorFill', - }, ///systemIcon: 'BsSuperscript' + BsSubscript - }, - ], - [ - DocumentType.FUNCPLOT, - { - layout: { view: FunctionPlotBox, dataField: defaultDataKey }, - options: { - layout_reflowHorizontal: true, - layout_reflowVertical: true, - layout_nativeDimEditable: true, - }, - }, - ], - [ - DocumentType.BUTTON, - { - layout: { view: LabelBox, dataField: 'title' }, - options: { layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true }, - }, - ], - [ - DocumentType.PRES, - { - layout: { view: PresBox, dataField: defaultDataKey }, - options: { defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true }, - }, - ], - [ - DocumentType.FONTICON, - { - layout: { view: FontIconBox, dataField: 'icon' }, - options: { defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 }, - }, - ], - [ - DocumentType.WEBCAM, - { - layout: { view: RecordingBox, dataField: defaultDataKey }, - options: { systemIcon: 'BsFillCameraVideoFill' }, - }, - ], - [ - DocumentType.PRESELEMENT, - { - layout: { view: PresElementBox, dataField: defaultDataKey }, - options: { title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' }, + options: { acl: '', title: 'Global Script Database' }, }, ], + [ DocumentType.CONFIG, { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { config: '', layout_hideLinkButton: true, layout_unrendered: true }, - }, - ], - [ - DocumentType.INK, - { - // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method - layout: { view: InkingStroke, dataField: 'stroke' }, - options: { - systemIcon: 'BsFillPencilFill', // - layout_nativeDimEditable: true, - layout_reflowVertical: true, - layout_reflowHorizontal: true, - layout_hideDecorationTitle: true, // don't show title when selected - fitWidth: false, - layout_isSvg: true, - }, - }, - ], - [ - DocumentType.SCREENSHOT, - { - layout: { view: ScreenshotBox, dataField: defaultDataKey }, - options: { layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' }, - }, - ], - [ - DocumentType.COMPARISON, - { - data: '', - layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { backgroundColor: 'gray', dropAction: dropActionType.move, waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' }, - }, - ], - [ - DocumentType.GROUPDB, - { layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { title: 'Global Group Database' }, - }, - ], - [ - DocumentType.GROUP, - { - layout: { view: EmptyBox, dataField: defaultDataKey }, - options: {}, - }, - ], - [ - DocumentType.DATAVIZ, - { - layout: { view: DataVizBox, dataField: defaultDataKey }, - options: { dataViz_title: '', dataViz_line: '', dataViz_pie: '', dataViz_histogram: '', dataViz: 'table', _layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true }, - }, - ], - [ - DocumentType.LOADING, - { - layout: { view: LoadingBox, dataField: '' }, - options: { _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true }, - }, - ], - [ - DocumentType.SIMULATION, - { - data: '', - layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 }, - options: { - _height: 100, - mass1: '', - mass2: '', - layout_nativeDimEditable: true, - position: '', - acceleration: '', - pendulum: '', - spring: '', - wedge: '', - simulation: '', - review: '', - systemIcon: 'BsShareFill', - }, - }, - ], - [ - DocumentType.PUSHPIN, - { - layout: { view: MapPushpinBox, dataField: defaultDataKey }, - options: {}, + options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true }, }, ], [ DocumentType.MAPROUTE, { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: {}, - }, - ], - [ - DocumentType.CALENDAR, - { - layout: { view: CalendarBox, dataField: defaultDataKey }, - options: {}, + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { acl: '' }, }, ], ]); @@ -850,7 +539,7 @@ export namespace Docs { // fetch the actual prototype documents from the server const actualProtos = await DocServer.GetRefFields(prototypeIds); // update this object to include any default values: DocumentOptions for all prototypes - prototypeIds.map(id => { + prototypeIds.forEach(id => { const existing = actualProtos[id] as Doc; const type = id.replace(suffix, '') as DocumentType; // get or create prototype of the specified type... @@ -908,7 +597,7 @@ export namespace Docs { if (!template) { return undefined; } - const layout = template.layout; + const { layout } = template; // create title const upper = suffix.toUpperCase(); const title = prototypeId.toUpperCase().replace(upper, `_${upper}`); @@ -921,14 +610,14 @@ export namespace Docs { type, isBaseProto: true, _width: 300, - 'acl-Guest': SharingPermissions.View, + acl_Guest: SharingPermissions.View, ...(template.options || {}), layout: layout.view?.LayoutString(layout.dataField), data: template.data, }; Object.entries(options) .filter(pair => typeof pair[1] === 'string' && pair[1].startsWith('@')) - .map(pair => { + .forEach(pair => { if (!existing || ScriptCast(existing[pair[0]])?.script.originalScript !== pair[1].substring(1)) { (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); } @@ -960,15 +649,17 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc, noView?: boolean) { + // eslint-disable-next-line default-param-last + function InstanceFromProto(proto: Doc, data: FieldType | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDocIn?: Doc, noView?: boolean) { + const placeholderDoc = placeholderDocIn; const viewKeys = ['x', 'y', 'isSystem']; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_'); - // dataProps['acl-Override'] = SharingPermissions.Unset; - dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); + // dataProps.acl_Override = SharingPermissions.Unset; + dataProps.acl_Guest = options.acl_Guest ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); dataProps.isSystem = viewProps.isSystem; dataProps.isDataDoc = true; - dataProps.author = Doc.CurrentUserEmail; + dataProps.author = ClientUtils.CurrentUserEmail(); dataProps.author_date = new DateField(); if (fieldKey) { dataProps[`${fieldKey}_modificationDate`] = new DateField(); @@ -988,8 +679,8 @@ export namespace Docs { } if (!noView) { - const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail }; - viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); + const viewFirstProps: { [id: string]: any } = { author: ClientUtils.CurrentUserEmail() }; + viewFirstProps.acl_Guest = options._acl_Guest ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); let viewDoc: Doc; // determines whether viewDoc should be created using placeholder Doc or default if (placeholderDoc) { @@ -997,14 +688,16 @@ export namespace Docs { placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined; viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); Array.from(Object.keys(placeholderDoc)) - .filter(key => key.startsWith('acl')) - .forEach(key => (dataDoc[key] = viewDoc[key] = placeholderDoc[key])); + .filter(key => key.startsWith('acl_')) + .forEach(key => { + dataDoc[key] = viewDoc[key] = placeholderDoc[key]; + }); } else { viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); } Doc.assign(viewDoc, viewProps, true, true); if (![DocumentType.LINK, DocumentType.CONFIG, DocumentType.LABEL].includes(viewDoc.type as any)) { - DocUtils.MakeLinkToActiveAudio(() => viewDoc); + CreateLinkToActiveAudio(() => viewDoc); } updateCachedAcls(dataDoc); updateCachedAcls(viewDoc); @@ -1017,6 +710,7 @@ export namespace Docs { return dataDoc; } + // eslint-disable-next-line default-param-last export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) { const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined; return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc); @@ -1035,18 +729,16 @@ export namespace Docs { * @param fieldKey the field that the compiled script is written into. * @returns the Scripting Doc */ + // eslint-disable-next-line default-param-last export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? `<ScriptingBox {...props} fieldKey={'${fieldKey}'}/>` /* ScriptingBox.LayoutString(fieldKey) */ : undefined }); } + // eslint-disable-next-line default-param-last export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc); } - export function YoutubeDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { - return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options, undefined, undefined, undefined, overwriteDoc); - } - export function WebCamDocument(url: string, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), '', options); } @@ -1059,6 +751,7 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options); } + // eslint-disable-next-line default-param-last export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc); } @@ -1072,7 +765,7 @@ export namespace Docs { } export function LoadingDocument(file: File | string, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, ''); + return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file === 'string' ? file : file.name, ...options }, undefined, ''); } export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { @@ -1101,6 +794,7 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } + // eslint-disable-next-line default-param-last export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) { const linkDoc = InstanceFromProto( Prototypes.get(DocumentType.LINK), @@ -1114,7 +808,7 @@ export namespace Docs { 'link' ); - LinkManager.Instance.addLink(linkDoc); + Doc.AddLink(linkDoc); return linkDoc; } @@ -1124,7 +818,7 @@ export namespace Docs { options: DocumentOptions = {}, strokeWidth = ActiveInkWidth(), color = ActiveInkColor(), - stroke_bezier = ActiveInkBezierApprox(), + strokeBezier = ActiveInkBezierApprox(), fillColor = ActiveFillColor(), arrowStart = ActiveArrowStart(), arrowEnd = ActiveArrowEnd(), @@ -1138,7 +832,7 @@ export namespace Docs { I.fillColor = fillColor; I.stroke = new InkField(points); I.stroke_width = strokeWidth; - I.stroke_bezier = stroke_bezier; + I.stroke_bezier = strokeBezier; I.stroke_startMarker = arrowStart; I.stroke_endMarker = arrowEnd; I.stroke_dash = dash; @@ -1147,13 +841,14 @@ export namespace Docs { I.rotation = 0; I.defaultDoubleClick = 'ignore'; I.author_date = new DateField(); - I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; - //I['acl-Override'] = SharingPermissions.Unset; + I.acl_Guest = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; + // I.acl_Override = SharingPermissions.Unset; I[Initializing] = false; return ink; } + // eslint-disable-next-line default-param-last export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { const width = options._width || undefined; const height = options._height || undefined; @@ -1169,7 +864,7 @@ export namespace Docs { const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'https://www.wikipedia.org/'), options); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url || 'https://www.wikipedia.org/'), options); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { @@ -1306,34 +1001,6 @@ export namespace Docs { return ret; } - export type DocConfig = { - doc: Doc; - initialWidth?: number; - path?: Doc[]; - }; - - export function StandardCollectionDockingDocument(configs: Array<DocConfig>, options: DocumentOptions, id?: string, type: string = 'row') { - const layoutConfig = { - content: [ - { - type: type, - content: [...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))], - }, - ], - }; - const doc = DockDocument( - configs.map(c => c.doc), - JSON.stringify(layoutConfig), - Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options }, - id - ); - configs.map(c => { - Doc.SetContainer(c.doc, doc); - inheritParentAcls(doc, c.doc, false); - }); - return doc; - } - export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) { return InstanceFromProto(proto, undefined, options); } @@ -1343,782 +1010,3 @@ export namespace Docs { } } } - -export namespace DocUtils { - function matchFieldValue(doc: Doc, key: string, value: any): boolean { - const hasFunctionFilter = Utils.HasFunctionFilter(value); - if (hasFunctionFilter) { - return hasFunctionFilter(StrCast(doc[key])); - } - if (key === LinkedTo) { - // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) - const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); - const matchLink = (value: string, anchor: Doc) => { - const linkedToExp = (value ?? '').split('='); - if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value; - return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; - }; - // prettier-ignore - return (value === Doc.FilterNone && !allLinks.length) || - (value === Doc.FilterAny && !!allLinks.length) || - (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || - matchLink(value,DocCast(link.link_anchor_2)) )); - } - if (typeof value === 'string') { - value = value.replace(`,${Utils.noRecursionHack}`, ''); - } - const fieldVal = doc[key]; - // prettier-ignore - if ((value === Doc.FilterAny && fieldVal !== undefined) || - (value === Doc.FilterNone && fieldVal === undefined)) { - return true; - } - const vals = StrListCast(fieldVal); // list typing is very imperfect. casting to a string list doesn't mean that the entries will actually be strings - if (vals.length) { - return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring - } - return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring - } - /** - * @param docs - * @param childFilters - * @param childFiltersByRanges - * @param parentCollection - * Given a list of docs and childFilters, @returns the list of Docs that match those filters - */ - export function FilterDocs(childDocs: Doc[], childFilters: string[], childFiltersByRanges: string[], parentCollection?: Doc) { - if (!childFilters?.length && !childFiltersByRanges?.length) { - return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one - } - - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields - childFilters.forEach(filter => { - const fields = filter.split(Doc.FilterSep); - const key = fields[0]; - const value = fields[1]; - const modifiers = fields[2]; - if (!filterFacets[key]) { - filterFacets[key] = {}; - } - filterFacets[key][value] = modifiers; - }); - - const filteredDocs = childFilters.length - ? childDocs.filter(d => { - if (d.z) return true; - // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter - if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { - return false; - } - for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragDocsFilter.split(Doc.FilterSep)[0])) { - const facet = filterFacets[facetKey]; - - // facets that match some value in the field of the document (e.g. some text field) - const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match'); - - // facets that have a check next to them - const checks = Object.keys(facet).filter(value => facet[value] === 'check'); - - // metadata facets that exist - const exists = Object.keys(facet).filter(value => facet[value] === 'exists'); - - // facets that unset metadata (a hack for making cookies work) - const unsets = Object.keys(facet).filter(value => facet[value] === 'unset'); - - // facets that specify that a field must not match a specific value - const xs = Object.keys(facet).filter(value => facet[value] === 'x'); - - if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; - const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value)); - const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value)); - const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length)); - const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined); - const satisfiesMatchFacets = !matches.length - ? true - : matches.some(value => { - if (facetKey.startsWith('*')) { - // fields starting with a '*' are used to match families of related fields. ie, *modificationDate will match text_modificationDate, data_modificationDate, etc - const allKeys = Array.from(Object.keys(d)); - allKeys.push(...Object.keys(Doc.GetProto(d))); - const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); - return keys.some(key => Field.toString(d[key] as Field).includes(value)); - } - return Field.toString(d[facetKey] as Field).includes(value); - }); - // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria - if (parentCollection?.childFilters_boolean === 'OR') { - if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; - } - // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria - else { - if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; - } - } - return parentCollection?.childFilters_boolean === 'OR' ? false : true; - }) - : childDocs; - const rangeFilteredDocs = filteredDocs.filter(d => { - for (let i = 0; i < childFiltersByRanges.length; i += 3) { - const key = childFiltersByRanges[i]; - const min = Number(childFiltersByRanges[i + 1]); - const max = Number(childFiltersByRanges[i + 2]); - const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null); - if (val === undefined) { - //console.log("Should 'undefined' pass range filter or not?") - } else if (val < min || val > max) return false; - } - return true; - }); - return rangeFilteredDocs; - } - - export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; - - export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { - broadcastEvent && runInAction(() => (Doc.RecordingEvent = Doc.RecordingEvent + 1)); - return DocUtils.ActiveRecordings.map(audio => { - const sourceDoc = getSourceDoc(); - return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' }); - }); - } - - export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) { - if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; - if (target.doc === Doc.UserDoc()) return undefined; - - const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { - if (showPopup) { - LinkManager.Instance.currentLink = linkDoc; - - TaskCompletionBox.textDisplayed = 'Link Created'; - TaskCompletionBox.popupX = showPopup[0]; - TaskCompletionBox.popupY = showPopup[1] - 33; - TaskCompletionBox.taskCompleted = true; - - LinkDescriptionPopup.Instance.popupX = showPopup[0]; - LinkDescriptionPopup.Instance.popupY = showPopup[1]; - LinkDescriptionPopup.Instance.display = true; - - const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { - LinkDescriptionPopup.Instance.popupX -= 190; - TaskCompletionBox.popupX -= 40; - } - if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { - LinkDescriptionPopup.Instance.popupY -= 40; - TaskCompletionBox.popupY -= 40; - } - - setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), - 2500 - ); - } - return linkDoc; - }); - - const a = source.layout_unrendered ? 'link_anchor_1?.annotationOn' : 'link_anchor_1'; - const b = target.layout_unrendered ? 'link_anchor_2?.annotationOn' : 'link_anchor_2'; - - return makeLink( - Docs.Create.LinkDocument( - source, - target, - { - 'acl-Guest': SharingPermissions.Augment, - '_acl-Guest': SharingPermissions.Augment, - title: ComputedField.MakeFunction('generateLinkTitle(this)') as any, - link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined, - link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, - link_displayLine: linkSettings.link_displayLine, - link_relationship: linkSettings.link_relationship, - link_description: linkSettings.link_description, - x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any, - y: ComputedField.MakeFunction(`((this.${a}?.y||0)+(this.${b}?.y||0))/2`) as any, - link_autoMoveAnchors: true, - _lockedPosition: true, - _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box) - _layout_showTitle: '', - // _layout_showCaption: 'link_description', - // _layout_showTitle: 'link_relationship', - }, - id - ), - showPopup - ); - } - - export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) { - scripts && - Object.keys(scripts).map(key => { - const script = scripts[key]; - if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { - (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, { - self: Doc.name, - this: Doc.name, - dragData: DragManager.DocumentDragData.name, - value: 'any', - _readOnly_: 'boolean', - scriptContext: 'any', - documentView: Doc.name, - heading: Doc.name, - checked: 'boolean', - containingTreeView: Doc.name, - altKey: 'boolean', - ctrlKey: 'boolean', - shiftKey: 'boolean', - }); - } - }); - funcs && - Object.keys(funcs) - .filter(key => !key.endsWith('-setter')) - .map(key => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) { - const setFunc = Cast(funcs[key + '-setter'], 'string', null); - (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = funcs[key] ? ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }, setFunc) : undefined; - } - }); - return doc; - } - export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) { - if (doc) { - const compareValues = (val1: any, val2: any) => { - if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) { - return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v)); - } - return val1 === val2; - }; - Object.entries(reqdOpts).forEach(pair => { - const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc); - if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) { - targetDoc[pair[0]] = pair[1]; - } - }); - items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item)); - items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item)); - } - return doc; - } - export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { - return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); - } - - export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { - let created: Doc | undefined; - const field = target[fieldKey]; - const resolved = options ?? {}; - if (field instanceof ImageField) { - created = Docs.Create.ImageDocument(field.url.href, resolved); - created.layout = ImageBox.LayoutString(fieldKey); - } else if (field instanceof Doc) { - created = field; - } else if (field instanceof VideoField) { - created = Docs.Create.VideoDocument(field.url.href, resolved); - created.layout = VideoBox.LayoutString(fieldKey); - } else if (field instanceof PdfField) { - created = Docs.Create.PdfDocument(field.url.href, resolved); - created.layout = PDFBox.LayoutString(fieldKey); - } else if (field instanceof AudioField) { - created = Docs.Create.AudioDocument(field.url.href, resolved); - created.layout = AudioBox.LayoutString(fieldKey); - } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(field.inkData, resolved); - created.layout = InkingStroke.LayoutString(fieldKey); - } else if (field instanceof List && field[0] instanceof Doc) { - created = Docs.Create.StackingDocument(DocListCast(field), resolved); - created.layout = CollectionView.LayoutString(fieldKey); - } else { - created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved }); - created.layout = FormattedTextBox.LayoutString(fieldKey); - } - if (created) { - created.title = fieldKey; - proto && created.proto && (created.proto = Doc.GetProto(proto)); - } - return created; - } - - /** - * - * @param type the type of file. - * @param path the path to the file. - * @param options the document options. - * @param overwriteDoc the placeholder loading doc. - * @returns - */ - export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> { - let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined = undefined; - if (type.indexOf('image') !== -1) { - ctor = Docs.Create.ImageDocument; - if (!options._width) options._width = 300; - } - if (type.indexOf('video') !== -1) { - ctor = Docs.Create.VideoDocument; - if (!options._width) options._width = 600; - if (!options._height) options._height = ((options._width as number) * 2) / 3; - } - if (type.indexOf('audio') !== -1) { - ctor = Docs.Create.AudioDocument; - } - if (type.indexOf('pdf') !== -1) { - ctor = Docs.Create.PdfDocument; - if (!options._width) options._width = 400; - if (!options._height) options._height = ((options._width as number) * 1200) / 927; - } - if (type.indexOf('csv') !== -1) { - ctor = Docs.Create.DataVizDocument; - if (!options._width) options._width = 400; - if (!options._height) options._height = ((options._width as number) * 1200) / 927; - } - //TODO:al+glr - // if (type.indexOf("map") !== -1) { - // ctor = Docs.Create.MapDocument; - // if (!options._width) options._width = 800; - // if (!options._height) options._height = (options._width as number) * 3 / 4; - // } - if (type.indexOf('html') !== -1) { - if (path.includes(window.location.hostname)) { - const s = path.split('/'); - const id = s[s.length - 1]; - return DocServer.GetRefField(id).then(field => { - if (field instanceof Doc) { - const embedding = Doc.MakeEmbedding(field); - embedding.x = (options.x as number) || 0; - embedding.y = (options.y as number) || 0; - embedding._width = (options._width as number) || 300; - embedding._height = (options._height as number) || (options._width as number) || 300; - return embedding; - } - return undefined; - }); - } - ctor = Docs.Create.WebDocument; - options = { ...options, _width: 400, _height: 512, title: path }; - } - - return ctor ? ctor(path, overwriteDoc ? { ...options, title: StrCast(overwriteDoc.title, path) } : options, overwriteDoc) : undefined; - } - - export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void { - const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) - .filter(btnDoc => !btnDoc.hidden) - .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) - .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title) - .map((dragDoc, i) => ({ - description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable((args: { x: number; y: number }) => { - const newDoc = DocUtils.copyDragFactory(dragDoc); - if (newDoc) { - newDoc.author = Doc.CurrentUserEmail; - newDoc.x = x; - newDoc.y = y; - EquationBox.SelectOnLoad = newDoc[Id]; - if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc); - if (pivotField) { - newDoc[pivotField] = pivotValue; - } - docAdder?.(newDoc); - } - }, StrCast(dragDoc.title)), - icon: Doc.toIcon(dragDoc), - })) as ContextMenuProps[]; - ContextMenu.Instance.addItem({ - description: 'Create document', - subitems: documentList, - icon: 'file', - }); - !simpleMenu && - ContextMenu.Instance.addItem({ - description: 'Styled Notes', - subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({ - description: ':' + StrCast(note.title), - event: undoable((args: { x: number; y: number }) => { - const textDoc = Docs.Create.TextDocument('', { - _width: 200, - x, - y, - _layout_autoHeight: note._layout_autoHeight !== false, - title: StrCast(note.title) + '#' + (note.embeddingCount = NumCast(note.embeddingCount) + 1), - }); - textDoc.layout_fieldKey = 'layout_' + note.title; - textDoc[textDoc.layout_fieldKey] = note; - if (pivotField) { - textDoc[pivotField] = pivotValue; - } - docTextAdder(textDoc); - }, 'create quick note'), - icon: StrCast(note.icon) as IconProp, - })) as ContextMenuProps[], - icon: 'sticky-note', - }); - const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data) - .filter(btnDoc => !btnDoc.hidden) - .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) - .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title) - .map((dragDoc, i) => ({ - description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable((args: { x: number; y: number }) => { - const newDoc = DocUtils.delegateDragFactory(dragDoc); - if (newDoc) { - newDoc.author = Doc.CurrentUserEmail; - newDoc.x = x; - newDoc.y = y; - EquationBox.SelectOnLoad = newDoc[Id]; - if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc); - if (pivotField) { - newDoc[pivotField] = pivotValue; - } - docAdder?.(newDoc); - } - }, StrCast(dragDoc.title)), - icon: Doc.toIcon(dragDoc), - })) as ContextMenuProps[]; - ContextMenu.Instance.addItem({ - description: 'User Templates', - subitems: userDocList, - icon: 'file', - }); - } - - // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) - - /** - * Applies a template to a Doc and logs the action with the UndoManager - * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon'). - * Alternatively, the signature can be omitted and the template can be provided. - * @param doc the Doc to apply the template to. - * @param creator a function that will create the template if it doesn't exist - * @param templateSignature the signature name for a template that has already been created and registered on the userDoc. (can be "" if template is provide) - * @param template the template to use (optional if templateSignature is provided) - * @returns doc - */ - export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) { - const batch = UndoManager.StartBatch('makeCustomViewClicked'); - createCustomView(doc, creator, templateSignature || StrCast(template?.title), template); - batch.end(); - return doc; - } - export function findTemplate(templateName: string, type: string, signature: string) { - let docLayoutTemplate: Opt<Doc>; - const iconViews = DocListCast(Cast(Doc.UserDoc()['template_icons'], Doc, null)?.data); - const templBtns = DocListCast(Cast(Doc.UserDoc()['template_buttons'], Doc, null)?.data); - const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data); - const userTypes = DocListCast(Cast(Doc.UserDoc()['template_user'], Doc, null)?.data); - const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data); - const allTemplates = iconViews - .concat(templBtns) - .concat(noteTypes) - .concat(userTypes) - .concat(clickFuncs) - .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc) - .filter(doc => doc.isTemplateDoc); - // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized - // first try to find a template that matches the specific document type (<typeName>_<templateName>). otherwise, fallback to a general match on <templateName> - !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc)); - !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc)); - return docLayoutTemplate; - } - export function createCustomView(doc: Doc, creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { - const templateName = templateSignature.replace(/\(.*\)/, ''); - doc.layout_fieldKey = 'layout_' + (templateSignature || docLayoutTemplate?.title); - docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature); - - const customName = 'layout_' + templateSignature; - const _width = NumCast(doc._width); - const _height = NumCast(doc._height); - const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _layout_autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _layout_showSidebar: false }; - - if (docLayoutTemplate) { - if (docLayoutTemplate !== doc[customName]) { - Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); - } - } else { - let fieldTemplate: Opt<Doc>; - if (doc.data instanceof RichTextField || typeof doc.data === 'string') { - fieldTemplate = Docs.Create.TextDocument('', options); - } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options); - } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options); - } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options); - } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options); - } - const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); - docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); - } - } - export function makeCustomView(doc: Doc, custom: boolean, layout: string) { - Doc.setNativeView(doc); - if (custom) { - makeCustomViewClicked(doc, Docs.Create.StackingDocument, layout, undefined); - } - } - export function iconify(doc: Doc) { - const layout_fieldKey = Cast(doc.layout_fieldKey, 'string', null); - DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined); - if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') doc.deiconifyLayout = layout_fieldKey.replace('layout_', ''); - } - - export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) { - runInAction(() => { - docList.forEach((d, i) => { - DocUtils.iconify(d); - d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size - size; - d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size - size; - d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - }); - }); - if (create) { - const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', _freeform_noZoom: true, x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, dragWhenActive: true, _layout_fitWidth: false }); - newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size; - newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size; - newCollection._width = newCollection._height = size * 2; - return newCollection; - } - } - export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) { - const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from'); - if (!portalLink) { - DocUtils.MakeLink( - doc, - Docs.Create.FreeformDocument([], { - _width: NumCast(layoutDoc._width) + 10, - _height: Math.max(NumCast(layoutDoc._height), NumCast(layoutDoc._width) + 10), - _isLightbox: true, - _layout_fitWidth: true, - title: StrCast(doc.title) + ' [Portal]', - }), - { link_relationship: 'portal to:portal from' } - ); - } - doc.followLinkLocation = OpenWhere.lightbox; - doc.onClick = FollowLinkScript(); - } - - export function LeavePushpin(doc: Doc, annotationField: string) { - if (doc.followLinkToggle) return undefined; - const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null); - const hasContextAnchor = LinkManager.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context)); - if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { - const pushpin = Docs.Create.FontIconDocument({ - title: '', - annotationOn: Cast(doc.annotationOn, Doc, null), - followLinkToggle: true, - icon: 'map-pin', - x: Cast(doc.x, 'number', null), - y: Cast(doc.y, 'number', null), - backgroundColor: '#ACCEF7', - layout_hideAllLinks: true, - _width: 15, - _height: 15, - _xPadding: 0, - onClick: FollowLinkScript(), - _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), - }); - Doc.AddDocToList(context, annotationField, pushpin); - const pushpinLink = DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, ''); - doc._timecodeToShow = undefined; - return pushpin; - } - return undefined; - } - - // /** - // * - // * @param dms Degree Minute Second format exif gps data - // * @param ref ref that determines negativity of decimal coordinates - // * @returns a decimal format of gps latitude / longitude - // */ - // function getDecimalfromDMS(dms?: number[], ref?: string) { - // if (dms && ref) { - // let degrees = dms[0] / dms[1]; - // let minutes = dms[2] / dms[3] / 60.0; - // let seconds = dms[4] / dms[5] / 3600.0; - - // if (['S', 'W'].includes(ref)) { - // degrees = -degrees; minutes = -minutes; seconds = -seconds - // } - // return (degrees + minutes + seconds).toFixed(5); - // } - // } - - function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) { - var dd = degrees + minutes / 60 + seconds / (60 * 60); - if (direction === 'S' || direction === 'W') { - dd = dd * -1; - } // Don't do anything for N or E - return dd; - } - - export function assignImageInfo(result: Upload.FileInformation, proto: Doc) { - if (Upload.isImageInformation(result)) { - const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); - const exifRotation = StrCast((result.exifData?.data as any)?.Orientation).toLowerCase(); - proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); - proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - if (NumCast(proto['data-nativeOrientation']) >= 5) { - proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - } - proto.data_exif = JSON.stringify(result.exifData?.data); - proto.data_contentSize = result.contentSize; - // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates - const latitude = result.exifData?.data?.GPSLatitude; - const latitudeDirection = result.exifData?.data?.GPSLatitudeRef; - const longitude = result.exifData?.data?.GPSLongitude; - const longitudeDirection = result.exifData?.data?.GPSLongitudeRef; - if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) { - proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); - proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); - } - } - } - - async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, overwriteDoc?: Doc) { - if (result instanceof Error) { - alert(`Upload failed: ${result.message}`); - return; - } - const full = { ...options, _width: 400, title: name }; - const pathname = result.accessPaths.agnostic.client; - const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc); - if (doc) { - const proto = Doc.GetProto(doc); - proto.text = result.rawText; - !(result instanceof Error) && DocUtils.assignImageInfo(result, proto); - if (Upload.isVideoInformation(result)) { - proto.data_duration = result.duration; - } - if (overwriteDoc) { - LoadingBox.removeCurrentlyLoading(overwriteDoc); - } - generatedDocuments.push(doc); - } - } - - export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) { - const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout); - const tbox = Docs.Create.TextDocument('', { - annotationOn, - backgroundColor, - x, - y, - title, - ...(defaultTextTemplate - ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance - : { - _width: width || 200, - _height: 35, - _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), - _layout_fitWidth: true, - _layout_autoHeight: true, - }), - }); - - if (defaultTextTemplate) { - tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); - Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template - tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values) - } - return tbox; - } - - export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) { - const generatedDocuments: Doc[] = []; - Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => { - const { - source: { newFilename, originalFilename, mimetype }, - result, - } = upfiles.lastElement(); - if ((result as any).message) { - if (overwriteDoc) { - overwriteDoc.isLoading = false; - overwriteDoc.loadingError = (result as any).message; - LoadingBox.removeCurrentlyLoading(overwriteDoc); - } - } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc); - }); - } - - /** - * uploadFilesToDocs will take in an array of Files, and creates documents for the - * new files. - * - * @param files an array of files that will be uploaded - * @param options options to use while uploading - * @returns - */ - export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { - const generatedDocuments: Doc[] = []; - - // These files do not have overwriteDocs, so we do not set the guid and let the client generate one. - const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file })); - - const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs); - for (const { - source: { newFilename, mimetype }, - result, - } of upfiles) { - newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options); - } - return generatedDocuments; - } - - export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) { - const generatedDocuments: Doc[] = []; - // Since this file has an overwriteDoc, we can set the client tracking guid to the overwriteDoc's guid. - Networking.UploadFilesToServer([{ file, guid: overwriteDoc[Id] }]).then(upfiles => { - const { - source: { newFilename, mimetype }, - result, - } = upfiles.lastElement() ?? { source: { newFilename: '', mimetype: '' }, result: { message: 'upload failed' } }; - if ((result as any).message) { - if (overwriteDoc) { - overwriteDoc.loadingError = (result as any).message; - LoadingBox.removeCurrentlyLoading(overwriteDoc); - } - } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc); - }); - } - - // copies the specified drag factory document - export function copyDragFactory(dragFactory: Doc) { - if (!dragFactory) return undefined; - const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); - if (ndoc && dragFactory['dragFactory_count'] !== undefined) { - dragFactory['dragFactory_count'] = NumCast(dragFactory['dragFactory_count']) + 1; - Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString(), true); - } - - return ndoc; - } - export function delegateDragFactory(dragFactory: Doc) { - const ndoc = Doc.MakeDelegateWithProto(dragFactory); - if (ndoc && dragFactory['dragFactory_count'] !== undefined) { - dragFactory['dragFactory_count'] = NumCast(dragFactory['dragFactory_count']) + 1; - Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory['dragFactory_count']).toString(); - } - return ndoc; - } -} - -ScriptingGlobals.add('Docs', Docs); -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) { - return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory; -}); -ScriptingGlobals.add(function makeDelegate(proto: any) { - const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title }); - return d; -}); -ScriptingGlobals.add(function generateLinkTitle(link: Doc) { - const link_anchor_1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>'; - const link_anchor_2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>'; - const relation = link.link_relationship || 'to'; - return `${link_anchor_1title} (${relation}) ${link_anchor_2title}`; -}); |