diff options
Diffstat (limited to 'src/client/documents/Documents.ts')
-rw-r--r-- | src/client/documents/Documents.ts | 317 |
1 files changed, 208 insertions, 109 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b160379df..d41a96db2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,28 +1,35 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { saveAs } from 'file-saver'; +import * as JSZip from 'jszip'; import { action, reaction, runInAction } from 'mobx'; import { basename } from 'path'; -import { OmitKeys, Utils } from '../../Utils'; +import { ClientUtils, OmitKeys } from '../../ClientUtils'; +import * as JSZipUtils from '../../JSZipUtils'; +import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc'; import { DocData, Initializing } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { HtmlField } from '../../fields/HtmlField'; -import { InkField, PointData } from '../../fields/InkField'; -import { List } from '../../fields/List'; +import { InkDataFieldName, InkField } from '../../fields/InkField'; +import { List, ListFieldName } from '../../fields/List'; +import { ProxyField } from '../../fields/Proxy'; import { RichTextField } from '../../fields/RichTextField'; import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; -import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; +import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from '../../fields/URLField'; import { SharingPermissions, inheritParentAcls } from '../../fields/util'; +import { PointData } from '../../pen-gestures/GestureTypes'; import { Upload } from '../../server/SharedMediaTypes'; import { DocServer } from '../DocServer'; import { Networking } from '../Network'; -import { YoutubeBox } from '../apis/youtube/YoutubeBox'; -import { DragManager, dropActionType } from '../util/DragManager'; +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'; @@ -30,7 +37,7 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveIn import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionView } from '../views/collections/CollectionView'; import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; -import { AudioBox, media_state } from '../views/nodes/AudioBox'; +import { 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'; @@ -60,6 +67,7 @@ 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', '')); @@ -83,7 +91,7 @@ export class FInfo { description: string = ''; readOnly: boolean = false; fieldType?: FInfoFieldType; - values?: Field[]; + values?: FieldType[]; filterable?: boolean = true; // can be used as a Filter in FilterPanel // format?: string; // format to display values (e.g, decimal places, $, etc) @@ -252,8 +260,8 @@ export class DocumentOptions { opacity?: NUMt = new NumInfo('document opacity', false); viewTransitionTime?: NUMt = new NumInfo('transition duration for view parameters', false); dontRegisterView?: BOOLt = new BoolInfo('are views of this document registered so that they can be found when following links, etc', false); - _undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' - undoIgnoreFields?: List<string>; //'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' + _undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' + undoIgnoreFields?: List<string>; // 'fields that should not be added to the undo stack (opacity for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack)' _header_height?: NUMt = new NumInfo('height of document header used for displaying title', false); _header_fontSize?: NUMt = new NumInfo('font size of header of custom notes', false); _header_pointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume'); @@ -321,7 +329,7 @@ export class DocumentOptions { _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false); stroke_width?: NUMt = new NumInfo('width of an ink stroke', false); stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke'); - mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false); + mediaState?: STRt = new StrInfo(`status of audio/video media document: ${mediaState.PendingRecording}, ${mediaState.Recording}, ${mediaState.Paused}, ${mediaState.Playing}`, false); recording?: BOOLt = new BoolInfo('whether WebCam is recording or not'); slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)'); autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.'); @@ -401,7 +409,7 @@ export class DocumentOptions { _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)'); _freeform_fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content'); - //BUTTONS + // BUTTONS buttonText?: string; btnType?: string; btnList?: List<string>; @@ -413,7 +421,7 @@ export class DocumentOptions { switchToggle?: boolean; badgeValue?: ScriptField; - //LINEAR VIEW + // LINEAR VIEW linearView_IsOpen?: BOOLt = new BoolInfo('is linear view open'); linearView_Expandable?: BOOLt = new BoolInfo('can linear view be expanded'); linearView_Dropdown?: BOOLt = new BoolInfo('can linear view be opened as a dropdown'); @@ -431,7 +439,7 @@ export class DocumentOptions { link_anchor_1_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)'); link_anchor_2_useSmallAnchor?: BOOLt = new BoolInfo('whether link_anchor_1 of a link should use a miniature anchor dot (as when the anchor is a text selection)'); link_relationshipList?: List<string>; // for storing different link relationships (when set by user in the link editor) - link_relationshipSizes?: List<number>; //stores number of links contained in each relationship + link_relationshipSizes?: List<number>; // stores number of links contained in each relationship link_colorList?: List<string>; // colors of links corresponding to specific link relationships followLinkZoom?: BOOLt = new BoolInfo('whether to zoom to the target of a link'); followLinkToggle?: BOOLt = new BoolInfo('whether target of link should be toggled on and off when following a link to it'); @@ -463,7 +471,7 @@ export class DocumentOptions { dragFactory_count?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)', false, true); dragFactory?: DOCt = new DocInfo('document to create when dragging with a suitable onDragStart script', false); clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false); - onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop + onDragStart?: ScriptField; // script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script) tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true); treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view'); @@ -502,8 +510,6 @@ export class DocumentOptions { export const DocOptions = new DocumentOptions(); export namespace Docs { - export let newAccount: boolean = false; - export namespace Prototypes { type LayoutSource = { LayoutString: (key: string) => string }; type PrototypeTemplate = { @@ -647,12 +653,6 @@ export namespace Docs { }, ], [ - DocumentType.YOUTUBE, - { - layout: { view: YoutubeBox, dataField: defaultDataKey }, - }, - ], - [ DocumentType.LABEL, { layout: { view: LabelBox, dataField: 'title' }, @@ -670,7 +670,7 @@ export namespace Docs { layout_nativeDimEditable: true, layout_hideDecorationTitle: true, systemIcon: 'BsCalculatorFill', - }, ///systemIcon: 'BsSuperscript' + BsSubscript + }, // systemIcon: 'BsSuperscript' + BsSubscript }, ], [ @@ -850,7 +850,7 @@ export namespace Docs { // fetch the actual prototype documents from the server const actualProtos = await DocServer.GetRefFields(prototypeIds); // update this object to include any default values: DocumentOptions for all prototypes - prototypeIds.map(id => { + prototypeIds.forEach(id => { const existing = actualProtos[id] as Doc; const type = id.replace(suffix, '') as DocumentType; // get or create prototype of the specified type... @@ -908,7 +908,7 @@ export namespace Docs { if (!template) { return undefined; } - const layout = template.layout; + const { layout } = template; // create title const upper = suffix.toUpperCase(); const title = prototypeId.toUpperCase().replace(upper, `_${upper}`); @@ -928,7 +928,7 @@ export namespace Docs { }; Object.entries(options) .filter(pair => typeof pair[1] === 'string' && pair[1].startsWith('@')) - .map(pair => { + .forEach(pair => { if (!existing || ScriptCast(existing[pair[0]])?.script.originalScript !== pair[1].substring(1)) { (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); } @@ -960,7 +960,9 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc, noView?: boolean) { + // eslint-disable-next-line default-param-last + function InstanceFromProto(proto: Doc, data: FieldType | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDocIn?: Doc, noView?: boolean) { + const placeholderDoc = placeholderDocIn; const viewKeys = ['x', 'y', 'isSystem']; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_'); @@ -968,7 +970,7 @@ export namespace Docs { dataProps['acl-Guest'] = options['acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); dataProps.isSystem = viewProps.isSystem; dataProps.isDataDoc = true; - dataProps.author = Doc.CurrentUserEmail; + dataProps.author = ClientUtils.CurrentUserEmail; dataProps.author_date = new DateField(); if (fieldKey) { dataProps[`${fieldKey}_modificationDate`] = new DateField(); @@ -988,7 +990,7 @@ export namespace Docs { } if (!noView) { - const viewFirstProps: { [id: string]: any } = { author: Doc.CurrentUserEmail }; + const viewFirstProps: { [id: string]: any } = { author: ClientUtils.CurrentUserEmail }; viewFirstProps['acl-Guest'] = options['_acl-Guest'] ?? (Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View); let viewDoc: Doc; // determines whether viewDoc should be created using placeholder Doc or default @@ -998,7 +1000,9 @@ export namespace Docs { viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); Array.from(Object.keys(placeholderDoc)) .filter(key => key.startsWith('acl')) - .forEach(key => (dataDoc[key] = viewDoc[key] = placeholderDoc[key])); + .forEach(key => { + dataDoc[key] = viewDoc[key] = placeholderDoc[key]; + }); } else { viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); } @@ -1017,6 +1021,7 @@ export namespace Docs { return dataDoc; } + // eslint-disable-next-line default-param-last export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) { const imgField = url instanceof ImageField ? url : url ? new ImageField(url) : undefined; return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField?.url.href ?? '-no image-'), ...options }, undefined, undefined, undefined, overwriteDoc); @@ -1035,18 +1040,16 @@ export namespace Docs { * @param fieldKey the field that the compiled script is written into. * @returns the Scripting Doc */ + // eslint-disable-next-line default-param-last export function ScriptingDocument(script: Opt<ScriptField> | null, options: DocumentOptions = {}, fieldKey?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } + // eslint-disable-next-line default-param-last export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc); } - export function YoutubeDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { - return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options, undefined, undefined, undefined, overwriteDoc); - } - export function WebCamDocument(url: string, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), '', options); } @@ -1059,6 +1062,7 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), undefined, options); } + // eslint-disable-next-line default-param-last export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), options, undefined, undefined, undefined, overwriteDoc); } @@ -1072,7 +1076,7 @@ export namespace Docs { } export function LoadingDocument(file: File | string, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file == 'string' ? file : file.name, ...options }, undefined, ''); + return InstanceFromProto(Prototypes.get(DocumentType.LOADING), undefined, { _height: 150, _width: 200, title: typeof file === 'string' ? file : file.name, ...options }, undefined, ''); } export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { @@ -1101,6 +1105,7 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } + // eslint-disable-next-line default-param-last export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) { const linkDoc = InstanceFromProto( Prototypes.get(DocumentType.LINK), @@ -1124,7 +1129,7 @@ export namespace Docs { options: DocumentOptions = {}, strokeWidth = ActiveInkWidth(), color = ActiveInkColor(), - stroke_bezier = ActiveInkBezierApprox(), + strokeBezier = ActiveInkBezierApprox(), fillColor = ActiveFillColor(), arrowStart = ActiveArrowStart(), arrowEnd = ActiveArrowEnd(), @@ -1138,7 +1143,7 @@ export namespace Docs { I.fillColor = fillColor; I.stroke = new InkField(points); I.stroke_width = strokeWidth; - I.stroke_bezier = stroke_bezier; + I.stroke_bezier = strokeBezier; I.stroke_startMarker = arrowStart; I.stroke_endMarker = arrowEnd; I.stroke_dash = dash; @@ -1148,12 +1153,13 @@ export namespace Docs { I.defaultDoubleClick = 'ignore'; I.author_date = new DateField(); I['acl-Guest'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.View; - //I['acl-Override'] = SharingPermissions.Unset; + // I['acl-Override'] = SharingPermissions.Unset; I[Initializing] = false; return ink; } + // eslint-disable-next-line default-param-last export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { const width = options._width || undefined; const height = options._height || undefined; @@ -1169,7 +1175,7 @@ export namespace Docs { const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'https://www.wikipedia.org/'), options); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url || 'https://www.wikipedia.org/'), options); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { @@ -1324,10 +1330,10 @@ export namespace Docs { const doc = DockDocument( configs.map(c => c.doc), JSON.stringify(layoutConfig), - Doc.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options }, + ClientUtils.CurrentUserEmail === 'guest' ? options : { 'acl-Guest': SharingPermissions.View, ...options }, id ); - configs.map(c => { + configs.forEach(c => { Doc.SetContainer(c.doc, doc); inheritParentAcls(doc, c.doc, false); }); @@ -1345,8 +1351,9 @@ export namespace Docs { } export namespace DocUtils { - function matchFieldValue(doc: Doc, key: string, value: any): boolean { - const hasFunctionFilter = Utils.HasFunctionFilter(value); + function matchFieldValue(doc: Doc, key: string, valueIn: any): boolean { + let value = valueIn; + const hasFunctionFilter = ClientUtils.HasFunctionFilter(value); if (hasFunctionFilter) { return hasFunctionFilter(StrCast(doc[key])); } @@ -1365,7 +1372,7 @@ export namespace DocUtils { matchLink(value,DocCast(link.link_anchor_2)) )); } if (typeof value === 'string') { - value = value.replace(`,${Utils.noRecursionHack}`, ''); + value = value.replace(`,${ClientUtils.noRecursionHack}`, ''); } const fieldVal = doc[key]; // prettier-ignore @@ -1377,7 +1384,7 @@ export namespace DocUtils { if (vals.length) { return vals.some(v => typeof v === 'string' && v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } - return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return Field.toString(fieldVal as FieldType).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } /** * @param docs @@ -1410,7 +1417,9 @@ export namespace DocUtils { if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { return false; } - for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragDocsFilter.split(Doc.FilterSep)[0])) { + const 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) @@ -1431,8 +1440,8 @@ export namespace DocUtils { if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value)); const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value)); - const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length)); - const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined); + const 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 => { @@ -1441,20 +1450,18 @@ export namespace DocUtils { const allKeys = Array.from(Object.keys(d)); allKeys.push(...Object.keys(Doc.GetProto(d))); const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); - return keys.some(key => Field.toString(d[key] as Field).includes(value)); + return keys.some(key => Field.toString(d[key] as FieldType).includes(value)); } - return Field.toString(d[facetKey] as Field).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; - } + else if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; } - return parentCollection?.childFilters_boolean === 'OR' ? false : true; + return parentCollection?.childFilters_boolean !== 'OR'; }) : childDocs; const rangeFilteredDocs = filteredDocs.filter(d => { @@ -1464,7 +1471,7 @@ export namespace DocUtils { 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?") + // console.log("Should 'undefined' pass range filter or not?") } else if (val < min || val > max) return false; } return true; @@ -1472,10 +1479,10 @@ export namespace DocUtils { return rangeFilteredDocs; } - export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; + export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { - broadcastEvent && runInAction(() => (Doc.RecordingEvent = Doc.RecordingEvent + 1)); + 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' }); @@ -1510,7 +1517,9 @@ export namespace DocUtils { } setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), + action(() => { + TaskCompletionBox.taskCompleted = false; + }), 2500 ); } @@ -1550,7 +1559,7 @@ export namespace DocUtils { export function AssignScripts(doc: Doc, scripts?: { [key: string]: string | undefined }, funcs?: { [key: string]: string }) { scripts && - Object.keys(scripts).map(key => { + 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, { @@ -1573,7 +1582,7 @@ export namespace DocUtils { funcs && Object.keys(funcs) .filter(key => !key.endsWith('-setter')) - .map(key => { + .forEach(key => { const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); if (ScriptCast(cfield)?.script.originalScript !== funcs[key]) { const setFunc = Cast(funcs[key + '-setter'], 'string', null); @@ -1602,6 +1611,7 @@ export namespace DocUtils { 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); } @@ -1649,7 +1659,7 @@ export namespace DocUtils { * @returns */ export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> { - let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined = undefined; + 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; @@ -1672,7 +1682,7 @@ export namespace DocUtils { if (!options._width) options._width = 400; if (!options._height) options._height = ((options._width as number) * 1200) / 927; } - //TODO:al+glr + // TODO:al+glr // if (type.indexOf("map") !== -1) { // ctor = Docs.Create.MapDocument; // if (!options._width) options._width = 800; @@ -1695,6 +1705,7 @@ export namespace DocUtils { }); } ctor = Docs.Create.WebDocument; + // eslint-disable-next-line no-param-reassign options = { ...options, _width: 400, _height: 512, title: path }; } @@ -1706,12 +1717,12 @@ export namespace DocUtils { .filter(btnDoc => !btnDoc.hidden) .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title) - .map((dragDoc, i) => ({ + .map(dragDoc => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable((args: { x: number; y: number }) => { + event: undoable(() => { const newDoc = DocUtils.copyDragFactory(dragDoc); if (newDoc) { - newDoc.author = Doc.CurrentUserEmail; + newDoc.author = ClientUtils.CurrentUserEmail; newDoc.x = x; newDoc.y = y; EquationBox.SelectOnLoad = newDoc[Id]; @@ -1732,9 +1743,9 @@ export namespace DocUtils { !simpleMenu && ContextMenu.Instance.addItem({ description: 'Styled Notes', - subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({ + subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map(note => ({ description: ':' + StrCast(note.title), - event: undoable((args: { x: number; y: number }) => { + event: undoable(() => { const textDoc = Docs.Create.TextDocument('', { _width: 200, x, @@ -1757,12 +1768,12 @@ export namespace DocUtils { .filter(btnDoc => !btnDoc.hidden) .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title) - .map((dragDoc, i) => ({ + .map(dragDoc => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoable((args: { x: number; y: number }) => { + event: undoable(() => { const newDoc = DocUtils.delegateDragFactory(dragDoc); if (newDoc) { - newDoc.author = Doc.CurrentUserEmail; + newDoc.author = ClientUtils.CurrentUserEmail; newDoc.x = x; newDoc.y = y; EquationBox.SelectOnLoad = newDoc[Id]; @@ -1802,11 +1813,11 @@ export namespace DocUtils { } export function findTemplate(templateName: string, type: string, signature: string) { let docLayoutTemplate: Opt<Doc>; - const iconViews = DocListCast(Cast(Doc.UserDoc()['template_icons'], Doc, null)?.data); - const templBtns = DocListCast(Cast(Doc.UserDoc()['template_buttons'], Doc, null)?.data); - const noteTypes = DocListCast(Cast(Doc.UserDoc()['template_notes'], Doc, null)?.data); - const userTypes = DocListCast(Cast(Doc.UserDoc()['template_user'], Doc, null)?.data); - const clickFuncs = DocListCast(Cast(Doc.UserDoc()['template_clickFuncs'], Doc, null)?.data); + const 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) @@ -1816,13 +1827,20 @@ export namespace DocUtils { .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)); + !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); + 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), templateSignature); const customName = 'layout_' + templateSignature; @@ -1859,14 +1877,15 @@ export namespace DocUtils { } } export function iconify(doc: Doc) { - const layout_fieldKey = Cast(doc.layout_fieldKey, 'string', null); + const layoutFieldKey = Cast(doc.layout_fieldKey, 'string', null); DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined); - if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') doc.deiconifyLayout = layout_fieldKey.replace('layout_', ''); + 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((d, i) => { + 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; @@ -1880,6 +1899,7 @@ export namespace DocUtils { 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'); @@ -1921,7 +1941,7 @@ export namespace DocUtils { _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); Doc.AddDocToList(context, annotationField, pushpin); - const pushpinLink = DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, ''); + DocUtils.MakeLink(pushpin, doc, { link_relationship: 'pushpin' }, ''); doc._timecodeToShow = undefined; return pushpin; } @@ -1948,23 +1968,24 @@ export namespace DocUtils { // } function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) { - var dd = degrees + minutes / 60 + seconds / (60 * 60); + let dd = degrees + minutes / 60 + seconds / (60 * 60); if (direction === 'S' || direction === 'W') { - dd = dd * -1; + dd *= -1; } // Don't do anything for N or E return dd; } - export function assignImageInfo(result: Upload.FileInformation, proto: Doc) { + 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_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; @@ -2033,7 +2054,7 @@ export namespace DocUtils { const generatedDocuments: Doc[] = []; Networking.UploadYoutubeToServer(videoId, overwriteDoc?.[Id]).then(upfiles => { const { - source: { newFilename, originalFilename, mimetype }, + source: { newFilename, mimetype }, result, } = upfiles.lastElement(); if ((result as any).message) { @@ -2061,12 +2082,9 @@ export namespace DocUtils { const fileNoGuidPairs: Networking.FileGuidPair[] = files.map(file => ({ file })); const upfiles = await Networking.UploadFilesToServer(fileNoGuidPairs); - for (const { - source: { newFilename, mimetype }, - result, - } of upfiles) { + upfiles.forEach(({ source: { newFilename, mimetype }, result }) => { newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options); - } + }); return generatedDocuments; } @@ -2091,34 +2109,115 @@ export namespace DocUtils { 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); + 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(); + 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(); + var 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 link_anchor_1title = link.link_anchor_1 && link.link_anchor_1 !== link ? Cast(link.link_anchor_1, Doc, null)?.title : '<?>'; - const link_anchor_2title = link.link_anchor_2 && link.link_anchor_2 !== link ? Cast(link.link_anchor_2, Doc, null)?.title : '<?>'; + const 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 `${link_anchor_1title} (${relation}) ${link_anchor_2title}`; + return `${linkAnchor1title} (${relation}) ${linkAnchor2title}`; }); |