diff options
Diffstat (limited to 'src/client/documents/Documents.ts')
-rw-r--r-- | src/client/documents/Documents.ts | 173 |
1 files changed, 125 insertions, 48 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c9424a92e..2a6a4f60d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -49,6 +49,7 @@ 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 { PDFBox } from '../views/nodes/PDFBox'; import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox'; @@ -169,6 +170,7 @@ export class DocumentOptions { _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged. _hideContextMenu?: boolean; // whether the context menu can be shown _viewType?: string; // sub type of a collection + viewType?: string; // sub type of a collection _gridGap?: number; // gap between items in masonry view _viewScale?: number; // how much a freeform view has been scaled (zoomed) _overflow?: string; // set overflow behavior @@ -395,6 +397,7 @@ export namespace Docs { _yMargin: 10, nativeDimModifiable: true, treeViewGrowsHorizontally: true, + nativeHeightUnfrozen: true, forceReflow: true, links: '@links(self)', }, @@ -596,6 +599,7 @@ export namespace Docs { DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: defaultDataKey }, + options: { title: 'pres element template', _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' }, }, ], [ @@ -655,6 +659,13 @@ export namespace Docs { layout: { view: SchemaRowBox, dataField: defaultDataKey }, }, ], + [ + DocumentType.LOADING, + { + layout: { view: LoadingBox, dataField: '' }, + options: { _fitWidth: true, _fitHeight: true, nativeDimModifiable: true, links: '@links(self)' }, + }, + ], ]); const suffix = 'Proto'; @@ -793,7 +804,7 @@ 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) { + function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string, placeholderDoc?: Doc) { const viewKeys = ['x', 'y', 'system']; // 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, '^_'); @@ -804,21 +815,36 @@ export namespace Docs { dataProps.isPrototype = true; dataProps.author = Doc.CurrentUserEmail; dataProps.creationDate = new DateField(); - dataProps[`${fieldKey}-lastModified`] = new DateField(); + if (fieldKey) { + dataProps[`${fieldKey}-lastModified`] = new DateField(); + dataProps[fieldKey] = data; + + // so that the list of annotations is already initialised, prevents issues in addonly. + // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. + dataProps[fieldKey + '-annotations'] = new List<Doc>(); + dataProps[fieldKey + '-sidebar'] = new List<Doc>(); + } - dataProps[fieldKey] = data; + // users placeholderDoc as proto if it exists + const dataDoc = Doc.assign(placeholderDoc ? Doc.GetProto(placeholderDoc) : Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); - // so that the list of annotations is already initialised, prevents issues in addonly. - // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. - dataProps[fieldKey + '-annotations'] = new List<Doc>(); - dataProps[fieldKey + '-sidebar'] = new List<Doc>(); - const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); + if (placeholderDoc) { + dataDoc.proto = proto; + } const viewFirstProps: { [id: string]: any } = {}; viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; viewFirstProps['acl-Override'] = 'None'; viewFirstProps.author = Doc.CurrentUserEmail; - const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); + let viewDoc: Doc; + // determines whether viewDoc should be created using placeholder Doc or default + if (placeholderDoc) { + placeholderDoc._height = options._height !== undefined ? Number(options._height) : undefined; + placeholderDoc._width = options._width !== undefined ? Number(options._width) : undefined; + viewDoc = Doc.assign(placeholderDoc, viewFirstProps, true, true); + } else { + viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); + } Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); @@ -830,12 +856,13 @@ export namespace Docs { updateCachedAcls(dataDoc); updateCachedAcls(viewDoc); + return viewDoc; } - export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}) { + export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}, overwriteDoc?: Doc) { const imgField = url instanceof ImageField ? url : new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }, undefined, undefined, undefined, overwriteDoc); } export function PresDocument(options: DocumentOptions = {}) { @@ -846,12 +873,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } - export function VideoDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options); + 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 = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(url), options); + 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 = {}) { @@ -866,8 +893,16 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), '', options); } - export function AudioDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }); + export function AudioDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { + return InstanceFromProto( + Prototypes.get(DocumentType.AUDIO), + new AudioField(url), + { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }, + undefined, + undefined, + undefined, + overwriteDoc + ); } export function RecordingDocument(url: string, options: DocumentOptions = {}) { @@ -881,6 +916,9 @@ export namespace Docs { export function ColorDocument(options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } + 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, ''); + } export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); @@ -971,13 +1009,13 @@ export namespace Docs { return I; } - export function PdfDocument(url: string, options: DocumentOptions = {}) { + export function PdfDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { const width = options._width || undefined; const height = options._height || undefined; 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.PDF), new PdfField(url), options); + return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options, undefined, undefined, undefined, overwriteDoc); } export function WebDocument(url: string, options: DocumentOptions = {}) { @@ -1104,12 +1142,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.FILTER), undefined, { ...(options || {}) }); } - export function PresElementBoxDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); - } - - export function SchemaRowDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.SCHEMAROW), undefined, { ...(options || {}) }); + export function PresElementBoxDocument() { + return Prototypes.get(DocumentType.PRESELEMENT); } export function DataVizDocument(url: string, options?: DocumentOptions) { @@ -1117,7 +1151,7 @@ export namespace Docs { } export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, viewType: CollectionViewType.Docking, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { @@ -1281,7 +1315,7 @@ export namespace DocUtils { }); } - export function DefaultFocus(doc: Doc, options?: DocFocusOptions) { + export function DefaultFocus(doc: Doc, options: DocFocusOptions) { options?.afterFocus?.(false); } @@ -1362,11 +1396,17 @@ export namespace DocUtils { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript( - scripts[key], - { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', thisContainer: Doc.name, documentView: Doc.name, heading: Doc.name, checked: 'boolean', containingTreeView: Doc.name }, - { _readOnly_: true } - ); + doc[key] = ScriptField.MakeScript(scripts[key], { + dragData: DragManager.DocumentDragData.name, + value: 'any', + _readOnly_: 'boolean', + scriptContext: 'any', + thisContainer: Doc.name, + documentView: Doc.name, + heading: Doc.name, + checked: 'boolean', + containingTreeView: Doc.name, + }); } }); funcs && @@ -1444,8 +1484,16 @@ export namespace DocUtils { return created; } - export async function DocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> { - let ctor: ((path: string, options: DocumentOptions) => Doc | Promise<Doc | undefined>) | undefined = undefined; + /** + * + * @param type the type of file. + * @param path the path to the file. + * @param options the document options. + * @param overwriteDoc the placeholder loading doc. + * @returns + */ + export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> { + let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined = undefined; if (type.indexOf('image') !== -1) { ctor = Docs.Create.ImageDocument; if (!options._width) options._width = 300; @@ -1494,7 +1542,7 @@ export namespace DocUtils { options = { ...options, _width: 400, _height: 512, title: path }; } - return ctor ? ctor(path, options) : undefined; + return ctor ? ctor(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 { @@ -1525,7 +1573,7 @@ export namespace DocUtils { 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().emptyPresentation) + .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail) .map((dragDoc, i) => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), event: undoBatch((args: { x: number; y: number }) => { @@ -1710,14 +1758,14 @@ export namespace DocUtils { return dd; } - async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions) { + async function processFileupload(generatedDocuments: Doc[], name: string, type: string, result: Error | Upload.FileInformation, options: DocumentOptions, rootDoc?: Doc) { if (result instanceof Error) { alert(`Upload failed: ${result.message}`); return; } const full = { ...options, _width: 400, title: name }; const pathname = Utils.prepend(result.accessPaths.agnostic.client); - const doc = await DocUtils.DocumentFromType(type, pathname, full); + const doc = await DocUtils.DocumentFromType(type, pathname, full, rootDoc); if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; @@ -1746,6 +1794,12 @@ export namespace DocUtils { proto.lng = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); } } + if (Upload.isVideoInformation(result)) { + proto['data-duration'] = result.duration; + } + if (rootDoc) { + Doc.removeCurrentlyLoading(rootDoc); + } generatedDocuments.push(doc); } } @@ -1774,16 +1828,23 @@ export namespace DocUtils { return tbox; } - export async function uploadYoutubeVideo(videoId: string, options: DocumentOptions) { + export function uploadYoutubeVideoLoading(videoId: string, options: DocumentOptions, overwriteDoc?: Doc) { const generatedDocuments: Doc[] = []; - for (const { - source: { name, type }, - result, - } of await Networking.UploadYoutubeToServer(videoId)) { - name && type && processFileupload(generatedDocuments, name, type, result, options); - } - return generatedDocuments; + Networking.UploadYoutubeToServer(videoId).then(upfiles => { + const { + source: { name, type }, + result, + } = upfiles.lastElement(); + if ((result as any).message) { + if (overwriteDoc) { + overwriteDoc.isLoading = false; + overwriteDoc.loadingError = (result as any).message; + Doc.removeCurrentlyLoading(overwriteDoc); + } + } else name && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + }); } + export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { const generatedDocuments: Doc[] = []; const upfiles = await Networking.UploadFilesToServer(files); @@ -1796,6 +1857,22 @@ export namespace DocUtils { return generatedDocuments; } + export function uploadFileToDoc(file: File, options: DocumentOptions, overwriteDoc: Doc) { + const generatedDocuments: Doc[] = []; + Networking.UploadFilesToServer([file]).then(upfiles => { + const { + source: { name, type }, + result, + } = upfiles.lastElement() ?? { source: { name: '', type: '' }, result: { message: 'upload failed' } }; + if ((result as any).message) { + if (overwriteDoc) { + overwriteDoc.loadingError = (result as any).message; + Doc.removeCurrentlyLoading(overwriteDoc); + } + } else name && type && processFileupload(generatedDocuments, name, type, result, options, overwriteDoc); + }); + } + // copies the specified drag factory document export function copyDragFactory(dragFactory: Doc) { if (!dragFactory) return undefined; @@ -1832,8 +1909,8 @@ ScriptingGlobals.add(function makeDelegate(proto: any) { return d; }); ScriptingGlobals.add(function generateLinkTitle(self: Doc) { - const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : '<?>'; - const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : '<?>'; + const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null)?.title : '<?>'; + const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null)?.title : '<?>'; const relation = self.linkRelationship || 'to'; return `${anchor1title} (${relation}) ${anchor2title}`; }); |