diff options
author | bobzel <zzzman@gmail.com> | 2024-04-30 23:35:18 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-04-30 23:35:18 -0400 |
commit | 098deaa68c8b9bb781748fbe0c1bd0104bab3596 (patch) | |
tree | edf78ab4ad63bc8f5ae499dcc994d22c9afb8414 /src/client/documents/Documents.ts | |
parent | 776c9cd88fc0799426ced87f36cb215dfdc1854b (diff) |
unwinding more import loops by splitting up Documents.ts into DocUtils.ts and moving crate functions to <>Box functions
Diffstat (limited to 'src/client/documents/Documents.ts')
-rw-r--r-- | src/client/documents/Documents.ts | 1270 |
1 files changed, 30 insertions, 1240 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0e7f911c7..a3fea1ce4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,79 +1,27 @@ /* eslint-disable prefer-destructuring */ /* eslint-disable default-param-last */ /* eslint-disable no-use-before-define */ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { saveAs } from 'file-saver'; -import * as JSZip from 'jszip'; -import { action, reaction, runInAction } from 'mobx'; +import { reaction } from 'mobx'; import { basename } from 'path'; import { ClientUtils, OmitKeys } from '../../ClientUtils'; -import * as JSZipUtils from '../../JSZipUtils'; -import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Field, FieldType, 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 { InkDataFieldName, InkField } from '../../fields/InkField'; -import { List, ListFieldName } from '../../fields/List'; -import { ProxyField } from '../../fields/Proxy'; +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 { ScriptCast, StrCast } from '../../fields/Types'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField'; -import { SharingPermissions, inheritParentAcls } from '../../fields/util'; +import { SharingPermissions } from '../../fields/util'; import { PointData } from '../../pen-gestures/GestureTypes'; -import { Upload } from '../../server/SharedMediaTypes'; import { DocServer } from '../DocServer'; -import { Networking } from '../Network'; -import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; -import { FollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; -import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { SerializationHelper } from '../util/SerializationHelper'; -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, mediaState } 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 { 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 ''; @@ -146,7 +94,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; @@ -202,7 +150,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; @@ -245,6 +193,7 @@ 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?: 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); @@ -253,6 +202,7 @@ export class DocumentOptions { 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); @@ -272,6 +222,12 @@ export class DocumentOptions { _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); @@ -325,6 +281,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)'); @@ -333,7 +290,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: ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.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.'); @@ -403,6 +360,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'); @@ -433,6 +391,7 @@ 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'); @@ -528,120 +487,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: { - acl: '', - _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: { acl: '', _width: 400 }, - }, - ], - [ - DocumentType.IMG, - { - layout: { view: ImageBox, dataField: defaultDataKey }, - options: { acl: '', freeform: '', systemIcon: 'BsFileEarmarkImageFill' }, - }, - ], - [ - DocumentType.WEB, - { - layout: { view: WebBox, dataField: defaultDataKey }, - options: { acl: '', _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: { - acl: '', - _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: { acl: '', _layout_fitWidth: true, _height: 150 }, - }, - ], - [ - DocumentType.VID, - { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { acl: '', _layout_currentTimecode: 0, systemIcon: 'BsFileEarmarkPlayFill' }, - }, - ], - [ - DocumentType.AUDIO, - { - layout: { view: AudioBox, dataField: defaultDataKey }, - options: { acl: '', _height: 100, layout_fitWidth: true, layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsFillVolumeUpFill' }, - }, - ], - [ - DocumentType.REC, - { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { acl: '', _height: 100, backgroundColor: 'pink', systemIcon: 'BsFillMicFill' }, - }, - ], - [ - DocumentType.PDF, - { - layout: { view: PDFBox, dataField: defaultDataKey }, - options: { acl: '', _layout_curPage: 1, _layout_fitWidth: true, layout_nativeDimEditable: true, layout_reflowVertical: true, systemIcon: 'BsFileEarmarkPdfFill' }, - }, - ], - [ - DocumentType.MAP, - { - layout: { view: MapBox, dataField: defaultDataKey }, - options: { acl: '', 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: { - acl: '', - 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' }, }, ], [ @@ -652,175 +503,18 @@ export namespace Docs { options: { acl: '', title: 'Global Script Database' }, }, ], - [ - DocumentType.SCRIPTING, - { - layout: { view: ScriptingBox, dataField: defaultDataKey }, - options: { acl: '', systemIcon: 'BsFileEarmarkCodeFill' }, - }, - ], - [ - DocumentType.LABEL, - { - layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', _singleLine: true, layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true }, - }, - ], - [ - DocumentType.EQUATION, - { - layout: { view: EquationBox, dataField: 'text' }, - options: { acl: '', 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: { acl: '', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true }, - }, - ], - [ - DocumentType.BUTTON, - { - layout: { view: LabelBox, dataField: 'title' }, - options: { acl: '', layout_nativeDimEditable: true, layout_reflowHorizontal: true, layout_reflowVertical: true }, - }, - ], - [ - DocumentType.PRES, - { - layout: { view: PresBox, dataField: defaultDataKey }, - options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true }, - }, - ], - [ - DocumentType.FONTICON, - { - layout: { view: FontIconBox, dataField: 'icon' }, - options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 }, - }, - ], - [ - DocumentType.WEBCAM, - { - layout: { view: RecordingBox, dataField: defaultDataKey }, - options: { acl: '', systemIcon: 'BsFillCameraVideoFill' }, - }, - ], - [ - DocumentType.PRESELEMENT, - { - layout: { view: PresElementBox, dataField: defaultDataKey }, - options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' }, - }, - ], + [ DocumentType.CONFIG, { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { acl: '', 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: { - acl: '', - 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: { acl: '', layout_nativeDimEditable: true, systemIcon: 'BsCameraFill' }, - }, - ], - [ - DocumentType.COMPARISON, - { - data: '', - layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { - acl: '', - 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: { acl: '', title: 'Global Group Database' }, - }, - ], - [ - DocumentType.DATAVIZ, - { - layout: { view: DataVizBox, dataField: defaultDataKey }, - options: { - acl: '', - 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: { acl: '', _layout_fitWidth: true, _fitHeight: true, layout_nativeDimEditable: true }, - }, - ], - [ - DocumentType.SIMULATION, - { - data: '', - layout: { view: PhysicsSimulationBox, dataField: defaultDataKey, _width: 1000, _height: 800 }, - options: { acl: '', _height: 100, mass1: '', mass2: '', layout_nativeDimEditable: true, position: '', acceleration: '', pendulum: '', spring: '', wedge: '', simulation: '', review: '', systemIcon: 'BsShareFill' }, - }, - ], - [ - DocumentType.PUSHPIN, - { - layout: { view: MapPushpinBox, dataField: defaultDataKey }, - options: { acl: '' }, + options: { acl: '', config: '', layout_hideLinkButton: true, layout_unrendered: true }, }, ], [ DocumentType.MAPROUTE, { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { acl: '' }, - }, - ], - [ - DocumentType.CALENDAR, - { - layout: { view: CalendarBox, dataField: defaultDataKey }, + layout: { view: EmptyBox, dataField: defaultDataKey }, options: { acl: '' }, }, ], @@ -1005,7 +699,7 @@ export namespace Docs { } 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); @@ -1039,7 +733,7 @@ export namespace Docs { */ // 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 || 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 @@ -1309,34 +1003,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), - ClientUtils.CurrentUserEmail() === 'guest' ? options : { acl_Guest: SharingPermissions.View, ...options }, - id - ); - configs.forEach(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); } @@ -1346,879 +1012,3 @@ export namespace Docs { } } } - -export namespace DocUtils { - function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean { - let value = valueIn; - const hasFunctionFilter = ClientUtils.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 = (val: string, anchor: Doc) => { - const linkedToExp = (val ?? '').split('='); - if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val; - 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(`,${ClientUtils.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 FieldType).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; - } - const facetKeys = Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== ClientUtils.noDragDocsFilter.split(Doc.FilterSep)[0]); - // eslint-disable-next-line no-restricted-syntax - for (const facetKey of facetKeys) { - 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 : facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length; - const satisfiesUnsetsFacets = !unsets.length ? true : 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 FieldType).includes(value)); - } - return Field.toString(d[facetKey] as FieldType).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'; - }) - : 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 const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; - - export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { - broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore - 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, showAt?: number[]) => { - if (showAt) { - LinkManager.Instance.currentLink = linkDoc; - - TaskCompletionBox.textDisplayed = 'Link Created'; - TaskCompletionBox.popupX = showAt[0]; - TaskCompletionBox.popupY = showAt[1] - 33; - TaskCompletionBox.taskCompleted = true; - - LinkDescriptionPopup.Instance.popupX = showAt[0]; - LinkDescriptionPopup.Instance.popupY = showAt[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).forEach(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')) - .forEach(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 }) { - // eslint-disable-next-line no-return-assign - 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; - 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; - // eslint-disable-next-line no-param-reassign - 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 => ({ - description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable(() => { - const newDoc = DocUtils.copyDragFactory(dragDoc); - if (newDoc) { - newDoc.author = ClientUtils.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 => ({ - description: ':' + StrCast(note.title), - event: undoable(() => { - 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 => ({ - description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable(() => { - const newDoc = DocUtils.delegateDragFactory(dragDoc); - if (newDoc) { - newDoc.author = ClientUtils.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) { - 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 ?? '')); - // eslint-disable-next-line no-param-reassign - docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type)); - - 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 layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null); - DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined); - if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') doc.deiconifyLayout = layoutFieldKey.replace('layout_', ''); - } - - export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) { - runInAction(() => { - docList.forEach((doc, i) => { - const d = doc; - 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; - } - return undefined; - } - 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); - 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) { - let dd = degrees + minutes / 60 + seconds / (60 * 60); - if (direction === 'S' || direction === 'W') { - dd *= -1; - } // Don't do anything for N or E - return dd; - } - - export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) { - const proto = protoIn; - 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, 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); - upfiles.forEach(({ source: { newFilename, mimetype }, result }) => { - 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; - } - - export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') { - const { clone, map, linkMap } = await Doc.MakeClone(doc); - const proms = new Set<string>(); - function replacer(key: any, value: any) { - if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; - if (value?.__type === 'image') { - const extension = value.url.replace(/.*\./, ''); - proms.add(value.url.replace('.' + extension, '_o.' + extension)); - return SerializationHelper.Serialize(new ImageField(value.url)); - } - if (value?.__type === 'pdf') { - proms.add(value.url); - return SerializationHelper.Serialize(new PdfField(value.url)); - } - if (value?.__type === 'audio') { - proms.add(value.url); - return SerializationHelper.Serialize(new AudioField(value.url)); - } - if (value?.__type === 'video') { - proms.add(value.url); - return SerializationHelper.Serialize(new VideoField(value.url)); - } - if ( - value instanceof Doc || - value instanceof ScriptField || - value instanceof RichTextField || - value instanceof InkField || - value instanceof CsvField || - value instanceof WebField || - value instanceof DateField || - value instanceof ProxyField || - value instanceof ComputedField - ) { - return SerializationHelper.Serialize(value); - } - if (value instanceof Array && key !== ListFieldName && key !== InkDataFieldName) return { fields: value, __type: 'list' }; - return value; - } - - const docs: { [id: string]: any } = {}; - const links: { [id: string]: any } = {}; - Array.from(map.entries()).forEach(f => { - docs[f[0]] = f[1]; - }); - Array.from(linkMap.entries()).forEach(l => { - links[l[0]] = l[1]; - }); - const jsonDocs = JSON.stringify({ id: clone[Id], docs, links }, decycle(replacer)); - - const zip = new JSZip(); - let count = 0; - const promArr = Array.from(proms) - .filter(url => url?.startsWith('/files')) - .map(url => url.replace('/', '')); // window.location.origin)); - console.log(promArr.length); - if (!promArr.length) { - zip.file('docs.json', jsonDocs); - zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); - } else - promArr.forEach((url, i) => { - // loading a file and add it in a zip file - JSZipUtils.getBinaryContent(window.location.origin + '/' + url, (err: any, data: any) => { - if (err) throw err; // or handle the error - // // Generate a directory within the Zip file structure - // const assets = zip.folder("assets"); - // assets.file(filename, data, {binary: true}); - const assetPathOnServer = promArr[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%'); - zip.file(assetPathOnServer, data, { binary: true }); - console.log(' => ' + url); - if (++count === promArr.length) { - zip.file('docs.json', jsonDocs); - zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); - // const a = document.createElement("a"); - // const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); - // a.href = url; - // a.download = `DocExport-${this.props.Document[Id]}.zip`; - // a.click(); - } - }); - }); - } -} - -ScriptingGlobals.add('Docs', Docs); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) { - return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory; -}); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function makeDelegate(proto: any) { - const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title }); - return d; -}); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function generateLinkTitle(link: Doc) { - const linkAnchor1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>'; - const linkAnchor2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>'; - const relation = link.link_relationship || 'to'; - return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`; -}); |