aboutsummaryrefslogtreecommitdiff
path: root/src/client/documents/Documents.ts
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-04-30 23:35:18 -0400
committerbobzel <zzzman@gmail.com>2024-04-30 23:35:18 -0400
commit098deaa68c8b9bb781748fbe0c1bd0104bab3596 (patch)
treeedf78ab4ad63bc8f5ae499dcc994d22c9afb8414 /src/client/documents/Documents.ts
parent776c9cd88fc0799426ced87f36cb215dfdc1854b (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.ts1270
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}`;
-});