From 8d88e7418ec0fb2a4afe71417fa1eb34c1e1060f Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Wed, 29 Jun 2022 21:04:20 -0700 Subject: Prettier and eslint rules added! --- .../views/nodes/button/textButton/TextButton.tsx | 23 +++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx index e18590a95..5d7d55863 100644 --- a/src/client/views/nodes/button/textButton/TextButton.tsx +++ b/src/client/views/nodes/button/textButton/TextButton.tsx @@ -9,9 +9,22 @@ export class TextButton extends Component { // Determine the type of toggle button const buttonText: boolean = BoolCast(this.props.rootDoc.switchToggle); - return (
- - {this.props.label} -
); + return ( +
+ + {this.props.label} +
+ ); } -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From a01f3541a40f119dbfd73236dbd5f99de9d78ff7 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Thu, 30 Jun 2022 10:16:41 -0700 Subject: added .prettierignore file Note that .prettierignore is formatted the same way as .gitignore --- .prettierignore | 0 .prettierrc.json | 8 +- src/client/util/CurrentUserUtils.ts | 2735 ++++++++++++++++++++++++++--------- 3 files changed, 2089 insertions(+), 654 deletions(-) create mode 100644 .prettierignore (limited to 'src') diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..e69de29bb diff --git a/.prettierrc.json b/.prettierrc.json index f91488470..e6983783b 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,6 +1,6 @@ { - "trailingComma": "es5", - "tabWidth": 4, - "semi": true, - "singleQuote": true + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": true } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2a0702a58..a7b24c426 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,43 +1,61 @@ -import { computed, observable, reaction } from "mobx"; +import { computed, observable, reaction } from 'mobx'; import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { InkTool } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { PrefetchProxy } from "../../fields/Proxy"; -import { RichTextField } from "../../fields/RichTextField"; -import { listSpec } from "../../fields/Schema"; -import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types"; -import { ImageField, nullAudio } from "../../fields/URLField"; -import { SharingPermissions } from "../../fields/util"; -import { OmitKeys, Utils } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; -import { DocumentType } from "../documents/DocumentTypes"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; -import { TreeViewType } from "../views/collections/CollectionTreeView"; -import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; -import { TreeView } from "../views/collections/TreeView"; -import { Colors } from "../views/global/globalEnums"; -import { MainView } from "../views/MainView"; -import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; -import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { OverlayView } from "../views/OverlayView"; -import { DocumentManager } from "./DocumentManager"; -import { DragManager } from "./DragManager"; -import { makeTemplate, MakeTemplate } from "./DropConverter"; -import { HistoryUtil } from "./History"; -import { LinkManager } from "./LinkManager"; -import { ScriptingGlobals } from "./ScriptingGlobals"; -import { SearchUtil } from "./SearchUtil"; -import { SelectionManager } from "./SelectionManager"; -import { ColorScheme } from "./SettingsManager"; -import { SharingManager } from "./SharingManager"; -import { SnappingManager } from "./SnappingManager"; -import { UndoManager } from "./UndoManager"; +import { + DataSym, + Doc, + DocListCast, + DocListCastAsync, + Opt, +} from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { InkTool } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { PrefetchProxy } from '../../fields/Proxy'; +import { RichTextField } from '../../fields/RichTextField'; +import { listSpec } from '../../fields/Schema'; +import { ComputedField, ScriptField } from '../../fields/ScriptField'; +import { + Cast, + DateCast, + DocCast, + FieldValue, + NumCast, + PromiseValue, + ScriptCast, + StrCast, +} from '../../fields/Types'; +import { ImageField, nullAudio } from '../../fields/URLField'; +import { SharingPermissions } from '../../fields/util'; +import { OmitKeys, Utils } from '../../Utils'; +import { DocServer } from '../DocServer'; +import { Docs, DocumentOptions, DocUtils, FInfo } from '../documents/Documents'; +import { DocumentType } from '../documents/DocumentTypes'; +import { CollectionDockingView } from '../views/collections/CollectionDockingView'; +import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; +import { TreeViewType } from '../views/collections/CollectionTreeView'; +import { + CollectionView, + CollectionViewType, +} from '../views/collections/CollectionView'; +import { TreeView } from '../views/collections/TreeView'; +import { Colors } from '../views/global/globalEnums'; +import { MainView } from '../views/MainView'; +import { ButtonType, NumButtonType } from '../views/nodes/button/FontIconBox'; +import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { OverlayView } from '../views/OverlayView'; +import { DocumentManager } from './DocumentManager'; +import { DragManager } from './DragManager'; +import { makeTemplate, MakeTemplate } from './DropConverter'; +import { HistoryUtil } from './History'; +import { LinkManager } from './LinkManager'; +import { ScriptingGlobals } from './ScriptingGlobals'; +import { SearchUtil } from './SearchUtil'; +import { SelectionManager } from './SelectionManager'; +import { ColorScheme } from './SettingsManager'; +import { SharingManager } from './SharingManager'; +import { SnappingManager } from './SnappingManager'; +import { UndoManager } from './UndoManager'; interface Button { // DocumentOptions fields a button can set @@ -55,21 +73,29 @@ interface Button { buttonText?: string; // fields that do not correspond to DocumentOption fields - scripts?: { script?: string; onClick?: string; } - funcs?: { [key:string]: string }; + scripts?: { script?: string; onClick?: string }; + funcs?: { [key: string]: string }; subMenu?: Button[]; } -export let resolvedPorts: { server: number, socket: number }; +export let resolvedPorts: { server: number; socket: number }; export class CurrentUserUtils { private static curr_id: string; //TODO tfs: these should be temporary... private static mainDocId: string | undefined; - public static get id() { return this.curr_id; } - public static get MainDocId() { return this.mainDocId; } - public static set MainDocId(id: string | undefined) { this.mainDocId = id; } - @computed public static get UserDocument() { return Doc.UserDoc(); } + public static get id() { + return this.curr_id; + } + public static get MainDocId() { + return this.mainDocId; + } + public static set MainDocId(id: string | undefined) { + this.mainDocId = id; + } + @computed public static get UserDocument() { + return Doc.UserDoc(); + } @observable public static GuestTarget: Doc | undefined; @observable public static GuestDashboard: Doc | undefined; @@ -78,151 +104,393 @@ export class CurrentUserUtils { @observable public static headerBarHeight: number = 0; @observable public static searchPanelWidth: number = 0; - static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) { - 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", documentView:Doc.name}, {"_readOnly_": true}); - } - }); - funcs && Object.keys(funcs).map(key => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) { - doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); - } - }); + static AssignScripts( + doc: Doc, + scripts?: { [key: string]: string }, + funcs?: { [key: string]: string } + ) { + 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', + documentView: Doc.name, + }, + { _readOnly_: true } + ); + } + }); + funcs && + Object.keys(funcs).map((key) => { + const cfield = ComputedField.WithoutComputed(() => + FieldValue(doc[key]) + ); + if ( + ScriptCast(cfield)?.script.originalScript !== funcs[key] && + funcs[key] + ) { + doc[key] = ComputedField.MakeFunction( + funcs[key], + { dragData: DragManager.DocumentDragData.name }, + { _readOnly_: true } + ); + } + }); return doc; } - static AssignOpts(doc:Doc|undefined, reqdOpts:DocumentOptions, items?:Doc[]) { + static 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)); + 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]])) { + }; + 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 => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), "data", item)); + items?.forEach( + (item) => + !DocListCast(doc.data).includes(item) && + Doc.AddDocToList(Doc.GetProto(doc), 'data', item) + ); + items && + DocListCast(doc.data).forEach( + (item) => + !items.includes(item) && + Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item) + ); } return doc; } - static AssignDocField(doc:Doc, field:string, creator:(reqdOpts:DocumentOptions, items?:Doc[]) => Doc, reqdOpts:DocumentOptions, items?: Doc[], scripts?:{[key:string]:string}, funcs?:{[key:string]:string}) { - return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); + static AssignDocField( + doc: Doc, + field: string, + creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, + reqdOpts: DocumentOptions, + items?: Doc[], + scripts?: { [key: string]: string }, + funcs?: { [key: string]: string } + ) { + return this.AssignScripts( + this.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? + (doc[field] = creator(reqdOpts, items)), + scripts, + funcs + ); } // initializes experimental advanced template views - slideView, headerView - static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) { - const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [ + static setupExperimentalTemplateButtons(doc: Doc, tempDocs?: Doc) { + const requiredTypeNameFields: { + btnOpts: DocumentOptions; + templateOpts: DocumentOptions; + template: (opts: DocumentOptions) => Doc; + }[] = [ { - btnOpts: { title: "slide", icon: "address-card" }, - templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true }, - template: (opts:DocumentOptions) => Docs.Create.MultirowDocument( - [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _fitWidth:true, _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) - ], opts) + btnOpts: { title: 'slide', icon: 'address-card' }, + templateOpts: { + _width: 400, + _height: 300, + title: 'slideView', + childDocumentsActive: true, + _xMargin: 3, + _yMargin: 3, + system: true, + }, + template: (opts: DocumentOptions) => + Docs.Create.MultirowDocument( + [ + Docs.Create.MulticolumnDocument([], { + title: 'data', + _height: 200, + system: true, + }), + Docs.Create.TextDocument('', { + title: 'text', + _fitWidth: true, + _height: 100, + system: true, + _fontFamily: StrCast(Doc.UserDoc()._fontFamily), + _fontSize: StrCast(Doc.UserDoc()._fontSize), + }), + ], + opts + ), }, { - btnOpts: { title: "mobile", icon: "mobile" }, - templateOpts: { title: "NEW MOBILE BUTTON", onClick: undefined, }, - template: (opts:DocumentOptions) => this.mobileButton(opts, - [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) - ] - ) + btnOpts: { title: 'mobile', icon: 'mobile' }, + templateOpts: { + title: 'NEW MOBILE BUTTON', + onClick: undefined, + }, + template: (opts: DocumentOptions) => + this.mobileButton(opts, [ + this.createToolButton({ + ignoreClick: true, + icon: 'mobile', + backgroundColor: 'transparent', + }), + this.mobileTextContainer({}, [ + this.mobileButtonText({}, 'NEW MOBILE BUTTON'), + this.mobileButtonInfo( + {}, + 'You can customize this button and make it your own.' + ), + ]), + ]), }, ]; - const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => { - const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title); - const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' }; - const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { - if (templateBtn) { - this.AssignOpts(templateBtn,btnOpts); - this.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions); - } - return templateBtn; - }; - const makeTemp = (doc:Doc) => { - doc.isTemplateDoc = makeTemplate(doc); - return doc; + const requiredTypes = requiredTypeNameFields.map( + ({ btnOpts, template, templateOpts }) => { + const tempBtn = DocListCast(tempDocs?.data)?.find( + (doc) => doc.title === btnOpts.title + ); + const reqdScripts = { + onDragStart: + '{ return copyDragFactory(this.dragFactory); }', + }; + const assignBtnAndTempOpts = ( + templateBtn: Opt, + btnOpts: DocumentOptions, + templateOptions: DocumentOptions + ) => { + if (templateBtn) { + this.AssignOpts(templateBtn, btnOpts); + this.AssignDocField( + templateBtn, + 'dragFactory', + (opts) => template(opts), + templateOptions + ); + } + return templateBtn; + }; + const makeTemp = (doc: Doc) => { + doc.isTemplateDoc = makeTemplate(doc); + return doc; + }; + return this.AssignScripts( + assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? + this.createToolButton({ + ...btnOpts, + dragFactory: makeTemp(template(templateOpts)), + }), + reqdScripts + ); } - return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); - }); + ); - const reqdOpts:DocumentOptions = { - title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, + const reqdOpts: DocumentOptions = { + title: 'Experimental Tools', + _xMargin: 0, + _showTitle: 'title', + _chromeHidden: true, + _stayInCollection: true, + _hideContextMenu: true, + _forceActive: true, + system: true, + childDocumentsActive: true, + _autoHeight: true, + _width: 500, + _height: 300, + _fitWidth: true, + _columnWidth: 35, + ignoreClick: true, + _lockedPosition: true, }; - const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; - const reqdFuncs = { hidden: "IsNoviceMode()" }; - return this.AssignScripts(this.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs); + const reqdScripts = { dropConverter: 'convertToButtons(dragData)' }; + const reqdFuncs = { hidden: 'IsNoviceMode()' }; + return this.AssignScripts( + this.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? + Docs.Create.MasonryDocument(requiredTypes, reqdOpts), + reqdScripts, + reqdFuncs + ); } /// Initializes templates that can be applied to notes - static setupNoteTemplates(doc: Doc, field="template-notes") { + static setupNoteTemplates(doc: Doc, field = 'template-notes') { const tempNotes = DocCast(doc[field]); - const reqdTempOpts:DocumentOptions[] = [ - { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"}, - { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" }, - { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; - const reqdNoteList = reqdTempOpts.map(opts => { - const reqdOpts = {...opts, title: "text", system: true}; - const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined; - const makeTemp = (doc:Doc, noteType?:string) => { - doc.isTemplateDoc = makeTemplate(doc, true, noteType??"Note"); + const reqdTempOpts: DocumentOptions[] = [ + { + noteType: 'Note', + backgroundColor: 'yellow', + icon: 'sticky-note', + }, + { noteType: 'Idea', backgroundColor: 'pink', icon: 'lightbulb' }, + { + noteType: 'Topic', + backgroundColor: 'lightblue', + icon: 'book-open', + }, + ]; + const reqdNoteList = reqdTempOpts.map((opts) => { + const reqdOpts = { ...opts, title: 'text', system: true }; + const noteType = tempNotes + ? DocListCast(tempNotes.data).find( + (doc) => doc.noteType === opts.noteType + ) + : undefined; + const makeTemp = (doc: Doc, noteType?: string) => { + doc.isTemplateDoc = makeTemplate(doc, true, noteType ?? 'Note'); return doc; - } - return this.AssignOpts(noteType, reqdOpts) ?? makeTemp(Docs.Create.TextDocument("",reqdOpts), opts.noteType); + }; + return ( + this.AssignOpts(noteType, reqdOpts) ?? + makeTemp(Docs.Create.TextDocument('', reqdOpts), opts.noteType) + ); }); - const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, system: true }; - return this.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); + const reqdOpts: DocumentOptions = { + title: 'Note Layouts', + _height: 75, + system: true, + }; + return ( + this.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? + (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)) + ); } /// Initializes collection of templates for notes and click functions - static setupDocTemplates(doc: Doc, field="myTemplates") { - this.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"}); + static setupDocTemplates(doc: Doc, field = 'myTemplates') { + this.AssignDocField( + doc, + 'presElement', + (opts) => Docs.Create.PresElementBoxDocument(opts), + { + title: 'pres element template', + type: DocumentType.PRESELEMENT, + _fitWidth: true, + _xMargin: 0, + isTemplateDoc: true, + isTemplateForField: 'data', + } + ); const templates = [ DocCast(doc.presElement), CurrentUserUtils.setupNoteTemplates(doc), - CurrentUserUtils.setupClickEditorTemplates(doc) + CurrentUserUtils.setupClickEditorTemplates(doc), ]; - const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; - const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; - return this.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts); + const reqdOpts = { + title: 'template layouts', + _xMargin: 0, + system: true, + }; + const reqdScripts = { dropConverter: 'convertToButtons(dragData)' }; + return this.AssignDocField( + doc, + field, + (opts, items) => Docs.Create.TreeDocument(items ?? [], opts), + reqdOpts, + templates, + reqdScripts + ); } // setup templates for different document types when they are iconified from Document Decorations - static setupDefaultIconTemplates(doc: Doc, field="template-icons") { - const reqdOpts = { title: "icon templates", _height: 75, system: true }; - const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); - - const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => { - const iconFieldName = "icon" + (type ? "_" + type : ""); + static setupDefaultIconTemplates(doc: Doc, field = 'template-icons') { + const reqdOpts = { title: 'icon templates', _height: 75, system: true }; + const templateIconsDoc = + this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? + (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + + const makeIconTemplate = ( + type: DocumentType | undefined, + templateField: string, + opts: DocumentOptions + ) => { + const iconFieldName = 'icon' + (type ? '_' + type : ''); const curIcon = DocCast(templateIconsDoc[iconFieldName]); let creator = labelBox; switch (opts.iconTemplate) { - case DocumentType.IMG : creator = imageBox; break; - case DocumentType.FONTICON: creator = fontBox; break; + case DocumentType.IMG: + creator = imageBox; + break; + case DocumentType.FONTICON: + creator = fontBox; + break; } - const allopts = {system: true, ...opts}; - return this.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? - this.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), - {onClick:"deiconifyView(documentView)"}); + const allopts = { system: true, ...opts }; + return this.AssignScripts( + (curIcon?.iconTemplate === opts.iconTemplate + ? this.AssignOpts(curIcon, allopts) + : undefined) ?? + (templateIconsDoc[iconFieldName] = MakeTemplate( + creator(allopts), + true, + iconFieldName, + templateField + )), + { onClick: 'deiconifyView(documentView)' } + ); }; - const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts - }); - const imageBox = (opts: DocumentOptions, url?:string) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", ...opts }); - const fontBox = (opts:DocumentOptions, data?:string) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); + const labelBox = (opts: DocumentOptions, data?: string) => + Docs.Create.LabelDocument({ + textTransform: 'unset', + letterSpacing: 'unset', + _singleLine: false, + _minFontSize: 14, + _maxFontSize: 24, + borderRounding: '5px', + _width: 150, + _height: 70, + _xPadding: 10, + _yPadding: 10, + ...opts, + }); + const imageBox = (opts: DocumentOptions, url?: string) => + Docs.Create.ImageDocument( + url ?? 'http://www.cs.brown.edu/~bcz/noImage.png', + { + 'icon-nativeWidth': 360 / 4, + 'icon-nativeHeight': 270 / 4, + iconTemplate: DocumentType.IMG, + _width: 360 / 4, + _height: 270 / 4, + _showTitle: 'title', + ...opts, + } + ); + const fontBox = (opts: DocumentOptions, data?: string) => + Docs.Create.FontIconDocument({ + _nativeHeight: 30, + _nativeWidth: 30, + _width: 30, + _height: 30, + ...opts, + }); + // prettier-ignore const iconTemplates = [ makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}), makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}), @@ -242,48 +510,88 @@ export class CurrentUserUtils { /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyPresentation static creatorBtnDescriptors(doc: Doc): { - title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, - backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, + title: string; + toolTip: string; + icon: string; + ignoreClick?: boolean; + dragFactory?: Doc; + backgroundColor?: string; + clickFactory?: Doc; + scripts?: { onClick?: string; onDragStart?: string }; + funcs?: { onDragStart?: string; hidden?: string }; }[] { - const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); + const standardOps = (key: string) => ({ + title: 'Untitled ' + key, + _fitWidth: true, + system: true, + 'dragFactory-count': 0, + cloneFieldFilter: new List(['system']), + }); const json = { doc: { - type: "doc", + type: 'doc', content: [ { - type: "paragraph", attrs: {}, content: [{ - type: "dashField", - attrs: { fieldKey: "author", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }, { - type: "dashField", - attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }] - }] + type: 'paragraph', + attrs: {}, + content: [ + { + type: 'dashField', + attrs: { + fieldKey: 'author', + docid: '', + hideKey: false, + }, + marks: [{ type: 'strong' }], + }, + { + type: 'dashField', + attrs: { + fieldKey: 'creationDate', + docid: '', + hideKey: false, + }, + marks: [{ type: 'strong' }], + }, + ], + }, + ], }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] + selection: { type: 'text', anchor: 1, head: 1 }, + storedMarks: [], }; const headerBtnHgt = 10; - const headerTemplate = (opts:DocumentOptions) => { - const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", - layout: - "" + - ` ` + - " " + - ` Metadata` + - "" - }, "header"); + const headerTemplate = (opts: DocumentOptions) => { + const header = Docs.Create.RTFDocument( + new RichTextField(JSON.stringify(json), ''), + { + ...opts, + title: 'text', + layout: + "" + + ` ` + + " " + + ` Metadata` + + '', + }, + 'header' + ); // "
" + // " " + // " " + // "
"; - Doc.GetProto(header).isTemplateDoc = makeTemplate(Doc.GetProto(header), true, "headerView"); - Doc.GetProto(header).title = "Untitled Header"; - return header; - } + Doc.GetProto(header).isTemplateDoc = makeTemplate( + Doc.GetProto(header), + true, + 'headerView' + ); + Doc.GetProto(header).title = 'Untitled Header'; + return header; + }; + // prettier-ignore const emptyThings:{key:string, // the field name where the empty thing will be stored opts:DocumentOptions, // the document options that are required for the empty thing funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing @@ -311,8 +619,18 @@ export class CurrentUserUtils { }, funcs: {title: 'self.text?.Text'}}, ]; - emptyThings.forEach(thing => this.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs)); - + emptyThings.forEach((thing) => + this.AssignDocField( + doc, + 'empty' + thing.key, + (opts) => thing.creator(opts), + { ...standardOps(thing.key), ...thing.opts }, + undefined, + undefined, + thing.funcs + ) + ); + // prettier-ignore return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, }, { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab), scripts: { onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, @@ -332,29 +650,82 @@ export class CurrentUserUtils { } /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools - static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc { - const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { - const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; - const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, - _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias", - btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, - _removeDropProperties: new List(["_stayInCollection"]), + static setupCreatorButtons(doc: Doc, dragCreatorDoc?: Doc): Doc { + const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map( + (reqdOpts) => { + const btn = dragCreatorDoc + ? DocListCast(dragCreatorDoc.data).find( + (doc) => doc.title === reqdOpts.title + ) + : undefined; + const opts: DocumentOptions = { + ...OmitKeys(reqdOpts, [ + 'funcs', + 'scripts', + 'backgroundColor', + ]).omit, + _nativeWidth: 50, + _nativeHeight: 50, + _width: 35, + _height: 35, + _hideContextMenu: true, + _stayInCollection: true, + _dropAction: 'alias', + btnType: ButtonType.ToolButton, + backgroundColor: + reqdOpts.backgroundColor ?? Colors.DARK_GRAY, + color: Colors.WHITE, + system: true, + _removeDropProperties: new List([ + '_stayInCollection', + ]), }; - return this.AssignScripts(this.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs); - }); + return this.AssignScripts( + this.AssignOpts(btn, opts) ?? + Docs.Create.FontIconDocument(opts), + reqdOpts.scripts, + reqdOpts.funcs + ); + } + ); - const reqdOpts:DocumentOptions = { - title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, - childDocumentsActive: true + const reqdOpts: DocumentOptions = { + title: 'Basic Item Creators', + _showTitle: 'title', + _xMargin: 0, + _stayInCollection: true, + _hideContextMenu: true, + _chromeHidden: true, + system: true, + _autoHeight: true, + _width: 500, + _height: 300, + _fitWidth: true, + _columnWidth: 40, + ignoreClick: true, + _lockedPosition: true, + _forceActive: true, + childDocumentsActive: true, }; - const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; - return this.AssignScripts(this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); + const reqdScripts = { dropConverter: 'convertToButtons(dragData)' }; + return this.AssignScripts( + this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? + Docs.Create.MasonryDocument(creatorBtns, reqdOpts), + reqdScripts + ); } /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents - static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}}[] { - const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; + static leftSidebarMenuBtnDescriptions(doc: Doc): { + title: string; + target: Doc; + icon: string; + scripts: { [key: string]: any }; + funcs?: { [key: string]: any }; + }[] { + const badgeValue = + "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; + // prettier-ignore return [ { title: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", }, { title: "Search", target: this.setupSearcher(doc, "mySearcher"), icon: "search", }, @@ -369,40 +740,110 @@ export class CurrentUserUtils { } /// the empty panel that is filled with whichever left menu button's panel has been selected - static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { - this.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.system = true; return doc;})(new Doc()), {system:true}); + static setupLeftSidebarPanel(doc: Doc, field = 'myLeftSidebarPanel') { + this.AssignDocField( + doc, + field, + (opts) => + ((doc: Doc) => { + doc.system = true; + return doc; + })(new Doc()), + { system: true } + ); } /// Initializes the left sidebar menu buttons and the panels they open up - static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") { + static setupLeftSidebarMenu(doc: Doc, field = 'myLeftSidebarMenu') { this.setupLeftSidebarPanel(doc); const myLeftSidebarMenu = DocCast(doc[field]); - const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => { - const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; - const reqdBtnOpts:DocumentOptions = { - title, icon, target, btnType: ButtonType.MenuButton, system: true, dontUndo: true, dontRegisterView: true, - _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions( + doc + ).map(({ title, target, icon, scripts, funcs }) => { + const btnDoc = myLeftSidebarMenu + ? DocListCast(myLeftSidebarMenu.data).find( + (doc) => doc.title === title + ) + : undefined; + const reqdBtnOpts: DocumentOptions = { + title, + icon, + target, + btnType: ButtonType.MenuButton, + system: true, + dontUndo: true, + dontRegisterView: true, + _width: 60, + _height: 60, + _stayInCollection: true, + _hideContextMenu: true, + _chromeHidden: true, + _dropAction: 'alias', + _removeDropProperties: new List([ + 'dropAction', + '_stayInCollection', + ]), }; - return this.AssignScripts(this.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs); + return this.AssignScripts( + this.AssignOpts(btnDoc, reqdBtnOpts) ?? + Docs.Create.FontIconDocument(reqdBtnOpts), + scripts, + funcs + ); }); - const reqdStackOpts:DocumentOptions ={ - title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, - _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true + const reqdStackOpts: DocumentOptions = { + title: 'menuItemPanel', + childDropAction: 'alias', + backgroundColor: Colors.DARK_GRAY, + boxShadow: 'rgba(0,0,0,0)', + dontRegisterView: true, + ignoreClick: true, + _chromeHidden: true, + _gridGap: 0, + _yMargin: 0, + _yPadding: 0, + _xMargin: 0, + _autoHeight: false, + _width: 60, + _columnWidth: 60, + _lockedPosition: true, + system: true, }; - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); + return this.AssignDocField( + doc, + field, + (opts, items) => Docs.Create.StackingDocument(items ?? [], opts), + reqdStackOpts, + menuBtns, + { dropConverter: 'convertToButtons(dragData)' } + ); } // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu - static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") { - const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,}; - this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts); + static setupActiveMobileMenu(doc: Doc, field = 'activeMobileMenu') { + const reqdOpts = { + _width: 980, + ignoreClick: true, + _lockedPosition: false, + title: 'home', + _yMargin: 100, + system: true, + _chromeHidden: true, + }; + this.AssignDocField( + doc, + field, + (opts, items) => + Docs.Create.StackingDocument(this.setupMobileButtons(), opts), + reqdOpts + ); } // Sets up mobile buttons for inside mobile menu static setupMobileButtons(doc?: Doc, buttons?: string[]) { return []; + // prettier-ignore const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " }, { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." }, @@ -412,45 +853,97 @@ export class CurrentUserUtils { { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." } ]; // returns a list of mobile buttons - return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => - this.mobileButton({ - title: data.title, - _lockedPosition: true, - onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - backgroundColor: data.backgroundColor, system: true - }, - [this.createToolButton({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", system: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) - ); + return docProtoData + .filter((d) => !buttons || !buttons.includes(d.title)) + .map((data) => + this.mobileButton( + { + title: data.title, + _lockedPosition: true, + onClick: data.click + ? ScriptField.MakeScript(data.click) + : undefined, + backgroundColor: data.backgroundColor, + system: true, + }, + [ + this.createToolButton({ + ignoreClick: true, + icon: data.icon, + backgroundColor: 'rgba(0,0,0,0)', + system: true, + btnType: ButtonType.ClickButton, + }), + this.mobileTextContainer({}, [ + this.mobileButtonText({}, data.title), + this.mobileButtonInfo({}, data.info), + ]), + ] + ) + ); } // sets up the main document for the mobile button - static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, { - ...opts, - _removeDropProperties: new List(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, - borderRounding: "5px", boxShadow: "0 0", system: true - }) as any as Doc + static mobileButton = (opts: DocumentOptions, docs: Doc[]) => + Docs.Create.MulticolumnDocument(docs, { + ...opts, + _removeDropProperties: new List(['dropAction']), + _nativeWidth: 900, + _nativeHeight: 250, + _width: 900, + _height: 250, + _yMargin: 15, + borderRounding: '5px', + boxShadow: '0 0', + system: true, + }) as any as Doc; // sets up the text container for the information contained within the mobile button - static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, { - ...opts, - _removeDropProperties: new List(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, - backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", ignoreClick: true, system: true - }) as any as Doc + static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => + Docs.Create.MultirowDocument(docs, { + ...opts, + _removeDropProperties: new List(['dropAction']), + _nativeWidth: 450, + _nativeHeight: 250, + _width: 450, + _height: 250, + _yMargin: 25, + backgroundColor: 'rgba(0,0,0,0)', + borderRounding: '0', + boxShadow: '0 0', + ignoreClick: true, + system: true, + }) as any as Doc; // Sets up the title of the button - static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, { - ...opts, - title: buttonTitle, _fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", system: true - }) as any as Doc + static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => + Docs.Create.TextDocument(buttonTitle, { + ...opts, + title: buttonTitle, + _fontSize: '37px', + _xMargin: 0, + _yMargin: 0, + ignoreClick: true, + backgroundColor: 'rgba(0,0,0,0)', + system: true, + }) as any as Doc; // Sets up the description of the button - static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, { - ...opts, - title: "info", _fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, system: true - }) as any as Doc - + static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => + Docs.Create.TextDocument(buttonInfo, { + ...opts, + title: 'info', + _fontSize: '25px', + _xMargin: 0, + _yMargin: 0, + ignoreClick: true, + backgroundColor: 'rgba(0,0,0,0)', + _dimMagnitude: 2, + system: true, + }) as any as Doc; static setupThumbButtons(doc: Doc) { + // prettier-ignore const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" }, { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" }, @@ -458,29 +951,58 @@ export class CurrentUserUtils { { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" }, { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" }, ]; - return docProtoData.map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, - _dropAction: data.pointerDown ? "copy" : undefined, - ignoreClick: data.ignoreClick, - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, - clipboard: data.clipboard, - onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, - onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, - backgroundColor: data.backgroundColor, - _removeDropProperties: new List(["dropAction"]), - dragFactory: data.dragFactory, - system: true - })); + return docProtoData.map((data) => + Docs.Create.FontIconDocument({ + _nativeWidth: 10, + _nativeHeight: 10, + _width: 10, + _height: 10, + title: data.title, + icon: data.icon, + _dropAction: data.pointerDown ? 'copy' : undefined, + ignoreClick: data.ignoreClick, + onDragStart: data.drag + ? ScriptField.MakeFunction(data.drag) + : undefined, + clipboard: data.clipboard, + onPointerUp: data.pointerUp + ? ScriptField.MakeScript(data.pointerUp) + : undefined, + onPointerDown: data.pointerDown + ? ScriptField.MakeScript(data.pointerDown) + : undefined, + backgroundColor: data.backgroundColor, + _removeDropProperties: new List(['dropAction']), + dragFactory: data.dragFactory, + system: true, + }) + ); } static setupThumbDoc(userDoc: Doc) { if (!userDoc.thumbDoc) { - const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { - _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons", - _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true - }); + const thumbDoc = Docs.Create.LinearDocument( + CurrentUserUtils.setupThumbButtons(userDoc), + { + _width: 100, + _height: 50, + ignoreClick: true, + _lockedPosition: true, + title: 'buttons', + _autoHeight: true, + _yMargin: 5, + linearViewIsExpanded: true, + backgroundColor: 'white', + system: true, + } + ); thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { - _width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true + _width: 300, + _height: 25, + _autoHeight: true, + linearViewIsExpanded: true, + flexDirection: 'column', + system: true, }); userDoc.thumbDoc = thumbDoc; } @@ -488,154 +1010,451 @@ export class CurrentUserUtils { } static setupMobileInkingDoc(userDoc: Doc) { - return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", system: true }); + return Docs.Create.FreeformDocument([], { + title: 'Mobile Inking', + backgroundColor: 'white', + system: true, + }); } static setupMobileUploadDoc(userDoc: Doc) { // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" }) - const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", { - title: "Upload Images From the Web", _lockedPosition: true, system: true - }); + const webDoc = Docs.Create.WebDocument( + 'https://www.britannica.com/biography/Miles-Davis', + { + title: 'Upload Images From the Web', + _lockedPosition: true, + system: true, + } + ); const uploadDoc = Docs.Create.StackingDocument([], { - title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true, _chromeHidden: true, + title: 'Mobile Upload Collection', + backgroundColor: 'white', + _lockedPosition: true, + system: true, + _chromeHidden: true, }); return Docs.Create.StackingDocument([webDoc, uploadDoc], { - _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true, _chromeHidden: true, + _width: screen.width, + _lockedPosition: true, + title: 'Upload', + _autoHeight: true, + _yMargin: 80, + backgroundColor: 'lightgray', + system: true, + _chromeHidden: true, }); } /// Search option on the left side button panel - static setupSearcher(doc: Doc, field:string) { - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), { - dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", - _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, }); + static setupSearcher(doc: Doc, field: string) { + return this.AssignDocField( + doc, + field, + (opts, items) => Docs.Create.SearchDocument(opts), + { + dontRegisterView: true, + backgroundColor: 'dimgray', + ignoreClick: true, + title: 'Search Panel', + system: true, + childDropAction: 'alias', + _lockedPosition: true, + _viewType: CollectionViewType.Schema, + _searchDoc: true, + } + ); } /// Initializes the panel of draggable tools that is opened from the left sidebar. - static setupToolsBtnPanel(doc: Doc, field:string) { + static setupToolsBtnPanel(doc: Doc, field: string) { const myTools = DocCast(doc[field]); - const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined); - const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined); - const reqdToolOps:DocumentOptions = { - title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", - _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, + const creatorBtns = CurrentUserUtils.setupCreatorButtons( + doc, + DocListCast(myTools?.data)?.length + ? DocListCast(myTools.data)[0] + : undefined + ); + const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons( + doc, + DocListCast(myTools?.data)?.length > 1 + ? DocListCast(myTools.data)[1] + : undefined + ); + const reqdToolOps: DocumentOptions = { + title: 'My Tools', + system: true, + ignoreClick: true, + boxShadow: '0 0', + _showTitle: 'title', + _width: 500, + _yMargin: 20, + _lockedPosition: true, + _forceActive: true, + _stayInCollection: true, + _hideContextMenu: true, + _chromeHidden: true, }; - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]); + return this.AssignDocField( + doc, + field, + (opts, items) => Docs.Create.StackingDocument(items ?? [], opts), + reqdToolOps, + [creatorBtns, templateBtns] + ); } /// initializes the left sidebar dashboard pane - static setupDashboards(doc: Doc, field:string) { + static setupDashboards(doc: Doc, field: string) { var myDashboards = DocCast(doc[field]); const newDashboard = `createNewDashboard()`; - const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, - title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true }; - const reqdBtnScript = {onClick: newDashboard,} - const newDashboardButton = this.AssignScripts(this.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); - - const reqdOpts:DocumentOptions = { - title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true, - targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true, - buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias", - _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true, - contextMenuLabels: new List(["Create New Dashboard"]), - contextMenuIcons: new List(["plus"]), - childContextMenuLabels: new List(["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]),// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters - childContextMenuIcons: new List(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters - explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." + const reqdBtnOpts: DocumentOptions = { + _forceActive: true, + _width: 30, + _height: 30, + _stayInCollection: true, + _hideContextMenu: true, + title: 'new dashboard', + btnType: ButtonType.ClickButton, + toolTip: 'Create new dashboard', + buttonText: 'New trail', + icon: 'plus', + system: true, + }; + const reqdBtnScript = { onClick: newDashboard }; + const newDashboardButton = this.AssignScripts( + this.AssignOpts( + DocCast(myDashboards?.buttonMenuDoc), + reqdBtnOpts + ) ?? Docs.Create.FontIconDocument(reqdBtnOpts), + reqdBtnScript + ); + + const reqdOpts: DocumentOptions = { + title: 'My Dashboards', + childHideLinkButton: true, + freezeChildren: 'remove|add', + treeViewHideTitle: true, + boxShadow: '0 0', + childDontRegisterViews: true, + targetDropAction: 'same', + treeViewType: TreeViewType.fileSystem, + isFolder: true, + system: true, + treeViewTruncateTitleWidth: 150, + ignoreClick: true, + buttonMenu: true, + buttonMenuDoc: newDashboardButton, + childDropAction: 'alias', + _showTitle: 'title', + _height: 400, + _gridGap: 5, + _forceActive: true, + _lockedPosition: true, + contextMenuLabels: new List(['Create New Dashboard']), + contextMenuIcons: new List(['plus']), + childContextMenuLabels: new List([ + 'Toggle Dark Theme', + 'Toggle Comic Mode', + 'Snapshot Dashboard', + 'Share Dashboard', + 'Remove Dashboard', + ]), // entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters + childContextMenuIcons: new List([ + 'chalkboard', + 'tv', + 'camera', + 'users', + 'times', + ]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters + explainer: + 'This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files.', }; - myDashboards = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); + myDashboards = this.AssignDocField( + doc, + field, + (opts) => Docs.Create.TreeDocument([], opts), + reqdOpts + ); const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; const contextMenuScripts = [newDashboard]; - const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters - const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts - if (Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { - myDashboards.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); - } - if (Cast(myDashboards.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { - myDashboards.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); - } - if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) { - myDashboards.childContextMenuFilters = new List(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!)); + const childContextMenuScripts = [ + toggleDarkTheme, + `toggleComicMode()`, + `snapshotDashboard()`, + `shareDashboard(self)`, + 'removeDashboard(self)', + ]; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters + const childContextMenuFilters = [ + '!IsNoviceMode()', + '!IsNoviceMode()', + '!IsNoviceMode()', + undefined as any, + undefined as any, + ]; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts + if ( + Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null) + ?.length !== contextMenuScripts.length + ) { + myDashboards.contextMenuScripts = new List( + contextMenuScripts.map( + (script) => ScriptField.MakeFunction(script)! + ) + ); + } + if ( + Cast( + myDashboards.childContextMenuScripts, + listSpec(ScriptField), + null + )?.length !== childContextMenuScripts.length + ) { + myDashboards.childContextMenuScripts = new List( + childContextMenuScripts.map( + (script) => ScriptField.MakeFunction(script)! + ) + ); + } + if ( + Cast( + myDashboards.childContextMenuFilters, + listSpec(ScriptField), + null + )?.length !== childContextMenuFilters.length + ) { + myDashboards.childContextMenuFilters = new List( + childContextMenuFilters.map((script) => + !script ? script : ScriptField.MakeFunction(script)! + ) + ); } return myDashboards; } /// initializes the left sidebar Trails pane - static setupTrails(doc: Doc, field:string) { + static setupTrails(doc: Doc, field: string) { var myTrails = DocCast(doc[field]); - const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, - title: "New trail", toolTip: "Create new trail", btnType: ButtonType.ClickButton, buttonText: "New trail", icon: "plus", system: true }; - const reqdBtnScript = {onClick: `createNewPresentation()`}; - const newTrailButton = this.AssignScripts(this.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); - - const reqdOpts:DocumentOptions = { - title: "My Trails", _showTitle: "title", _height: 100, - treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton, - contextMenuIcons: new List(["plus"]), - contextMenuLabels: new List(["Create New Trail"]), - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, - explainer: "All of the trails that you have created will appear here." + const reqdBtnOpts: DocumentOptions = { + _forceActive: true, + _width: 30, + _height: 30, + _stayInCollection: true, + _hideContextMenu: true, + title: 'New trail', + toolTip: 'Create new trail', + btnType: ButtonType.ClickButton, + buttonText: 'New trail', + icon: 'plus', + system: true, }; - myTrails = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); + const reqdBtnScript = { onClick: `createNewPresentation()` }; + const newTrailButton = this.AssignScripts( + this.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? + Docs.Create.FontIconDocument(reqdBtnOpts), + reqdBtnScript + ); + + const reqdOpts: DocumentOptions = { + title: 'My Trails', + _showTitle: 'title', + _height: 100, + treeViewHideTitle: true, + _fitWidth: true, + _gridGap: 5, + _forceActive: true, + childDropAction: 'alias', + treeViewTruncateTitleWidth: 150, + ignoreClick: true, + buttonMenu: true, + buttonMenuDoc: newTrailButton, + contextMenuIcons: new List(['plus']), + contextMenuLabels: new List(['Create New Trail']), + _lockedPosition: true, + boxShadow: '0 0', + childDontRegisterViews: true, + targetDropAction: 'same', + system: true, + explainer: + 'All of the trails that you have created will appear here.', + }; + myTrails = this.AssignDocField( + doc, + field, + (opts) => Docs.Create.TreeDocument([], opts), + reqdOpts + ); const contextMenuScripts = [reqdBtnScript.onClick]; - if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { - myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); + if ( + Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null) + ?.length !== contextMenuScripts.length + ) { + myTrails.contextMenuScripts = new List( + contextMenuScripts.map( + (script) => ScriptField.MakeFunction(script)! + ) + ); } return myTrails; } /// initializes the left sidebar File system pane - static setupFilesystem(doc: Doc, field:string) { + static setupFilesystem(doc: Doc, field: string) { var myFilesystem = DocCast(doc[field]); - const myFileOrphans = this.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); - + const myFileOrphans = this.AssignDocField( + doc, + 'myFileOrphans', + (opts) => Docs.Create.TreeDocument([], opts), + { + title: 'Unfiled', + _stayInCollection: true, + system: true, + isFolder: true, + } + ); + const newFolder = `makeTopLevelFolder()`; const newFolderOpts: DocumentOptions = { - _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _width: 30, _height: 30, - title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", system: true + _forceActive: true, + _stayInCollection: true, + _hideContextMenu: true, + _width: 30, + _height: 30, + title: 'New folder', + btnType: ButtonType.ClickButton, + toolTip: 'Create new folder', + buttonText: 'New folder', + icon: 'folder-plus', + system: true, }; - const newFolderScript = { onClick: newFolder}; - const newFolderButton = this.AssignScripts(this.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); - - const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, - title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true, - isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, - treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias", - childContextMenuLabels: new List(["Create new folder"]), - childContextMenuIcons: new List(["plus"]), - explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." + const newFolderScript = { onClick: newFolder }; + const newFolderButton = this.AssignScripts( + this.AssignOpts( + DocCast(myFilesystem?.buttonMenuDoc), + newFolderOpts + ) ?? Docs.Create.FontIconDocument(newFolderOpts), + newFolderScript + ); + + const reqdOpts: DocumentOptions = { + _showTitle: 'title', + _height: 100, + _gridGap: 5, + _forceActive: true, + _lockedPosition: true, + title: 'My Documents', + buttonMenu: true, + buttonMenuDoc: newFolderButton, + treeViewHideTitle: true, + targetDropAction: 'proto', + system: true, + isFolder: true, + treeViewType: TreeViewType.fileSystem, + childHideLinkButton: true, + boxShadow: '0 0', + childDontRegisterViews: true, + treeViewTruncateTitleWidth: 150, + ignoreClick: true, + childDropAction: 'alias', + childContextMenuLabels: new List(['Create new folder']), + childContextMenuIcons: new List(['plus']), + explainer: + 'This is your file manager where you can create folders to keep track of documents independently of your dashboard.', }; - myFilesystem = this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]); + myFilesystem = this.AssignDocField( + doc, + field, + (opts, items) => Docs.Create.TreeDocument(items ?? [], opts), + reqdOpts, + [myFileOrphans] + ); const childContextMenuScripts = [newFolder]; - if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { - myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); + if ( + Cast( + myFilesystem.childContextMenuScripts, + listSpec(ScriptField), + null + )?.length !== childContextMenuScripts.length + ) { + myFilesystem.childContextMenuScripts = new List( + childContextMenuScripts.map( + (script) => ScriptField.MakeFunction(script)! + ) + ); } return myFilesystem; } /// initializes the panel displaying docs that have been recently closed - static setupRecentlyClosed(doc: Doc, field:string) { - const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, - title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true, - treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", - contextMenuLabels: new List(["Empty recently closed"]), - contextMenuIcons:new List(["trash"]), - explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." + static setupRecentlyClosed(doc: Doc, field: string) { + const reqdOpts: DocumentOptions = { + _showTitle: 'title', + _lockedPosition: true, + _gridGap: 5, + _forceActive: true, + title: 'My Recently Closed', + buttonMenu: true, + childHideLinkButton: true, + treeViewHideTitle: true, + childDropAction: 'alias', + system: true, + treeViewTruncateTitleWidth: 150, + ignoreClick: true, + boxShadow: '0 0', + childDontRegisterViews: true, + targetDropAction: 'same', + contextMenuLabels: new List(['Empty recently closed']), + contextMenuIcons: new List(['trash']), + explainer: + 'Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list.', + }; + const recentlyClosed = this.AssignDocField( + doc, + field, + (opts) => Docs.Create.TreeDocument([], opts), + reqdOpts + ); + + const clearAll = (target: string) => + `getProto(${target}).data = new List([])`; + const clearBtnsOpts: DocumentOptions = { + _width: 30, + _height: 30, + _forceActive: true, + _stayInCollection: true, + _hideContextMenu: true, + title: 'Empty', + target: recentlyClosed, + btnType: ButtonType.ClickButton, + buttonText: 'Empty', + icon: 'trash', + system: true, + toolTip: 'Empty recently closed', }; - const recentlyClosed = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); - - const clearAll = (target:string) => `getProto(${target}).data = new List([])`; - const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, - title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", system: true, - toolTip: "Empty recently closed",}; - const clearDocsButton = this.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")}); - - if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton; - - if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) { - recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("self"))!]) + const clearDocsButton = this.AssignDocField( + recentlyClosed, + 'clearDocsBtn', + (opts) => Docs.Create.FontIconDocument(opts), + clearBtnsOpts, + undefined, + { onClick: clearAll('self.target') } + ); + + if (recentlyClosed.buttonMenuDoc !== clearDocsButton) + Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton; + + if ( + !Cast( + recentlyClosed.contextMenuScripts, + listSpec(ScriptField), + null + )?.find( + (script) => script.script.originalScript === clearAll('self') + ) + ) { + recentlyClosed.contextMenuScripts = new List([ + ScriptField.MakeScript(clearAll('self'))!, + ]); } return recentlyClosed; } @@ -643,60 +1462,184 @@ export class CurrentUserUtils { /// creates a new, empty filter doc static createFilterDoc() { const clearAll = `getProto(self).data = new List([])`; - const reqdOpts:DocumentOptions = { - _lockedPosition: true, _autoHeight: true, _fitWidth: true, _height: 150, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, - title: "Unnamed Filter", filterBoolean: "AND", boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, - childDropAction: "none", treeViewHideTitle: true, treeViewTruncateTitleWidth: 150, - childContextMenuLabels: new List(["Clear All"]), - childContextMenuScripts: new List([ScriptField.MakeFunction(clearAll)!]), + const reqdOpts: DocumentOptions = { + _lockedPosition: true, + _autoHeight: true, + _fitWidth: true, + _height: 150, + _xPadding: 5, + _yPadding: 5, + _gridGap: 5, + _forceActive: true, + title: 'Unnamed Filter', + filterBoolean: 'AND', + boxShadow: '0 0', + childDontRegisterViews: true, + targetDropAction: 'same', + ignoreClick: true, + system: true, + childDropAction: 'none', + treeViewHideTitle: true, + treeViewTruncateTitleWidth: 150, + childContextMenuLabels: new List(['Clear All']), + childContextMenuScripts: new List([ + ScriptField.MakeFunction(clearAll)!, + ]), }; return Docs.Create.FilterDocument(reqdOpts); } /// initializes the left sidebar panel view of the UserDoc - static setupUserDocView(doc: Doc, field:string) { - const reqdOpts:DocumentOptions = { - _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view", - boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, - treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 + static setupUserDocView(doc: Doc, field: string) { + const reqdOpts: DocumentOptions = { + _lockedPosition: true, + _gridGap: 5, + _forceActive: true, + title: Doc.CurrentUserEmail + '-view', + boxShadow: '0 0', + childDontRegisterViews: true, + targetDropAction: 'same', + ignoreClick: true, + system: true, + treeViewHideTitle: true, + treeViewTruncateTitleWidth: 150, }; - if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }); - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]); + if (!doc[field]) + this.AssignOpts(doc, { + treeViewOpen: true, + treeViewExpandedView: 'fields', + }); + return this.AssignDocField( + doc, + field, + (opts, items) => Docs.Create.TreeDocument(items ?? [], opts), + reqdOpts, + [doc] + ); } - static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { - ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - _lockedPosition: true, system: true, flexDirection: "row" - }) + static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => + Docs.Create.LinearDocument(docs, { + ...opts, + _gridGap: 0, + _xMargin: 5, + _yMargin: 5, + boxShadow: '0 0', + _forceActive: true, + dropConverter: ScriptField.MakeScript( + 'convertToButtons(dragData)', + { + dragData: DragManager.DocumentDragData.name, + } + ), + _lockedPosition: true, + system: true, + flexDirection: 'row', + }); - static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({ - btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, - _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), - _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, - }) + static createToolButton = (opts: DocumentOptions) => + Docs.Create.FontIconDocument({ + btnType: ButtonType.ToolButton, + _forceActive: true, + _dropAction: 'alias', + _hideContextMenu: true, + _removeDropProperties: new List([ + '_dropAction', + '_hideContextMenu', + 'stayInCollection', + ]), + _nativeWidth: 40, + _nativeHeight: 40, + _width: 40, + _height: 40, + system: true, + ...opts, + }); /// initializes the required buttons in the expanding button menu at the bottom of the Dash window - static setupDockedButtons(doc: Doc, field="myDockedBtns") { + static setupDockedButtons(doc: Doc, field = 'myDockedBtns') { const dockedBtns = DocCast(doc[field]); - const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}) => - this.AssignScripts(this.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? - CurrentUserUtils.createToolButton(opts), scripts); - - const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, - { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }} + const dockBtn = ( + opts: DocumentOptions, + scripts: { [key: string]: string } + ) => + this.AssignScripts( + this.AssignOpts( + DocListCast(dockedBtns?.data)?.find( + (doc) => doc.title === opts.title + ), + opts + ) ?? CurrentUserUtils.createToolButton(opts), + scripts + ); + + const btnDescs = [ + // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet + { + scripts: { onClick: 'undo()' }, + opts: { + title: 'undo', + icon: 'undo-alt', + toolTip: 'Click to undo', + }, + }, + { + scripts: { onClick: 'redo()' }, + opts: { + title: 'redo', + icon: 'redo-alt', + toolTip: 'Click to redo', + }, + }, ]; - const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); + const btns = btnDescs.map((desc) => + dockBtn( + { + _width: 30, + _height: 30, + dontUndo: true, + _stayInCollection: true, + ...desc.opts, + }, + desc.scripts + ) + ); const dockBtnsReqdOpts = { - title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, - childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true + title: 'docked buttons', + _height: 40, + flexGap: 0, + linearViewFloating: true, + childDontRegisterViews: true, + linearViewIsExpanded: true, + linearViewExpandable: true, + ignoreClick: true, }; - reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - return this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); + reaction( + () => UndoManager.redoStack.slice(), + () => + (Doc.GetProto( + btns.find((btn) => btn.title === 'redo')! + ).opacity = UndoManager.CanRedo() ? 1 : 0.4), + { fireImmediately: true } + ); + reaction( + () => UndoManager.undoStack.slice(), + () => + (Doc.GetProto( + btns.find((btn) => btn.title === 'undo')! + ).opacity = UndoManager.CanUndo() ? 1 : 0.4), + { fireImmediately: true } + ); + return this.AssignDocField( + doc, + field, + (opts, items) => this.linearButtonList(opts, items ?? []), + dockBtnsReqdOpts, + btns + ); } + // prettier-ignore static textTools():Button[] { return [ { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'}, @@ -719,6 +1662,7 @@ export class CurrentUserUtils { ]; } + // prettier-ignore static inkTools():Button[] { return [ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", _readOnly_);}' }}, @@ -734,10 +1678,22 @@ export class CurrentUserUtils { ]; } - static schemaTools():Button[] { - return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{return toggleSchemaPreview(_readOnly_);}'}, }]; + static schemaTools(): Button[] { + return [ + { + title: 'Show preview', + toolTip: 'Show preview of selected document', + btnType: ButtonType.ToggleButton, + buttonText: 'Show Preview', + icon: 'eye', + scripts: { + onClick: '{return toggleSchemaPreview(_readOnly_);}', + }, + }, + ]; } + // prettier-ignore static webTools() { return [ { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(_readOnly_); }' }}, @@ -747,6 +1703,7 @@ export class CurrentUserUtils { ]; } + // prettier-ignore static contextMenuTools():Button[] { return [ { btnList: new List([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, @@ -768,260 +1725,570 @@ export class CurrentUserUtils { } /// initializes a context menu button for the top bar context menu - static setupContextMenuButton(params:Button, btnDoc?:Doc) { - const reqdOpts:DocumentOptions = { - ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, - backgroundColor: params.scripts?.onClick ? undefined: "transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background - color: Colors.WHITE, system: true, dontUndo: true, - _nativeWidth: params.width ?? 30, _width: params.width ?? 30, - _height: 30, _nativeHeight: 30, - _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, - _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + static setupContextMenuButton(params: Button, btnDoc?: Doc) { + const reqdOpts: DocumentOptions = { + ...OmitKeys(params, ['scripts', 'funcs', 'subMenu']).omit, + backgroundColor: params.scripts?.onClick + ? undefined + : 'transparent', /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background + color: Colors.WHITE, + system: true, + dontUndo: true, + _nativeWidth: params.width ?? 30, + _width: params.width ?? 30, + _height: 30, + _nativeHeight: 30, + _stayInCollection: true, + _hideContextMenu: true, + _lockedPosition: true, + _dropAction: 'alias', + _removeDropProperties: new List([ + 'dropAction', + '_stayInCollection', + ]), }; - const reqdFuncs:{[key:string]:any} = { + const reqdFuncs: { [key: string]: any } = { ...params.funcs, - backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally - } - return this.AssignScripts(this.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); + backgroundColor: params.scripts?.onClick, /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally + }; + return this.AssignScripts( + this.AssignOpts(btnDoc, reqdOpts) ?? + Docs.Create.FontIconDocument(reqdOpts), + params.scripts, + reqdFuncs + ); } /// Initializes all the default buttons for the top bar context menu - static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; - const ctxtMenuBtnsDoc = this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); - const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { - const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); - if (!params.subMenu) { - return this.setupContextMenuButton(params, menuBtnDoc); - } else { - const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, - childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, - linearViewSubMenu: true, linearViewExpandable: true, }; - return this.AssignScripts(this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? - (ctxtMenuBtnsDoc[StrCast(params.title)]= this.linearButtonList(reqdSubMenuOpts, params.subMenu.map(sub => - this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) - ))), undefined, params.funcs); + static setupContextMenuButtons(doc: Doc, field = 'myContextMenuBtns') { + const reqdCtxtOpts = { + title: 'context menu buttons', + flexGap: 0, + childDontRegisterViews: true, + linearViewIsExpanded: true, + ignoreClick: true, + linearViewExpandable: false, + _height: 35, + }; + const ctxtMenuBtnsDoc = this.AssignDocField( + doc, + field, + (opts, items) => this.linearButtonList(opts, items ?? []), + reqdCtxtOpts, + undefined + ); + const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map( + (params) => { + const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find( + (doc) => doc.title === params.title + ); + if (!params.subMenu) { + return this.setupContextMenuButton(params, menuBtnDoc); + } else { + const reqdSubMenuOpts = { + ...OmitKeys(params, ['scripts', 'funcs', 'subMenu']) + .omit, + childDontRegisterViews: true, + flexGap: 0, + _height: 30, + ignoreClick: true, + linearViewSubMenu: true, + linearViewExpandable: true, + }; + return this.AssignScripts( + this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? + (ctxtMenuBtnsDoc[StrCast(params.title)] = + this.linearButtonList( + reqdSubMenuOpts, + params.subMenu.map((sub) => + this.setupContextMenuButton( + sub, + DocListCast(menuBtnDoc?.data).find( + (doc) => doc.title === sub.title + ) + ) + ) + )), + undefined, + params.funcs + ); + } } - }); + ); return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); } /// collection of documents rendered in the overlay layer above all tabs and other UI - static setupOverlays(doc: Doc, field = "myOverlayDocs") { - return this.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", system: true }); + static setupOverlays(doc: Doc, field = 'myOverlayDocs') { + return this.AssignDocField( + doc, + field, + (opts) => Docs.Create.FreeformDocument([], opts), + { + title: 'overlay documents', + backgroundColor: '#aca3a6', + system: true, + } + ); } - static setupPublished(doc:Doc, field = "myPublishedDocs") { - return this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", system: true }); + static setupPublished(doc: Doc, field = 'myPublishedDocs') { + return this.AssignDocField( + doc, + field, + (opts) => Docs.Create.TreeDocument([], opts), + { + title: 'published docs', + backgroundColor: '#aca3a6', + system: true, + } + ); } - + /// The database of all links on all documents static setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (!(Docs.newAccount ? undefined : DocCast(doc.myLinkDatabase))) { const linkDocs = new Doc(linkDatabaseId, true); - linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail; + linkDocs.title = 'LINK DATABASE: ' + Doc.CurrentUserEmail; linkDocs.author = Doc.CurrentUserEmail; linkDocs.data = new List([]); - linkDocs["acl-Public"] = SharingPermissions.Augment; + linkDocs['acl-Public'] = SharingPermissions.Augment; doc.myLinkDatabase = new PrefetchProxy(linkDocs); } } /// Shared documents option on the left side button panel - // A user's sharing document is where all documents that are shared to that user are placed. + // A user's sharing document is where all documents that are shared to that user are placed. // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents static setupSharedDocs(doc: Doc, sharingDocumentId: string) { const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); - const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); - const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}"; - - const sharedScripts = { treeViewChildDoubleClick: dblClkScript, } - const sharedDocOpts:DocumentOptions = { - title: "My Shared Docs", - userColor: "rgb(202, 202, 202)", - childContextMenuFilters: new List([dashboardFilter!,]), - childContextMenuScripts: new List([addToDashboards!,]), - childContextMenuLabels: new List(["Add to Dashboards",]), - childContextMenuIcons: new List(["user-plus",]), - "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, + const dashboardFilter = ScriptField.MakeFunction( + `doc._viewType === '${CollectionViewType.Docking}'`, + { doc: Doc.name } + ); + const dblClkScript = + "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}"; + + const sharedScripts = { treeViewChildDoubleClick: dblClkScript }; + const sharedDocOpts: DocumentOptions = { + title: 'My Shared Docs', + userColor: 'rgb(202, 202, 202)', + childContextMenuFilters: new List([dashboardFilter!]), + childContextMenuScripts: new List([addToDashboards!]), + childContextMenuLabels: new List(['Add to Dashboards']), + childContextMenuIcons: new List(['user-plus']), + 'acl-Public': SharingPermissions.Augment, + '_acl-Public': SharingPermissions.Augment, + childDropAction: 'alias', + system: true, + contentPointerEvents: 'all', + childLimitHeight: 0, + _yMargin: 50, + _gridGap: 15, // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar - _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, - explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" + _showTitle: 'title', + treeViewHideTitle: true, + ignoreClick: true, + _lockedPosition: true, + boxShadow: '0 0', + _chromeHidden: true, + dontRegisterView: true, + explainer: + "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'", }; - - this.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); + + this.AssignDocField( + doc, + 'mySharedDocs', + (opts) => + Docs.Create.TreeDocument( + [], + opts, + sharingDocumentId + 'layout', + sharingDocumentId + ), + sharedDocOpts, + undefined, + sharedScripts + ); } - /// Import option on the left side button panel - static setupImportSidebar(doc: Doc, field:string) { - const reqdOpts:DocumentOptions = { - title: "My Imports", _forceActive: true, buttonMenu: true, ignoreClick: true, _showTitle: "title", - _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, - childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, - dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." + /// Import option on the left side button panel + static setupImportSidebar(doc: Doc, field: string) { + const reqdOpts: DocumentOptions = { + title: 'My Imports', + _forceActive: true, + buttonMenu: true, + ignoreClick: true, + _showTitle: 'title', + _stayInCollection: true, + _hideContextMenu: true, + childLimitHeight: 0, + childDropAction: 'copy', + _autoHeight: true, + _yMargin: 50, + _gridGap: 15, + boxShadow: '0 0', + _lockedPosition: true, + system: true, + _chromeHidden: true, + dontRegisterView: true, + explainer: + 'This is where documents that are Imported into Dash will go.', }; - const myImports = this.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts); - - const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", - _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, - buttonText: "Import", icon: "upload", system: true }; - this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); + const myImports = this.AssignDocField( + doc, + field, + (opts) => Docs.Create.StackingDocument([], opts), + reqdOpts + ); + + const reqdBtnOpts: DocumentOptions = { + _forceActive: true, + toolTip: 'Import from computer', + _width: 30, + _height: 30, + _stayInCollection: true, + _hideContextMenu: true, + title: 'Import', + btnType: ButtonType.ClickButton, + buttonText: 'Import', + icon: 'upload', + system: true, + }; + this.AssignDocField( + myImports, + 'buttonMenuDoc', + (opts) => Docs.Create.FontIconDocument(opts), + reqdBtnOpts, + undefined, + { onClick: 'importDocument()' } + ); return myImports; } static setupClickEditorTemplates(doc: Doc) { - if (doc["clickFuncs-child"] === undefined) { + if (doc['clickFuncs-child'] === undefined) { // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target - const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ", - { thisContainer: Doc.name }), { - title: "Click to open in target", _width: 300, _height: 200, - targetScriptKey: "onChildClick", system: true - }); - - const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "openOnRight(self.doubleClickView)", {}), - { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); + const openInTarget = Docs.Create.ScriptingDocument( + ScriptField.MakeScript( + 'docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ', + { thisContainer: Doc.name } + ), + { + title: 'Click to open in target', + _width: 300, + _height: 200, + targetScriptKey: 'onChildClick', + system: true, + } + ); + + const openDetail = Docs.Create.ScriptingDocument( + ScriptField.MakeScript('openOnRight(self.doubleClickView)', {}), + { + title: 'Double click to open doubleClickView', + _width: 300, + _height: 200, + targetScriptKey: 'onChildDoubleClick', + system: true, + } + ); - doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); + doc['clickFuncs-child'] = Docs.Create.TreeDocument( + [openInTarget, openDetail], + { title: 'on Child Click function templates', system: true } + ); } // this is equivalent to using PrefetchProxies to make sure all the childClickFuncs have been retrieved. - PromiseValue(Cast(doc["clickFuncs-child"], Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); + PromiseValue(Cast(doc['clickFuncs-child'], Doc)).then( + (func) => func && PromiseValue(func.data).then(DocListCast) + ); if (doc.clickFuncs === undefined) { - const onClick = Docs.Create.ScriptingDocument(undefined, { - title: "onClick", "onClick-rawScript": "console.log('click')", - isTemplateDoc: true, isTemplateForField: "onClick", _width: 300, _height: 200, system: true - }, "onClick"); - const onChildClick = Docs.Create.ScriptingDocument(undefined, { - title: "onChildClick", "onChildClick-rawScript": "console.log('child click')", - isTemplateDoc: true, isTemplateForField: "onChildClick", _width: 300, _height: 200, system: true - }, "onChildClick"); - const onDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onDoubleClick", "onDoubleClick-rawScript": "console.log('double click')", - isTemplateDoc: true, isTemplateForField: "onDoubleClick", _width: 300, _height: 200, system: true - }, "onDoubleClick"); - const onChildDoubleClick = Docs.Create.ScriptingDocument(undefined, { - title: "onChildDoubleClick", "onChildDoubleClick-rawScript": "console.log('child double click')", - isTemplateDoc: true, isTemplateForField: "onChildDoubleClick", _width: 300, _height: 200, system: true - }, "onChildDoubleClick"); - const onCheckedClick = Docs.Create.ScriptingDocument(undefined, { - title: "onCheckedClick", "onCheckedClick-rawScript": "console.log(heading + checked + containingTreeView)", - "onCheckedClick-params": new List(["heading", "checked", "containingTreeView"]), isTemplateDoc: true, - isTemplateForField: "onCheckedClick", _width: 300, _height: 200, system: true - }, "onCheckedClick"); - doc.clickFuncs = Docs.Create.TreeDocument([onClick, onChildClick, onDoubleClick, onCheckedClick], { title: "onClick funcs", system: true }); + const onClick = Docs.Create.ScriptingDocument( + undefined, + { + title: 'onClick', + 'onClick-rawScript': "console.log('click')", + isTemplateDoc: true, + isTemplateForField: 'onClick', + _width: 300, + _height: 200, + system: true, + }, + 'onClick' + ); + const onChildClick = Docs.Create.ScriptingDocument( + undefined, + { + title: 'onChildClick', + 'onChildClick-rawScript': "console.log('child click')", + isTemplateDoc: true, + isTemplateForField: 'onChildClick', + _width: 300, + _height: 200, + system: true, + }, + 'onChildClick' + ); + const onDoubleClick = Docs.Create.ScriptingDocument( + undefined, + { + title: 'onDoubleClick', + 'onDoubleClick-rawScript': "console.log('double click')", + isTemplateDoc: true, + isTemplateForField: 'onDoubleClick', + _width: 300, + _height: 200, + system: true, + }, + 'onDoubleClick' + ); + const onChildDoubleClick = Docs.Create.ScriptingDocument( + undefined, + { + title: 'onChildDoubleClick', + 'onChildDoubleClick-rawScript': + "console.log('child double click')", + isTemplateDoc: true, + isTemplateForField: 'onChildDoubleClick', + _width: 300, + _height: 200, + system: true, + }, + 'onChildDoubleClick' + ); + const onCheckedClick = Docs.Create.ScriptingDocument( + undefined, + { + title: 'onCheckedClick', + 'onCheckedClick-rawScript': + 'console.log(heading + checked + containingTreeView)', + 'onCheckedClick-params': new List([ + 'heading', + 'checked', + 'containingTreeView', + ]), + isTemplateDoc: true, + isTemplateForField: 'onCheckedClick', + _width: 300, + _height: 200, + system: true, + }, + 'onCheckedClick' + ); + doc.clickFuncs = Docs.Create.TreeDocument( + [onClick, onChildClick, onDoubleClick, onCheckedClick], + { title: 'onClick funcs', system: true } + ); } - PromiseValue(Cast(doc.clickFuncs, Doc)).then(func => func && PromiseValue(func.data).then(DocListCast)); + PromiseValue(Cast(doc.clickFuncs, Doc)).then( + (func) => func && PromiseValue(func.data).then(DocListCast) + ); return doc.clickFuncs as Doc; } - /// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be /// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field /// whether to revert to "default" values, or to leave them as the user/system last set them. - static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - this.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {}); - reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]), + static updateUserDocument( + doc: Doc, + sharingDocumentId: string, + linkDatabaseId: string + ) { + this.AssignDocField( + doc, + 'globalGroupDatabase', + () => Docs.Prototypes.MainGroupDocument(), + {} + ); + reaction( + () => + DateCast(DocCast(doc.globalGroupDatabase)['data-lastModified']), async () => { - const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); - const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; - SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); - }, { fireImmediately: true }); + const groups = await DocListCastAsync( + DocCast(doc.globalGroupDatabase).data + ); + const mygroups = + groups?.filter((group) => + JSON.parse(StrCast(group.members)).includes( + Doc.CurrentUserEmail + ) + ) || []; + SnappingManager.SetCachedGroups([ + 'Public', + ...mygroups?.map((g) => StrCast(g.title)), + ]); + }, + { fireImmediately: true } + ); doc.system ?? (doc.system = true); doc.title ?? (doc.title = Doc.CurrentUserEmail); Doc.noviceMode ?? (Doc.noviceMode = true); doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); doc._showLabel ?? (doc._showLabel = true); - doc.textAlign ?? (doc.textAlign = "left"); - doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");; + doc.textAlign ?? (doc.textAlign = 'left'); + doc.activeInkColor ?? (doc.activeInkColor = 'rgb(0, 0, 0)'); doc.activeInkWidth ?? (doc.activeInkWidth = 1); - doc.activeInkBezier ?? (doc.activeInkBezier = "0"); - doc.activeFillColor ?? (doc.activeFillColor = ""); - doc.activeArrowStart ?? (doc.activeArrowStart = ""); - doc.activeArrowEnd ?? (doc.activeArrowEnd = ""); - doc.activeDash ?? (doc.activeDash == "0"); - doc.fontSize ?? (doc.fontSize = "12px"); - doc.fontFamily ?? (doc.fontFamily = "Arial"); - doc.fontColor ?? (doc.fontColor = "black"); - doc.fontHighlight ?? (doc.fontHighlight = ""); + doc.activeInkBezier ?? (doc.activeInkBezier = '0'); + doc.activeFillColor ?? (doc.activeFillColor = ''); + doc.activeArrowStart ?? (doc.activeArrowStart = ''); + doc.activeArrowEnd ?? (doc.activeArrowEnd = ''); + doc.activeDash ?? doc.activeDash == '0'; + doc.fontSize ?? (doc.fontSize = '12px'); + doc.fontFamily ?? (doc.fontFamily = 'Arial'); + doc.fontColor ?? (doc.fontColor = 'black'); + doc.fontHighlight ?? (doc.fontHighlight = ''); doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false); doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; - doc.freezeChildren = "remove|add"; + doc.freezeChildren = 'remove|add'; this.setupLinkDocs(doc, linkDatabaseId); - this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing - this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon + this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing + this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile - this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard - this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box) + this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard + this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box) this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected - this.setupDockedButtons(doc); // the bottom bar of font icons + this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption - this.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {}); - this.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents - + this.AssignDocField( + doc, + 'globalScriptDatabase', + (opts) => Docs.Prototypes.MainScriptDocument(), + {} + ); + this.AssignDocField( + doc, + 'myHeaderBar', + (opts) => Docs.Create.MulticolumnDocument([], opts), + { title: 'header bar', system: true } + ); // drop down panel at top of dashboard for stashing documents + if (doc.activeDashboard instanceof Doc) { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) - doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; + doc.activeDashboard.colorScheme = + doc.activeDashboard.colorScheme === ColorScheme.Light + ? undefined + : doc.activeDashboard.colorScheme; } new LinkManager(); DocServer.UPDATE_SERVER_CACHE(); return doc; } - static setupFieldInfos(doc:Doc, field="fieldInfos") { - const fieldInfoOpts = { title: "Field Infos", system: true}; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object - const infos = this.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts); + static setupFieldInfos(doc: Doc, field = 'fieldInfos') { + const fieldInfoOpts = { title: 'Field Infos', system: true }; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object + const infos = this.AssignDocField( + doc, + field, + (opts) => Doc.assign(new Doc(), opts as any), + fieldInfoOpts + ); const entries = Object.entries(new DocumentOptions()); - entries.forEach(pair => { + entries.forEach((pair) => { if (!Array.from(Object.keys(fieldInfoOpts)).includes(pair[0])) { const options = pair[1] as FInfo; - const opts:DocumentOptions = { system: true, title: pair[0], ...OmitKeys(options, ["values"]).omit, fieldIsLayout: pair[0].startsWith("_")}; + const opts: DocumentOptions = { + system: true, + title: pair[0], + ...OmitKeys(options, ['values']).omit, + fieldIsLayout: pair[0].startsWith('_'), + }; switch (options.fieldType) { - case "boolean": opts.fieldValues = new List(options.values as any); break; - case "number": opts.fieldValues = new List(options.values as any); break; - case "Doc": opts.fieldValues = new List(options.values as any); break; - default: opts.fieldValues = new List(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType + case 'boolean': + opts.fieldValues = new List( + options.values as any + ); + break; + case 'number': + opts.fieldValues = new List( + options.values as any + ); + break; + case 'Doc': + opts.fieldValues = new List(options.values as any); + break; + default: + opts.fieldValues = new List( + options.values as any + ); + break; // string, pointerEvents, dimUnit, dropActionType } - this.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts); + this.AssignDocField( + infos, + pair[0], + (opts) => + Doc.assign(new Doc(), OmitKeys(opts, ['values']).omit), + opts + ); } }); } public static async loadCurrentUser() { - return rp.get(Utils.prepend("/getCurrentUser")).then(async response => { - if (response) { - const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response); - Doc.CurrentUserEmail = result.email; - resolvedPorts = JSON.parse(await (await fetch("/resolvedPorts")).text()); - DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email); - result.cacheDocumentIds && (await DocServer.GetRefFields(result.cacheDocumentIds.split(";"))); - return result; - } else { - throw new Error("There should be a user! Why does Dash think there isn't one?"); - } - }); + return rp + .get(Utils.prepend('/getCurrentUser')) + .then(async (response) => { + if (response) { + const result: { + id: string; + email: string; + cacheDocumentIds: string; + } = JSON.parse(response); + Doc.CurrentUserEmail = result.email; + resolvedPorts = JSON.parse( + await (await fetch('/resolvedPorts')).text() + ); + DocServer.init( + window.location.protocol, + window.location.hostname, + resolvedPorts.socket, + result.email + ); + result.cacheDocumentIds && + (await DocServer.GetRefFields( + result.cacheDocumentIds.split(';') + )); + return result; + } else { + throw new Error( + "There should be a user! Why does Dash think there isn't one?" + ); + } + }); } public static async loadUserDocument(id: string) { this.curr_id = id; - await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { - const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids); - if (userDocumentId !== "guest") { - return DocServer.GetRefField(userDocumentId).then(async field => { - Docs.newAccount = !(field instanceof Doc); - await Docs.Prototypes.initialize(); - const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; - Docs.newAccount &&(userDoc.activePage = "home"); - return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); - }); + await rp.get(Utils.prepend('/getUserDocumentIds')).then((ids) => { + const { userDocumentId, sharingDocumentId, linkDatabaseId } = + JSON.parse(ids); + if (userDocumentId !== 'guest') { + return DocServer.GetRefField(userDocumentId).then( + async (field) => { + Docs.newAccount = !(field instanceof Doc); + await Docs.Prototypes.initialize(); + const userDoc = Docs.newAccount + ? new Doc(userDocumentId, true) + : (field as Doc); + Docs.newAccount && (userDoc.activePage = 'home'); + return this.updateUserDocument( + Doc.SetUserDoc(userDoc), + sharingDocumentId, + linkDatabaseId + ); + } + ); } else { - throw new Error("There should be a user id! Why does Dash think there isn't one?"); + throw new Error( + "There should be a user id! Why does Dash think there isn't one?" + ); } }); } @@ -1030,25 +2297,31 @@ export class CurrentUserUtils { /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) - public static openDashboard = (doc: Doc|undefined, fromHistory = false) => { + public static openDashboard = ( + doc: Doc | undefined, + fromHistory = false + ) => { if (!doc) return false; CurrentUserUtils.MainDocId = doc[Id]; - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); + Doc.AddDocToList(CurrentUserUtils.MyDashboards, 'data', doc); // this has the side-effect of setting the main container since we're assigning the active/guest dashboard - Doc.UserDoc() ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); - + Doc.UserDoc() + ? (CurrentUserUtils.ActiveDashboard = doc) + : (CurrentUserUtils.GuestDashboard = doc); + const state = CurrentUserUtils._urlState; if (state.sharing === true && !Doc.UserDoc()) { DocServer.Control.makeReadOnly(); } else { - fromHistory || HistoryUtil.pushState({ - type: "doc", - docId: doc[Id], - readonly: state.readonly, - nro: state.nro, - sharing: false, - }); + fromHistory || + HistoryUtil.pushState({ + type: 'doc', + docId: doc[Id], + readonly: state.readonly, + nro: state.nro, + sharing: false, + }); if (state.readonly === true || state.readonly === null) { DocServer.Control.makeReadOnly(); } else if (state.safe) { @@ -1056,7 +2329,11 @@ export class CurrentUserUtils { DocServer.Control.makeReadOnly(); } CollectionView.SetSafeMode(true); - } else if (state.nro || state.nro === null || state.readonly === false) { + } else if ( + state.nro || + state.nro === null || + state.readonly === false + ) { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); } else { @@ -1065,15 +2342,15 @@ export class CurrentUserUtils { } return true; - } + }; public static importDocument = () => { - const input = document.createElement("input"); - input.type = "file"; + const input = document.createElement('input'); + input.type = 'file'; input.multiple = true; - input.accept = ".zip, application/pdf, video/*, image/*, audio/*"; - input.onchange = async _e => { - const upload = Utils.prepend("/uploadDoc"); + input.accept = '.zip, application/pdf, video/*, image/*, audio/*'; + input.onchange = async (_e) => { + const upload = Utils.prepend('/uploadDoc'); const formData = new FormData(); const file = input.files?.[0]; if (file?.type === 'application/zip') { @@ -1082,38 +2359,69 @@ export class CurrentUserUtils { // setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => // docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added. // } - const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null); + const list = Cast( + CurrentUserUtils.MyImports.data, + listSpec(Doc), + null + ); doc instanceof Doc && list?.splice(0, 0, doc); } else if (input.files && input.files.length !== 0) { const disposer = OverlayView.ShowSpinner(); - const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); + const results = await DocUtils.uploadFilesToDocs( + Array.from(input.files || []), + {} + ); if (results.length !== input.files?.length) { - alert("Error uploading files - possibly due to unsupported file types"); + alert( + 'Error uploading files - possibly due to unsupported file types' + ); } - const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null); + const list = Cast( + CurrentUserUtils.MyImports.data, + listSpec(Doc), + null + ); list?.splice(0, 0, ...results); disposer(); } else { - console.log("No file selected"); + console.log('No file selected'); } }; input.click(); + }; + + public static snapshotDashboard() { + return CollectionDockingView.TakeSnapshot( + CurrentUserUtils.ActiveDashboard + ); } - - public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(CurrentUserUtils.ActiveDashboard); } - public static closeActiveDashboard = () => { CurrentUserUtils.ActiveDashboard = undefined; } + public static closeActiveDashboard = () => { + CurrentUserUtils.ActiveDashboard = undefined; + }; - public static removeDashboard = async (dashboard:Doc) => { - const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); + public static removeDashboard = async (dashboard: Doc) => { + const dashboards = await DocListCastAsync( + CurrentUserUtils.MyDashboards.data + ); if (dashboards?.length) { - if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)); - Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); - if (!dashboards.length) CurrentUserUtils.ActivePage = "home"; + if (dashboard === CurrentUserUtils.ActiveDashboard) + CurrentUserUtils.openDashboard( + dashboards.find((doc) => doc !== dashboard) + ); + Doc.RemoveDocFromList( + CurrentUserUtils.MyDashboards, + 'data', + dashboard + ); + if (!dashboards.length) CurrentUserUtils.ActivePage = 'home'; } - } + }; public static createNewDashboard = (id?: string, name?: string) => { - const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true); + const presentation = Doc.MakeCopy( + Doc.UserDoc().emptyPresentation as Doc, + true + ); const dashboards = CurrentUserUtils.MyDashboards; const dashboardCount = DocListCast(dashboards.data).length + 1; const freeformOptions: DocumentOptions = { @@ -1125,84 +2433,211 @@ export class CurrentUserUtils { _backgroundGridShow: true, title: `Untitled Tab 1`, }; - const title = name ? name : `Dashboard ${dashboardCount}` - const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, "row"); + const title = name ? name : `Dashboard ${dashboardCount}`; + const freeformDoc = + CurrentUserUtils.GuestTarget || + Docs.Create.FreeformDocument([], freeformOptions); + const dashboardDoc = Docs.Create.StandardCollectionDockingDocument( + [{ doc: freeformDoc, initialWidth: 600 }], + { title: title }, + id, + 'row' + ); freeformDoc.context = dashboardDoc; // switching the tabs from the datadoc to the regular doc const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); dashboardDoc.data = new List(dashboardTabs); - dashboardDoc["pane-count"] = 1; + dashboardDoc['pane-count'] = 1; CurrentUserUtils.ActivePresentation = presentation; - Doc.AddDocToList(dashboards, "data", dashboardDoc); - // open this new dashboard + Doc.AddDocToList(dashboards, 'data', dashboardDoc); + // open this new dashboard CurrentUserUtils.ActiveDashboard = dashboardDoc; - CurrentUserUtils.ActivePage = "dashboard"; - } - - public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { - const tbox = Docs.Create.TextDocument("", { - _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor, - _width: width || 200, _height: 35, x: x, y: y, _fitWidth: true, _autoHeight: true, title + CurrentUserUtils.ActivePage = 'dashboard'; + }; + + public static GetNewTextDoc( + title: string, + x: number, + y: number, + width?: number, + height?: number, + noMargins?: boolean, + annotationOn?: Doc, + maxHeight?: number, + backgroundColor?: string + ) { + const tbox = Docs.Create.TextDocument('', { + _xMargin: noMargins ? 0 : undefined, + _yMargin: noMargins ? 0 : undefined, + annotationOn, + docMaxAutoHeight: maxHeight, + backgroundColor: backgroundColor, + _width: width || 200, + _height: 35, + x: x, + y: y, + _fitWidth: true, + _autoHeight: true, + title, }); const template = Doc.UserDoc().defaultTextLayout; if (template instanceof Doc) { tbox._width = NumCast(template._width); - tbox.layoutKey = "layout_" + StrCast(template.title); + tbox.layoutKey = 'layout_' + StrCast(template.title); Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template; } return tbox; } - public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } - public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); } - public static get MySearcher() { return DocCast(Doc.UserDoc().mySearcher); } - public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } - public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); } - public static get MyTools() { return DocCast(Doc.UserDoc().myTools); } - public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } - public static get MyFileOrphans() { return DocCast(Doc.UserDoc().myFileOrphans); } - public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); } - public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } - public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } - public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } - public static get MyTrails() { return DocCast(Doc.UserDoc().myTrails); } - public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } - public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } - public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } - public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); } - public static get MyPublishedDocs() { return DocCast(Doc.UserDoc().myPublishedDocs); } - public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } - public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; } - public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } - public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; } - public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); } - public static set ActivePage(val) { Doc.UserDoc().activePage = val; } - public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; } - public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } + public static get MyUserDocView() { + return DocCast(Doc.UserDoc().myUserDocView); + } + public static get MyDockedBtns() { + return DocCast(Doc.UserDoc().myDockedBtns); + } + public static get MySearcher() { + return DocCast(Doc.UserDoc().mySearcher); + } + public static get MyFilesystem() { + return DocCast(Doc.UserDoc().myFilesystem); + } + public static get MyHeaderBar() { + return DocCast(Doc.UserDoc().myHeaderBar); + } + public static get MyTools() { + return DocCast(Doc.UserDoc().myTools); + } + public static get MyDashboards() { + return DocCast(Doc.UserDoc().myDashboards); + } + public static get MyFileOrphans() { + return DocCast(Doc.UserDoc().myFileOrphans); + } + public static get MyTemplates() { + return DocCast(Doc.UserDoc().myTemplates); + } + public static get MyLeftSidebarMenu() { + return DocCast(Doc.UserDoc().myLeftSidebarMenu); + } + public static get MyLeftSidebarPanel() { + return DocCast(Doc.UserDoc().myLeftSidebarPanel); + } + public static get MySharedDocs() { + return DocCast(Doc.UserDoc().mySharedDocs); + } + public static get MyTrails() { + return DocCast(Doc.UserDoc().myTrails); + } + public static get MyImports() { + return DocCast(Doc.UserDoc().myImports); + } + public static get MyContextMenuBtns() { + return DocCast(Doc.UserDoc().myContextMenuBtns); + } + public static get MyRecentlyClosed() { + return DocCast(Doc.UserDoc().myRecentlyClosed); + } + public static get MyOverlayDocs() { + return DocCast(Doc.UserDoc().myOverlayDocs); + } + public static get MyPublishedDocs() { + return DocCast(Doc.UserDoc().myPublishedDocs); + } + public static get ActiveDashboard() { + return DocCast(Doc.UserDoc().activeDashboard); + } + public static set ActiveDashboard(val: Doc | undefined) { + Doc.UserDoc().activeDashboard = val; + } + public static get ActivePresentation() { + return DocCast(Doc.UserDoc().activePresentation); + } + public static set ActivePresentation(val) { + Doc.UserDoc().activePresentation = val; + } + public static get ActivePage() { + return StrCast(Doc.UserDoc().activePage); + } + public static set ActivePage(val) { + Doc.UserDoc().activePage = val; + } + public static set ActiveTool(tool: InkTool) { + Doc.UserDoc().activeTool = tool; + } + public static get ActiveTool(): InkTool { + return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; + } } -ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); -ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); -ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); -ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(); }, "creates a snapshot copy of a dashboard"); -ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(); }, "creates a new dashboard when called"); -ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); -ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); -ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); -ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); -ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); -ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); -ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { CurrentUserUtils.openDashboard( Doc.MakeAlias(dashboard)); }, "adds Dashboard to set of Dashboards"); -ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) { - let selected = (sel => checkContext ? DocCast(sel?.context) : sel)(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); - return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true; +ScriptingGlobals.add(function MySharedDocs() { + return CurrentUserUtils.MySharedDocs; +}, 'document containing all shared Docs'); +ScriptingGlobals.add(function IsNoviceMode() { + return Doc.noviceMode; +}, 'is Dash in novice mode'); +ScriptingGlobals.add(function toggleComicMode() { + Doc.UserDoc().renderStyle = + Doc.UserDoc().renderStyle === 'comic' ? undefined : 'comic'; +}, 'switches between comic and normal document rendering'); +ScriptingGlobals.add(function snapshotDashboard() { + CurrentUserUtils.snapshotDashboard(); +}, 'creates a snapshot copy of a dashboard'); +ScriptingGlobals.add(function createNewDashboard() { + return CurrentUserUtils.createNewDashboard(); +}, 'creates a new dashboard when called'); +ScriptingGlobals.add(function createNewPresentation() { + return MainView.Instance.createNewPresentation(); +}, 'creates a new presentation when called'); +ScriptingGlobals.add(function createNewFolder() { + return MainView.Instance.createNewFolder(); +}, 'creates a new folder in myFiles when called'); +ScriptingGlobals.add( + function links(doc: any) { + return new List(LinkManager.Instance.getAllRelatedLinks(doc)); + }, + 'returns all the links to the document or its annotations', + '(doc: any)' +); +ScriptingGlobals.add(function importDocument() { + return CurrentUserUtils.importDocument(); +}, 'imports files from device directly into the import sidebar'); +ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { + SharingManager.Instance.open(undefined, dashboard); +}, 'opens sharing dialog for Dashboard'); +ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { + CurrentUserUtils.removeDashboard(dashboard); +}, 'Remove Dashboard from Dashboards'); +ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { + CurrentUserUtils.openDashboard(Doc.MakeAlias(dashboard)); +}, 'adds Dashboard to set of Dashboards'); +ScriptingGlobals.add(function selectedDocumentType( + docType?: DocumentType, + colType?: CollectionViewType, + checkContext?: boolean +) { + let selected = ((sel) => (checkContext ? DocCast(sel?.context) : sel))( + SelectionManager.SelectedSchemaDoc() ?? + SelectionManager.Docs().lastElement() + ); + return docType + ? selected?.type === docType + : colType + ? selected?.viewType === colType + : true; }); ScriptingGlobals.add(function makeTopLevelFolder() { TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; - const opts = { title: "Untitled folder", _stayInCollection: true, isFolder: true }; - return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); -}); \ No newline at end of file + const opts = { + title: 'Untitled folder', + _stayInCollection: true, + isFolder: true, + }; + return Doc.AddDocToList( + CurrentUserUtils.MyFilesystem, + 'data', + Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id) + ); +}); -- cgit v1.2.3-70-g09d2 From da19cbc10d28b2e39a6592b80880106a415acc1c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Jul 2022 09:01:53 -0400 Subject: allow longer lines - people can always break them up explicitly if they don't look right. --- .prettierrc.json | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1637 +++++++++++--------- 2 files changed, 904 insertions(+), 735 deletions(-) (limited to 'src') diff --git a/.prettierrc.json b/.prettierrc.json index a8189121a..8f7564e26 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -4,7 +4,7 @@ "semi": true, "singleQuote": true, "singleAttributePerLine": false, - "printWidth": 200, + "printWidth": 250, "jsxBracketSameLine": true, "arrowParens": "avoid" } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b9da4faa4..d33f0b5be 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,65 +1,64 @@ -import { Bezier } from "bezier-js"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { computedFn } from "mobx-utils"; -import { DateField } from "../../../../fields/DateField"; -import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField"; -import { List } from "../../../../fields/List"; -import { ObjectField } from "../../../../fields/ObjectField"; -import { RichTextField } from "../../../../fields/RichTextField"; -import { listSpec } from "../../../../fields/Schema"; -import { ScriptField } from "../../../../fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; -import { ImageField } from "../../../../fields/URLField"; -import { TraceMobx } from "../../../../fields/util"; -import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; -import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { DragManager, dropActionType } from "../../../util/DragManager"; -import { HistoryUtil } from "../../../util/History"; -import { InteractionUtils } from "../../../util/InteractionUtils"; -import { LinkManager } from "../../../util/LinkManager"; -import { RecordingApi } from "../../../util/RecordingApi"; -import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; -import { SearchUtil } from "../../../util/SearchUtil"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { ColorScheme } from "../../../util/SettingsManager"; -import { SnappingManager } from "../../../util/SnappingManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH } from "../../../views/global/globalCssVariables.scss"; -import { Timeline } from "../../animationtimeline/Timeline"; -import { ContextMenu } from "../../ContextMenu"; -import { GestureOverlay } from "../../GestureOverlay"; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "../../InkingStroke"; -import { LightboxView } from "../../LightboxView"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView"; -import { FieldViewProps } from "../../nodes/FieldView"; -import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; -import { PresBox } from "../../nodes/trails/PresBox"; -import { VideoBox } from "../../nodes/VideoBox"; -import { CreateImage } from "../../nodes/WebBoxRenderer"; -import { StyleProp } from "../../StyleProvider"; -import { CollectionDockingView } from "../CollectionDockingView"; -import { CollectionSubView } from "../CollectionSubView"; -import { TreeViewType } from "../CollectionTreeView"; -import { CollectionViewType } from "../CollectionView"; -import { TabDocView } from "../TabDocView"; -import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; -import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; -import "./CollectionFreeFormView.scss"; -import { MarqueeView } from "./MarqueeView"; -import React = require("react"); -import e = require("connect-flash"); - +import { Bezier } from 'bezier-js'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { computedFn } from 'mobx-utils'; +import { DateField } from '../../../../fields/DateField'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; +import { List } from '../../../../fields/List'; +import { ObjectField } from '../../../../fields/ObjectField'; +import { RichTextField } from '../../../../fields/RichTextField'; +import { listSpec } from '../../../../fields/Schema'; +import { ScriptField } from '../../../../fields/ScriptField'; +import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { TraceMobx } from '../../../../fields/util'; +import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; +import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; +import { DocServer } from '../../../DocServer'; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { DragManager, dropActionType } from '../../../util/DragManager'; +import { HistoryUtil } from '../../../util/History'; +import { InteractionUtils } from '../../../util/InteractionUtils'; +import { LinkManager } from '../../../util/LinkManager'; +import { RecordingApi } from '../../../util/RecordingApi'; +import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; +import { SearchUtil } from '../../../util/SearchUtil'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { ColorScheme } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { Transform } from '../../../util/Transform'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss'; +import { Timeline } from '../../animationtimeline/Timeline'; +import { ContextMenu } from '../../ContextMenu'; +import { GestureOverlay } from '../../GestureOverlay'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; +import { LightboxView } from '../../LightboxView'; +import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; +import { FieldViewProps } from '../../nodes/FieldView'; +import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; +import { PresBox } from '../../nodes/trails/PresBox'; +import { VideoBox } from '../../nodes/VideoBox'; +import { CreateImage } from '../../nodes/WebBoxRenderer'; +import { StyleProp } from '../../StyleProvider'; +import { CollectionDockingView } from '../CollectionDockingView'; +import { CollectionSubView } from '../CollectionSubView'; +import { TreeViewType } from '../CollectionTreeView'; +import { CollectionViewType } from '../CollectionView'; +import { TabDocView } from '../TabDocView'; +import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; +import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; +import './CollectionFreeFormView.scss'; +import { MarqueeView } from './MarqueeView'; +import React = require('react'); +import e = require('connect-flash'); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -69,13 +68,15 @@ export type collectionFreeformViewProps = { noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them - dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. + dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. }; @observer export class CollectionFreeFormView extends CollectionSubView>() { - public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive + public get displayName() { + return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')'; + } // this makes mobx trace() statements more descriptive private _lastNudge: any; private _lastX: number = 0; @@ -90,27 +91,33 @@ export class CollectionFreeFormView extends CollectionSubView(); private _layoutPoolData = observable.map(); - private _layoutSizeData = observable.map(); + private _layoutSizeData = observable.map(); private _cachedPool: Map = new Map(); private _lastTap = 0; private _batch: UndoManager.Batch | undefined = undefined; // private isWritingMode: boolean = true; - // private writingModeDocs: Doc[] = []; + // private writingModeDocs: Doc[] = []; - private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } - private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } - private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } + private get isAnnotationOverlay() { + return this.props.isAnnotationOverlay; + } + private get scaleFieldKey() { + return this.props.scaleField || '_viewScale'; + } + private get borderWidth() { + return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; + } @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables - @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0 + @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0 @observable _hLines: number[] | undefined; @observable _vLines: number[] | undefined; @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. @observable _pullCoords: number[] = [0, 0]; - @observable _pullDirection: string = ""; + @observable _pullDirection: string = ''; @observable _showAnimTimeline = false; - @observable _clusterSets: (Doc[])[] = []; + @observable _clusterSets: Doc[][] = []; @observable _deleteList: DocumentView[] = []; @observable _timelineRef = React.createRef(); @observable _marqueeRef = React.createRef(); @@ -118,43 +125,57 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } + @computed get views() { + return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + } @computed get fitToContentVals() { return { bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 }, - scale: !this.childDocs.length ? 1 : - Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), - this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) + scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)), }; } - @computed get fitContentsToBox() { return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay; } - @computed get contentBounds() { - const cb = Cast(this.rootDoc.contentBounds, listSpec("number")); - return cb ? {x:cb[0], y:cb[1], r:cb[2], b: cb[3]} : - this.props.contentBounds?.() ?? aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); + @computed get fitContentsToBox() { + return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay; + } + @computed get contentBounds() { + const cb = Cast(this.rootDoc.contentBounds, listSpec('number')); + return cb + ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] } + : this.props.contentBounds?.() ?? + aggregateBounds( + this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), + NumCast(this.layoutDoc._xPadding, 10), + NumCast(this.layoutDoc._yPadding, 10) + ); + } + @computed get nativeWidth() { + return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); + } + @computed get nativeHeight() { + return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } - @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } - @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } @computed get cachedCenteringShiftX(): number { const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; - return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections + return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedCenteringShiftY(): number { const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; - return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling;// shift so pan position is at center of window for non-overlay collections + return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedGetLocalTransform(): Transform { - return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); + return Transform.Identity() + .scale(1 / this.zoomScaling()) + .translate(this.panX(), this.panY()); } @computed get cachedGetContainerTransform(): Transform { return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); } @computed get cachedGetTransform(): Transform { - return this.getContainerTransform().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); + return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); } - changeKeyFrame = (back=false) => { - const currentFrame = Cast(this.Document._currentFrame, "number", null); + changeKeyFrame = (back = false) => { + const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { this.Document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); @@ -167,8 +188,8 @@ export class CollectionFreeFormView extends CollectionSubView this._keyframeEditing = set; + }; + @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set); getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); @@ -179,32 +200,35 @@ export class CollectionFreeFormView extends CollectionSubView !this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined; - reverseNativeScaling = () => this.fitContentsToBox ? true : false; + }; + freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined); + reverseNativeScaling = () => (this.fitContentsToBox ? true : false); // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document. - // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image + // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1)); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1)); zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); - contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; + contentTransform = () => + !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 + ? '' + : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; getTransform = () => this.cachedGetTransform.copy(); getLocalTransform = () => this.cachedGetLocalTransform.copy(); getContainerTransform = () => this.cachedGetContainerTransform.copy(); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); isAnyChildContentActive = () => this.props.isAnyChildContentActive(); addLiveTextBox = (newBox: Doc) => { - FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed + FormattedTextBox.SelectOnLoad = newBox[Id]; // track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newBox); - } + }; selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); - } + }; addDocument = (newBox: Doc | Doc[]) => { let retVal = false; if (newBox instanceof Doc) { - if (retVal = (this.props.addDocument?.(newBox) || false)) { + if ((retVal = this.props.addDocument?.(newBox) || false)) { this.bringToFront(newBox); this.updateCluster(newBox); } @@ -213,14 +237,14 @@ export class CollectionFreeFormView extends CollectionSubView newBox[field]); CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field}-indexed`]); CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field]); delete newBox.activeFrame; - CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== "opacity" && (newBox[field] = vals[i])); + CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== 'opacity' && (newBox[field] = vals[i])); } } if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) { @@ -228,13 +252,13 @@ export class CollectionFreeFormView extends CollectionSubView= -1e-4 && curTime <= endTime); + return dispTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime); } @action @@ -246,8 +270,11 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - zsorted.forEach((doc, index) => doc.zIndex = doc.isInkMask ? 5000 : index + 1); + const zsorted = this.childLayoutPairs + .map(pair => pair.layout) + .slice() + .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1)); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)]; for (let i = 0; i < docDragData.droppedDocuments.length; i++) { @@ -267,8 +294,8 @@ export class CollectionFreeFormView extends CollectionSubView { return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY)); - } + }; pickCluster(probe: number[]) { - return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); - if (grouping !== -1) { - const layoutDoc = Doc.Layout(cd); - const cx = NumCast(cd.x) - this._clusterDistance; - const cy = NumCast(cd.y) - this._clusterDistance; - const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; - const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; - return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; - } - return cluster; - }, -1); + return this.childLayoutPairs + .map(pair => pair.layout) + .reduce((cluster, cd) => { + const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); + if (grouping !== -1) { + const layoutDoc = Doc.Layout(cd); + const cx = NumCast(cd.x) - this._clusterDistance; + const cy = NumCast(cd.y) - this._clusterDistance; + const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; + const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; + return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; + } + return cluster; + }, -1); } tryDragCluster(e: PointerEvent | TouchEvent, cluster: number) { @@ -338,10 +369,16 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster); const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 }; - const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? "alias" : undefined); + const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined); de.moveDocument = this.props.moveDocument; de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); - DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction }); + DragManager.StartDocumentDrag( + clusterDocs.map(v => v.ContentDiv!), + de, + ptsParent.clientX, + ptsParent.clientY, + { hideSource: !de.dropAction } + ); return true; } } @@ -364,12 +401,16 @@ export class CollectionFreeFormView extends CollectionSubView this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); const preferredInd = NumCast(docFirst.cluster); - docs.map(doc => doc.cluster = -1); - docs.map(doc => this._clusterSets.map((set, i) => set.map(member => { - if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { - docFirst.cluster = i; - } - }))); + docs.map(doc => (doc.cluster = -1)); + docs.map(doc => + this._clusterSets.map((set, i) => + set.map(member => { + if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { + docFirst.cluster = i; + } + }) + ) + ); if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { docFirst.cluster = preferredInd; } @@ -385,7 +426,7 @@ export class CollectionFreeFormView extends CollectionSubView this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc)); + docs.map(doc => this._clusterSets[(doc.cluster = NumCast(docFirst.cluster))].push(doc)); } childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child)); } @@ -399,11 +440,13 @@ export class CollectionFreeFormView extends CollectionSubView Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); const preferredInd = NumCast(doc.cluster); doc.cluster = -1; - this._clusterSets.forEach((set, i) => set.forEach(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { - doc.cluster = i; - } - })); + this._clusterSets.forEach((set, i) => + set.forEach(member => { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { + doc.cluster = i; + } + }) + ); if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } @@ -423,7 +466,7 @@ export class CollectionFreeFormView extends CollectionSubView, props: Opt, property: string) => { - let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 + let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 if (property !== StyleProp.BackgroundColor) return styleProp; const cluster = NumCast(doc?.cluster); if (this.Document._useClusters) { @@ -431,15 +474,15 @@ export class CollectionFreeFormView extends CollectionSubView doc && this.updateCluster(doc)); } else { // choose a cluster color from a palette - const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"]; + const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; styleProp = colors[cluster % colors.length]; const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.map(s => styleProp = StrCast(s.backgroundColor)); + set?.map(s => (styleProp = StrCast(s.backgroundColor))); } } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; return styleProp; - } + }; trySelectCluster = (addToSel: boolean) => { if (this._hitCluster !== -1) { @@ -449,30 +492,32 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener("pointerup", this.onPenUp); - const currentCol = DocListCast(this.rootDoc.currentInkDoc) + document.removeEventListener('pointerup', this.onPenUp); + const currentCol = DocListCast(this.rootDoc.currentInkDoc); const rootDocList = DocListCast(this.rootDoc.data); currentCol.push(rootDocList[rootDocList.length - 1]); console.log(currentCol); this._batch?.end(); } - } + }; @action onPointerDown = (e: React.PointerEvent): void => { this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) { - if (!e.nativeEvent.cancelBubble && + if ( + !e.nativeEvent.cancelBubble && !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && - !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { + !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) + ) { switch (CurrentUserUtils.ActiveTool) { case InkTool.Highlighter: break; @@ -482,23 +527,23 @@ export class CollectionFreeFormView extends CollectionSubView) => { @@ -517,14 +562,13 @@ export class CollectionFreeFormView extends CollectionSubView(); @@ -534,8 +578,13 @@ export class CollectionFreeFormView extends CollectionSubView this.props.removeDocument?.(d)); e.stopPropagation(); break; @@ -573,17 +622,17 @@ export class CollectionFreeFormView extends CollectionSubView p.X)), Math.max(...ge.points.map(p => p.Y))); - const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color); - const sets = setDocs.map((sd) => { + const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color); + const sets = setDocs.map(sd => { return Cast(sd.text, RichTextField)?.Text as string; }); if (sets.length && sets[0]) { this._wordPalette.clear(); const colors = setDocs.map(sd => FieldValue(sd.color) as string); - sets.forEach((st: string, i: number) => st.split(",").forEach(word => this._wordPalette.set(word, colors[i]))); + sets.forEach((st: string, i: number) => st.split(',').forEach(word => this._wordPalette.set(word, colors[i]))); } const inks = this.getActiveDocuments().filter(doc => { - if (doc.type === "ink") { + if (doc.type === 'ink') { const l = NumCast(doc.x); const r = l + doc[WidthSym](); const t = NumCast(doc.y); @@ -599,15 +648,15 @@ export class CollectionFreeFormView extends CollectionSubView pd.X) ?? [0]); - const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); + const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); + const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); if (d) { strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top }))); } }); - CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => { - const wordResults = results.filter((r: any) => r.category === "inkWord"); + CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => { + const wordResults = results.filter((r: any) => r.category === 'inkWord'); for (const word of wordResults) { const indices: number[] = word.strokeIds; indices.forEach(i => { @@ -619,8 +668,7 @@ export class CollectionFreeFormView extends CollectionSubView(uniqueColors); if (this._wordPalette.has(word.recognizedText.toLowerCase())) { inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase()); - } - else if (word.alternates) { + } else if (word.alternates) { for (const alt of word.alternates) { if (this._wordPalette.has(alt.recognizedString.toLowerCase())) { inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase()); @@ -641,27 +689,27 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener("pointermove", this.onEraserMove); - document.removeEventListener("pointerup", this.onEraserUp); + document.removeEventListener('pointermove', this.onEraserMove); + document.removeEventListener('pointerup', this.onEraserUp); this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc)); this._deleteList = []; this._batch?.end(); } - } + }; @action onPointerUp = (e: PointerEvent): void => { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener('pointermove', this.onPointerMove); + document.removeEventListener('pointerup', this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); } - } + }; onClick = (e: React.MouseEvent) => { if (this.onBrowseClickHandler()) { @@ -670,10 +718,10 @@ export class CollectionFreeFormView extends CollectionSubView { + pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => { const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true); this._lastX = e.clientX; this._lastY = e.clientY; - } + }; /** * Erases strokes by intersecting them with an invisible "eraser stroke". @@ -703,14 +751,16 @@ export class CollectionFreeFormView extends CollectionSubView { if (!this._deleteList.includes(intersect.inkView)) { this._deleteList.push(intersect.inkView); - SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || "1"); - SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || "black"); + SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1'); + SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black'); // create a new curve by appending all curves of the current segment together in order to render a single new stroke. - !e.shiftKey && this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment => - GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, - segment.reduce((data, curve) => [...data, ...curve.points - .map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 }) - ], [] as PointData[]))); + !e.shiftKey && + this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment => + GestureOverlay.Instance.dispatchGesture( + GestureUtils.Gestures.Stroke, + segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) + ) + ); // Lower ink opacity to give the user a visual indicator of deletion. intersect.inkView.layoutDoc.opacity = 0.5; } @@ -720,7 +770,7 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -730,45 +780,54 @@ export class CollectionFreeFormView extends CollectionSubView { + getEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => { const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) }; const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) }; return this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) - .filter(({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap - eraserMin.X <= inkViewBounds.right && eraserMin.Y <= inkViewBounds.bottom && - eraserMax.X >= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top) + .filter( + ({ inkViewBounds }) => + inkViewBounds && // bounding box of eraser segment and ink stroke overlap + eraserMin.X <= inkViewBounds.right && + eraserMin.Y <= inkViewBounds.bottom && + eraserMax.X >= inkViewBounds.left && + eraserMax.Y >= inkViewBounds.top + ) .reduce((intersections, { inkStroke, inkView }) => { const { inkData } = inkStroke.inkScaledData(); // Convert from screen space to ink space for the intersection. const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); const currPointInkSpace = inkStroke.ptFromScreen(currPoint); for (var i = 0; i < inkData.length - 3; i += 4) { - const intersects = Array.from(new Set(InkField.Segment(inkData, i).intersects({ // compute all unique intersections - p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, - p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y } - }) as (number | string)[])); // convert to more manageable union array type + const intersects = Array.from( + new Set( + InkField.Segment(inkData, i).intersects({ + // compute all unique intersections + p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, + p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, + }) as (number | string)[] + ) + ); // convert to more manageable union array type // return tuples of the inkingStroke intersected, and the t value of the intersection - intersections.push(...intersects.map(t => ({ inkView, t: (+t) + Math.floor(i / 4) })));// convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve + intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } return intersections; - }, [] as { t: number, inkView: DocumentView }[]); - } + }, [] as { t: number; inkView: DocumentView }[]); + }; /** * Performs segmentation of the ink stroke - creates "segments" or subsections of the current ink stroke at points in which the @@ -805,11 +864,11 @@ export class CollectionFreeFormView extends CollectionSubView (inkData.length / 4)) { + if (excludeT < startSegmentT || excludeT > inkData.length / 4) { segment.length && segments.push(segment); } return segments; - } + }; /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all @@ -837,13 +896,13 @@ export class CollectionFreeFormView extends CollectionSubView ({ x: p.X, y: p.Y }))); curve.intersects(otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. - const t = +val.toString().split("/")[0]; - if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). + const t = +val.toString().split('/')[0]; + if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). }); } }); return tVals; - } + }; handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (!e.cancelBubble) { @@ -853,8 +912,8 @@ export class CollectionFreeFormView extends CollectionSubView only want to do this when collection is selected' @@ -864,7 +923,7 @@ export class CollectionFreeFormView extends CollectionSubView) => { // pinch zooming @@ -887,7 +946,7 @@ export class CollectionFreeFormView extends CollectionSubView) => { @@ -934,21 +994,20 @@ export class CollectionFreeFormView extends CollectionSubView { switch (this._pullDirection) { - case "left": case "right": case "top": case "bottom": - CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection); + case 'left': + case 'right': + case 'top': + case 'bottom': + CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: 'New Collection' }), this._pullDirection); } - this._pullDirection = ""; + this._pullDirection = ''; this._pullCoords = [0, 0]; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener('pointermove', this.onPointerMove); + document.removeEventListener('pointerup', this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); - } + }; @action zoom = (pointX: number, pointY: number, deltaY: number): void => { if (this.Document._isGroup) return; - let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05; + let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; const [x, y] = this.getTransform().transformPoint(pointX, pointY); const invTransform = this.getLocalTransform().inverse(); @@ -996,27 +1058,28 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; - if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) + return; + if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { + // things that can scroll vertically should do that instead of zooming e.stopPropagation(); - } - else if (this.props.isContentActive(true) && !this.Document._isGroup) { + } else if (this.props.isContentActive(true) && !this.Document._isGroup) { e.stopPropagation(); e.preventDefault(); !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? } - } + }; @action setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { // set the current respective FFview to the tab being panned. - (Doc.UserDoc()?.presentationMode === 'recording') && RecordingApi.Instance.setRecordingFFView(this); + Doc.UserDoc()?.presentationMode === 'recording' && RecordingApi.Instance.setRecordingFFView(this); // TODO: make this based off the specific recording FFView - (Doc.UserDoc()?.presentationMode === 'none') && RecordingApi.Instance.setPlayFFView(this); + Doc.UserDoc()?.presentationMode === 'none' && RecordingApi.Instance.setPlayFFView(this); if (Doc.UserDoc()?.presentationMode === 'watching') { RecordingApi.Instance.pauseVideoAndMovements(); Doc.UserDoc().presentationMode = 'none'; @@ -1026,24 +1089,30 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(doc => doc instanceof Doc); - const measuredDocs = docs.map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ""), size: this.childSizeProviderUnmemoized(doc, "") })) - .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! })); + const measuredDocs = docs + .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') })) + .filter(({ pos, size }) => pos && size) + .map(({ pos, size }) => ({ pos: pos!, size: size! })); if (measuredDocs.length) { - const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content - ({ - xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) }, - yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) } - }) - , { + const ranges = measuredDocs.reduce( + ( + { xrange, yrange }, + { pos, size } // computes range of content + ) => ({ + xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) }, + yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }, + }), + { xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, - yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE } - }); + yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, + } + ); const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()]; - if (ranges.xrange.min >= (panX + panelDim[0] / 2)) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds - else if (ranges.xrange.max <= (panX - panelDim[0] / 2)) panX = ranges.xrange.min - panelDim[0] / 2; - if (ranges.yrange.min >= (panY + panelDim[1] / 2)) panY = ranges.yrange.max + panelDim[1] / 2; - else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2; + if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds + else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2; + if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2; + else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2; } } if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.Document)) { @@ -1052,10 +1121,8 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || - this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out... - this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(), - NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), nudgeTime, true); + if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) { + // bcz: this isn't ideal, but want to try it out... + this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true); this._lastNudge && clearTimeout(this._lastNudge); - this._lastNudge = setTimeout(action(() => this._viewTransition = 0), nudgeTime); + this._lastNudge = setTimeout( + action(() => (this._viewTransition = 0)), + nudgeTime + ); return true; } return false; - } + }; @action bringToFront = (doc: Doc, sendToBack?: boolean) => { @@ -1090,12 +1159,15 @@ export class CollectionFreeFormView extends CollectionSubView this._viewTransition = 0), this._viewTransition = transitionTime); // set transition to be smooth, then reset + setTimeout( + action(() => (this._viewTransition = 0)), + (this._viewTransition = transitionTime) + ); // set transition to be smooth, then reset const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); this.layoutDoc[this.scaleFieldKey] = scale; const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); @@ -1110,7 +1182,7 @@ export class CollectionFreeFormView extends CollectionSubView this._viewTransition = 0); + runInAction(() => (this._viewTransition = 0)); } return resetView; }; - const xf = !cantTransform ? Transform.Identity() : - this.props.isAnnotationOverlay ? - new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) - : - new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), - NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); + const xf = !cantTransform + ? Transform.Identity() + : this.props.isAnnotationOverlay + ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) + : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); this.props.focus(cantTransform ? doc : this.rootDoc, { ...options, docTransform: xf, - afterFocus: (didFocus: boolean) => new Promise(res => - setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))) + afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), }); } - } + }; calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { const layoutdoc = Doc.Layout(doc); @@ -1185,11 +1256,11 @@ export class CollectionFreeFormView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); @@ -1217,190 +1287,215 @@ export class CollectionFreeFormView extends CollectionSubView { const docView = fieldProps.DocumentView?.(); - if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) { + if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ['Tab', 'Enter'].includes(e.key)) { e.stopPropagation?.(); - const below = !e.altKey && e.key !== "Tab"; + const below = !e.altKey && e.key !== 'Tab'; const layoutKey = StrCast(docView.LayoutFieldKey); const newDoc = Doc.MakeCopy(docView.rootDoc, true); const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10; else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10; - if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) { newDoc[layoutKey] = docView.rootDoc[layoutKey]; } Doc.GetProto(newDoc).text = undefined; FormattedTextBox.SelectOnLoad = newDoc[Id]; return this.addDocument?.(newDoc); } - } + }; pointerEvents = () => { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); - const pointerEvents = this.props.isContentActive() === false ? "none" : - this.props.childPointerEvents ? "all" : - (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.(); + const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ? 'all' : this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.(); return pointerEvents; - } + }; getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; - return ; + return ( + + ); } addDocTab = action((doc: Doc, where: string) => { - if (where === "inParent") { - ((doc instanceof Doc) ? [doc] : doc).forEach(doc => { + if (where === 'inParent') { + (doc instanceof Doc ? [doc] : doc).forEach(doc => { const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = pt[0]; doc.y = pt[1]; }); return this.props.addDocument?.(doc) || false; } - if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List(doc as any as Doc[]); return true; } return this.props.addDocTab(doc, where); }); - getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc }): PoolData { + getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { const layoutDoc = Doc.Layout(params.pair.layout); const { z, color, zIndex } = params.pair.layout; - const { x, y, opacity } = this.Document._currentFrame === undefined ? - { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } : - CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame)); + const { x, y, opacity } = + this.Document._currentFrame === undefined + ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } + : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame)); return { - x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"), - transition: StrCast(layoutDoc.dataTransition), opacity: this._keyframeEditing ? 1 : Cast(opacity, "number", null), - width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: "" + x: NumCast(x), + y: NumCast(y), + z: Cast(z, 'number'), + color: StrCast(color), + zIndex: Cast(zIndex, 'number'), + transition: StrCast(layoutDoc.dataTransition), + opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number', null), + width: Cast(layoutDoc._width, 'number'), + height: Cast(layoutDoc._height, 'number'), + pair: params.pair, + replica: '', }; } onViewDefDivClick = (e: React.MouseEvent, payload: any) => { (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload }); e.stopPropagation(); - } + }; viewDefsToJSX = (views: ViewDefBounds[]) => { return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!); - } + }; viewDefToJSX(viewDef: ViewDefBounds): Opt { const { x, y, z } = viewDef; const color = StrCast(viewDef.color); - const width = Cast(viewDef.width, "number"); - const height = Cast(viewDef.height, "number"); + const width = Cast(viewDef.width, 'number'); + const height = Cast(viewDef.height, 'number'); const transform = `translate(${x}px, ${y}px)`; - if (viewDef.type === "text") { - const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below - const fontSize = Cast(viewDef.fontSize, "string"); - return [text, x, y].some(val => val === undefined) ? undefined : - { - ele:
- {text} -
, - bounds: viewDef - }; - } else if (viewDef.type === "div") { - return [x, y].some(val => val === undefined) ? undefined : - { - ele:
this.onViewDefDivClick(e, viewDef)} - style={{ width, height, backgroundColor: color, transform }} />, - bounds: viewDef - }; + if (viewDef.type === 'text') { + const text = Cast(viewDef.text, 'string'); // don't use NumCast, StrCast, etc since we want to test for undefined below + const fontSize = Cast(viewDef.fontSize, 'string'); + return [text, x, y].some(val => val === undefined) + ? undefined + : { + ele: ( +
+ {text} +
+ ), + bounds: viewDef, + }; + } else if (viewDef.type === 'div') { + return [x, y].some(val => val === undefined) + ? undefined + : { + ele: ( +
this.onViewDefDivClick(e, viewDef)} + style={{ width, height, backgroundColor: color, transform }} + /> + ), + bounds: viewDef, + }; } } - - renderCutoffProvider = computedFn(function renderCutoffProvider(this: any, doc: Doc) { - return !this._renderCutoffData.get(doc[Id] + ""); - }.bind(this)); - + renderCutoffProvider = computedFn( + function renderCutoffProvider(this: any, doc: Doc) { + return !this._renderCutoffData.get(doc[Id] + ''); + }.bind(this) + ); childPositionProviderUnmemoized = (doc: Doc, replica: string) => { - return this._layoutPoolData.get(doc[Id] + (replica || "")); - } - childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) { - return this._layoutPoolData.get(doc[Id] + (replica || "")); - }.bind(this)); + return this._layoutPoolData.get(doc[Id] + (replica || '')); + }; + childDataProvider = computedFn( + function childDataProvider(this: any, doc: Doc, replica: string) { + return this._layoutPoolData.get(doc[Id] + (replica || '')); + }.bind(this) + ); childSizeProviderUnmemoized = (doc: Doc, replica: string) => { - return this._layoutSizeData.get(doc[Id] + (replica || "")); - } - childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) { - return this._layoutSizeData.get(doc[Id] + (replica || "")); - }.bind(this)); - - doEngineLayout(poolData: Map, - engine: ( - poolData: Map, - pivotDoc: Doc, - childPairs: { layout: Doc, data?: Doc }[], - panelDim: number[], - viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[]), - engineProps: any) => ViewDefResult[] + return this._layoutSizeData.get(doc[Id] + (replica || '')); + }; + childSizeProvider = computedFn( + function childSizeProvider(this: any, doc: Doc, replica: string) { + return this._layoutSizeData.get(doc[Id] + (replica || '')); + }.bind(this) + ); + + doEngineLayout( + poolData: Map, + engine: (poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[] ) { return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps); } doFreeformLayout(poolData: Map) { - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => - poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document }))); + this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document }))); return [] as ViewDefResult[]; } - @computed get layoutEngine() { return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); } + @computed get layoutEngine() { + return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); + } @computed get doInternalLayoutComputation() { TraceMobx(); const newPool = new Map(); switch (this.layoutEngine) { - case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; - case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; - case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; - case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; + case 'pass': + return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; + case 'timeline': + return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; + case 'pivot': + return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; + case 'starburst': + return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; } return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } @@ -1424,15 +1519,19 @@ export class CollectionFreeFormView extends CollectionSubView this._cachedPool.set(k[0], k[1])); const elements = computedElementData.slice(); - Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) => - elements.push({ - ele: this.getChildDocView(entry[1]), - bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) - })); + Array.from(newPool.entries()) + .filter(entry => this.isCurrent(entry[1].pair.layout)) + .forEach((entry, i) => + elements.push({ + ele: this.getChildDocView(entry[1]), + bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica), + }) + ); - if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar + if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { + // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling()); - else this.props.Document[this.scaleFieldKey] = Math.max(1,this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey])); + else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey])); } this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true); @@ -1453,60 +1552,69 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.props.Document.annotationOn) { return this.rootDoc; } - const anchor = Docs.Create.TextanchorDocument({ title: "ViewSpec - " + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); const proto = Doc.GetProto(anchor); - proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType; + proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List([]); - if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor); + if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); } else { - this.dataDoc[this.props.fieldKey + "-annotations"] = new List([anchor]); + this.dataDoc[this.props.fieldKey + '-annotations'] = new List([anchor]); } return anchor; - } + }; @action componentDidMount() { super.componentDidMount?.(); this.props.setContentView?.(this); - setTimeout(action(() => { - this._firstRender = false; - this._disposers.layoutComputation = reaction(() => this.doLayoutComputation, - (elements) => this._layoutElements = elements || [], - { fireImmediately: true, name: "doLayout" }); - - this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); - - this._disposers.groupBounds = reaction(() => { - if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) { - const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() })); - return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); - } - return undefined; - }, - (cbounds) => { - if (cbounds) { - const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; - const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; - const pbounds = { - x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], - r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1] - }; - this.layoutDoc._width = (pbounds.r - pbounds.x); - this.layoutDoc._height = (pbounds.b - pbounds.y); - this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; - this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; - this.layoutDoc.x = pbounds.x; - this.layoutDoc.y = pbounds.y; - } - }, { fireImmediately: true }); - })); + setTimeout( + action(() => { + this._firstRender = false; + this._disposers.layoutComputation = reaction( + () => this.doLayoutComputation, + elements => (this._layoutElements = elements || []), + { fireImmediately: true, name: 'doLayout' } + ); + + this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); + + this._disposers.groupBounds = reaction( + () => { + if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) { + const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() })); + return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); + } + return undefined; + }, + cbounds => { + if (cbounds) { + const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; + const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; + const pbounds = { + x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], + y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], + r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], + b: (cbounds.b - p[1]) * this.zoomScaling() + c[1], + }; + this.layoutDoc._width = pbounds.r - pbounds.x; + this.layoutDoc._height = pbounds.b - pbounds.y; + this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; + this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; + this.layoutDoc.x = pbounds.x; + this.layoutDoc.y = pbounds.y; + } + }, + { fireImmediately: true } + ); + }) + ); } static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { @@ -1516,15 +1624,15 @@ export class CollectionFreeFormView extends CollectionSubView CollectionFreeFormView.UpdateIcon( - this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), - this.props.docViewPath().lastElement().ContentDiv!, - this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), - this.props.PanelWidth(), this.props.PanelHeight(), 0, 1, false, "", - (iconFile, nativeWidth, nativeHeight) => { - this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }); + updateIcon = () => + CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + '-icon' + new Date().getTime(), + this.props.docViewPath().lastElement().ContentDiv!, + this.layoutDoc[WidthSym](), + this.layoutDoc[HeightSym](), + this.props.PanelWidth(), + this.props.PanelHeight(), + 0, + 1, + false, + '', + (iconFile, nativeWidth, nativeHeight) => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc['icon-nativeWidth'] = nativeWidth; + this.dataDoc['icon-nativeHeight'] = nativeHeight; + } + ); public static UpdateIcon( - filename:string, docViewContent:HTMLElement, - width: number, height: number, - panelWidth:number, panelHeight: number, - scrollTop:number, + filename: string, + docViewContent: HTMLElement, + width: number, + height: number, + panelWidth: number, + panelHeight: number, + scrollTop: number, realNativeHeight: number, noSuffix: boolean, - replaceRootFilename: string| undefined, - cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) - { + replaceRootFilename: string | undefined, + cb: (iconFile: string, nativeWidth: number, nativeHeight: number) => any + ) { const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; newDiv.style.width = width.toString(); newDiv.style.height = height.toString(); @@ -1563,15 +1682,8 @@ export class CollectionFreeFormView extends CollectionSubView { + return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) + .then(async (data_url: any) => { const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) @@ -1582,13 +1694,13 @@ export class CollectionFreeFormView extends CollectionSubView disposer?.()); - this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); + this._marqueeRef.current?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); } @action onCursorMove = (e: React.PointerEvent) => { // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); - } + }; @action onDragAutoScroll = (e: CustomEvent) => { @@ -1608,7 +1720,7 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1618,9 +1730,9 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1629,73 +1741,88 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(doc._height))) + 20; const dim = Math.ceil(Math.sqrt(docs.length)); docs.forEach((doc, i) => { - doc.x = NumCast(this.Document._panX) + (i % dim) * width - width * dim / 2; - doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - height * dim / 2; + doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2; + doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2; }); - } + }; @undoBatch - toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight) + toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight); onContextMenu = (e: React.MouseEvent) => { if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return; - const appearance = ContextMenu.Instance.findByDescription("Appearance..."); - const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; - appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); - !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); - appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" }); + const appearance = ContextMenu.Instance.findByDescription('Appearance...'); + const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; + appearanceItems.push({ + description: 'Reset View', + event: () => { + this.props.Document._panX = this.props.Document._panY = 0; + this.props.Document[this.scaleFieldKey] = 1; + }, + icon: 'compress-arrows-alt', + }); + !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' }); + appearanceItems.push({ + description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`, + event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox), + icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt', + }); + appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); - this.props.ContainingCollectionView && - appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); + this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); - this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: "Ink to text", event: () => this.transcribeStrokes(false), icon: "font" }); + this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' }); // this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" }); - !Doc.noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; - !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); - - const viewctrls = ContextMenu.Instance.findByDescription("UI Controls..."); - const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : []; - !Doc.noviceMode ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? "Hide" : "Show") + " Snap Lines", event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: "compress-arrows-alt" }) : null; - !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; - !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); - - const options = ContextMenu.Instance.findByDescription("Options..."); - const optionItems = options && "subitems" in options ? options.subitems : []; - !this.props.isAnnotationOverlay && !Doc.noviceMode && - optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" }); - this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); + !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null; + !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); + + const viewctrls = ContextMenu.Instance.findByDescription('UI Controls...'); + const viewCtrlItems = viewctrls && 'subitems' in viewctrls ? viewctrls.subitems : []; + !Doc.noviceMode + ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? 'Hide' : 'Show') + ' Snap Lines', event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: 'compress-arrows-alt' }) + : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._useClusters), icon: 'braille' }) : null; + !viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' }); + + const options = ContextMenu.Instance.findByDescription('Options...'); + const optionItems = options && 'subitems' in options ? options.subitems : []; + !this.props.isAnnotationOverlay && + !Doc.noviceMode && + optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' }); + this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' }); if (!Doc.noviceMode) { - optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); + optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' }); } - !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); - const mores = ContextMenu.Instance.findByDescription("More..."); - const moreItems = mores && "subitems" in mores ? mores.subitems : []; + !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); + const mores = ContextMenu.Instance.findByDescription('More...'); + const moreItems = mores && 'subitems' in mores ? mores.subitems : []; if (!Doc.noviceMode) { e.persist(); - moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) }); - moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) }); + moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); + moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) }); } - !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); - } + !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' }); + }; importDocument = (x: number, y: number) => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".zip"; + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.zip'; input.onchange = _e => { - input.files && Doc.importDocument(input.files[0]).then(doc => { - if (doc instanceof Doc) { - const [xx, yy] = this.getTransform().transformPoint(x, y); - doc.x = xx, doc.y = yy; - this.props.addDocument?.(doc);} - }); + input.files && + Doc.importDocument(input.files[0]).then(doc => { + if (doc instanceof Doc) { + const [xx, yy] = this.getTransform().transformPoint(x, y); + (doc.x = xx), (doc.y = yy); + this.props.addDocument?.(doc); + } + }); }; input.click(); - } + }; @undoBatch @action @@ -1704,13 +1831,13 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1718,37 +1845,39 @@ export class CollectionFreeFormView extends CollectionSubView ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); - const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect); + const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect); const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs const horizLines: number[] = []; const vertLines: number[] = []; const invXf = this.getTransform().inverse(); - snappableDocs.filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { - const { left, top, width, height } = docDims(doc); - const topLeftInScreen = invXf.transformPoint(left, top); - const docSize = invXf.transformDirection(width, height); + snappableDocs + .filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)) + .forEach(doc => { + const { left, top, width, height } = docDims(doc); + const topLeftInScreen = invXf.transformPoint(left, top); + const docSize = invXf.transformDirection(width, height); - horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line - vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line - }); + horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line + vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line + }); DragManager.SetSnapLines(horizLines, vertLines); - } + }; onPointerOver = (e: React.PointerEvent) => { e.stopPropagation(); - } + }; incrementalRender = action(() => { if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); const loadIncrement = 5; for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) { - this._renderCutoffData.set(unrendered[i][Id] + "", true); + this._renderCutoffData.set(unrendered[i][Id] + '', true); } } this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); @@ -1756,64 +1885,67 @@ export class CollectionFreeFormView extends CollectionSubView { this.incrementalRender(); - const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; - return [ - ...children, - ...this.views, - - ]; - } + const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : []; + return [...children, ...this.views, ]; + }; @computed get placeholder() { - return
- {this.props.Document.title?.toString()} -
; + return ( +
+ {this.props.Document.title?.toString()} +
+ ); } @computed get marqueeView() { TraceMobx(); - return 0 ? undefined : this.nudge} - addDocTab={this.addDocTab} - trySelectCluster={this.trySelectCluster} - activeDocuments={this.getActiveDocuments} - selectDocuments={this.selectDocuments} - addDocument={this.addDocument} - addLiveTextDocument={this.addLiveTextBox} - getContainerTransform={this.getContainerTransform} - getTransform={this.getTransform} - isAnnotationOverlay={this.isAnnotationOverlay}> -
- {this.layoutDoc._backgroundGridShow ? -
0 ? undefined : this.nudge} + addDocTab={this.addDocTab} + trySelectCluster={this.trySelectCluster} + activeDocuments={this.getActiveDocuments} + selectDocuments={this.selectDocuments} + addDocument={this.addDocument} + addLiveTextDocument={this.addLiveTextBox} + getContainerTransform={this.getContainerTransform} + getTransform={this.getTransform} + isAnnotationOverlay={this.isAnnotationOverlay}> +
+ {this.layoutDoc._backgroundGridShow ? ( +
+ +
+ ) : null} +
: (null)} - - {this.children} - -
- {this._showAnimTimeline ? : (null)} - ; + isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable} + transform={this.contentTransform} + zoomScaling={this.zoomScaling} + presPaths={BoolCast(this.Document.presPathView)} + progressivize={BoolCast(this.Document.editProgressivize)} + presPinView={BoolCast(this.Document.presPinView)} + transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)} + viewDefDivClick={this.props.viewDefDivClick}> + {this.children} + +
+ {this._showAnimTimeline ? : null} +
+ ); } @computed get contentScaling() { @@ -1826,66 +1958,83 @@ export class CollectionFreeFormView extends CollectionSubView { //used for stacking and masonry view + protected createGroupEventsTarget = (ele: HTMLDivElement) => { + //used for stacking and masonry view this.groupDropDisposer?.(); if (ele) { this.groupDropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this)); } - } + }; render() { TraceMobx(); const clientRect = this._mainCont?.getBoundingClientRect(); - return
e.preventDefault()} - onContextMenu={this.onContextMenu} - style={{ - pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach. - (SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())) ? "all" : this.props.pointerEvents?.() as any, - transform: `scale(${this.contentScaling || 1})`, - width: `${100 / (this.contentScaling || 1)}%`, - height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() - }}> - {this._firstRender ? - this.placeholder : this.marqueeView} - {this.props.noOverlay ? (null) : } - - -
e.preventDefault()} + onContextMenu={this.onContextMenu} style={{ - display: this._pullDirection ? "block" : "none", - top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto", - left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto", - width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0, - height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0, - + pointerEvents: + this.props.Document.type === DocumentType.MARKER + ? 'none' // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach. + : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) + ? 'all' + : (this.props.pointerEvents?.() as any), + transform: `scale(${this.contentScaling || 1})`, + width: `${100 / (this.contentScaling || 1)}%`, + height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}> + {this._firstRender ? this.placeholder : this.marqueeView} + {this.props.noOverlay ? null : } + +
+ { + // uncomment to show snap lines +
+ + {this._hLines?.map(l => ( + + ))} + {this._vLines?.map(l => ( + + ))} + +
+ } + + {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? ( +
+ ) : null}
- {// uncomment to show snap lines -
- - {this._hLines?.map(l => )} - {this._vLines?.map(l => )} - -
} - - {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? -
: (null)} -
; + ); } } @@ -1894,9 +2043,12 @@ interface CollectionFreeFormOverlayViewProps { } @observer -class CollectionFreeFormOverlayView extends React.Component{ +class CollectionFreeFormOverlayView extends React.Component { render() { - return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); + return this.props + .elements() + .filter(ele => ele.bounds?.z) + .map(ele => ele.ele); } } @@ -1914,50 +2066,50 @@ interface CollectionFreeFormViewPannableContentsProps { } @observer -class CollectionFreeFormViewPannableContents extends React.Component{ +class CollectionFreeFormViewPannableContents extends React.Component { @observable _drag: string = ''; //Adds event listener so knows pointer is down and moving onPointerDown = (e: React.PointerEvent): void => { e.stopPropagation(); e.preventDefault(); - this._drag = (e.target as any)?.id ?? ""; + this._drag = (e.target as any)?.id ?? ''; document.getElementById(this._drag) && setupMoveUpEvents(e.target, e, this.onPointerMove, emptyFunction, emptyFunction); - } + }; //Adjusts the value in NodeStore @action onPointerMove = (e: PointerEvent) => { const doc = document.getElementById('resizable'); - const toNumber = (original: number, delta: number) => original + (delta * this.props.zoomScaling()); + const toNumber = (original: number, delta: number) => original + delta * this.props.zoomScaling(); if (doc) { switch (this._drag) { - case "resizer-br": + case 'resizer-br': doc.style.width = toNumber(doc.offsetWidth, e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px'; break; - case "resizer-bl": + case 'resizer-bl': doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px'; doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px'; break; - case "resizer-tr": + case 'resizer-tr': doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px'; doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px'; - case "resizer-tl": + case 'resizer-tl': doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px'; doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px'; doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px'; - case "resizable": + case 'resizable': doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px'; doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px'; } return false; } return true; - } + }; // scale: NumCast(targetDoc._viewScale), @computed get zoomProgressivizeContainer() { @@ -1968,66 +2120,73 @@ class CollectionFreeFormViewPannableContents extends React.Component -
-
-
-
-
+
+
+
+
+
-
; +
+ ); } } @computed get zoomProgressivize() { - return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : (null); + return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : null; } @computed get progressivize() { - return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : (null); + return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : null; } @computed get presPaths() { - const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden"); - return !PresBox.Instance || !this.props.presPaths ? (null) : <> -
{PresBox.Instance.order}
- - - - - - - - - - - - - {PresBox.Instance.paths} - - ; + const presPaths = 'presPaths' + (this.props.presPaths ? '' : '-hidden'); + return !PresBox.Instance || !this.props.presPaths ? null : ( + <> +
{PresBox.Instance.order}
+ + + + + + + + + + + + + {PresBox.Instance.paths} + + + ); } render() { - return
{ - const target = e.target as any; - if (getComputedStyle(target)?.overflow === "visible") { // if collection is visible, then scrolling will mess things up since there are no scroll bars - target.scrollTop = target.scrollLeft = 0; - } - }} - style={{ - transform: this.props.transform(), - transition: this.props.transition, - width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection - //willChange: "transform" - }}> - {this.props.children()} - {this.presPaths} - {this.progressivize} - {this.zoomProgressivize} -
; + return ( +
{ + const target = e.target as any; + if (getComputedStyle(target)?.overflow === 'visible') { + // if collection is visible, then scrolling will mess things up since there are no scroll bars + target.scrollTop = target.scrollLeft = 0; + } + }} + style={{ + transform: this.props.transform(), + transition: this.props.transition, + width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection + //willChange: "transform" + }}> + {this.props.children()} + {this.presPaths} + {this.progressivize} + {this.zoomProgressivize} +
+ ); } } @@ -2044,63 +2203,73 @@ interface CollectionFreeFormViewBackgroundGridProps { } @observer class CollectionFreeFormBackgroundGrid extends React.Component { - - chooseGridSpace = (gridSpace: number): number => { if (!this.props.zoomScaling()) return 50; const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3; return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); - } + }; render() { - const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc["_backgroundGrid-spacing"], 50)); - const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.props.panX() % gridSpace - gridSpace) * this.props.zoomScaling(); - const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.props.panY() % gridSpace - gridSpace) * this.props.zoomScaling(); + const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50)); + const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling(); + const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling(); const renderGridSpace = gridSpace * this.props.zoomScaling(); const w = this.props.PanelWidth() + 2 * renderGridSpace; const h = this.props.PanelHeight() + 2 * renderGridSpace; - const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)"; - return { - const ctx = el?.getContext('2d'); - if (ctx) { - const Cx = this.props.cachedCenteringShiftX % renderGridSpace; - const Cy = this.props.cachedCenteringShiftY % renderGridSpace; - ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); - ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); - ctx.clearRect(0, 0, w, h); + const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)'; + return ( + { + const ctx = el?.getContext('2d'); if (ctx) { - ctx.strokeStyle = strokeStyle; - ctx.beginPath(); - for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { - ctx.moveTo(x, Cy - h); - ctx.lineTo(x, Cy + h); + const Cx = this.props.cachedCenteringShiftX % renderGridSpace; + const Cy = this.props.cachedCenteringShiftY % renderGridSpace; + ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); + ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); + ctx.clearRect(0, 0, w, h); + if (ctx) { + ctx.strokeStyle = strokeStyle; + ctx.beginPath(); + for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { + ctx.moveTo(x, Cy - h); + ctx.lineTo(x, Cy + h); + } + for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { + ctx.moveTo(Cx - w, y); + ctx.lineTo(Cx + w, y); + } + ctx.stroke(); } - for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { - ctx.moveTo(Cx - w, y); - ctx.lineTo(Cx + w, y); - } - ctx.stroke(); } - } - }} />; + }} + /> + ); } } export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { SelectionManager.DeselectAll(); dv.props.focus(dv.props.Document, { - willZoom: true, afterFocus: async (didMove) => { + willZoom: true, + afterFocus: async didMove => { if (!didMove) { const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || "_viewScale"] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview + const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5); } return ViewAdjustment.doNothing; - } + }, }); Doc.linkFollowHighlight(dv?.props.Document, false); } ScriptingGlobals.add(CollectionBrowseClick); -ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); }); -ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); \ No newline at end of file +ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { + !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); +}); +ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { + !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); +}); -- cgit v1.2.3-70-g09d2 From 31804518309794b3356c8f7159485a376dc21f2e Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Fri, 1 Jul 2022 12:23:28 -0700 Subject: readded removed docs --- src/client/util/RecordingApi.ts | 269 +++++++++++++++++++++++++++++++++++++ src/client/views/nodes/DataViz.tsx | 20 +++ 2 files changed, 289 insertions(+) create mode 100644 src/client/util/RecordingApi.ts create mode 100644 src/client/views/nodes/DataViz.tsx (limited to 'src') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts new file mode 100644 index 000000000..7bffb0379 --- /dev/null +++ b/src/client/util/RecordingApi.ts @@ -0,0 +1,269 @@ +import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; +import { IReactionDisposer, observable, reaction } from 'mobx'; +import { NumCast } from '../../fields/Types'; +import { Doc } from '../../fields/Doc'; +import { VideoBox } from '../views/nodes/VideoBox'; +import { scaleDiverging } from 'd3-scale'; +import { Transform } from './Transform'; + +type Movement = { + time: number; + panX: number; + panY: number; + scale: number; +}; + +type Presentation = { + movements: Array | null; + meta: Object; +}; + +export class RecordingApi { + private static NULL_PRESENTATION: Presentation = { + movements: null, + meta: {}, + }; + + // instance variables + private currentPresentation: Presentation; + private isRecording: boolean; + private absoluteStart: number; + + // create static instance and getter for global use + @observable static _instance: RecordingApi; + public static get Instance(): RecordingApi { + return RecordingApi._instance; + } + public constructor() { + // init the global instance + RecordingApi._instance = this; + + // init the instance variables + this.currentPresentation = RecordingApi.NULL_PRESENTATION; + this.isRecording = false; + this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; + this.recordingFFView = null; + + // for now, set playFFView + this.playFFView = null; + this.timers = null; + } + + // little helper :) + private get isInitPresenation(): boolean { + return this.currentPresentation.movements === null; + } + + public start = (meta?: Object): Error | undefined => { + // check if already init a presentation + if (!this.isInitPresenation) { + console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.'); + return new Error('[recordingApi.ts] start()'); + } + + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording'; + + // (1a) get start date for presenation + const startDate = new Date(); + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime(); + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {}; + // (3) assign start date to currentPresenation + this.currentPresentation.movements = []; + // (4) set isRecording true to allow trackMovements + this.isRecording = true; + }; + + public clear = (): Error | Presentation => { + // TODO: maybe archive the data? + if (this.isRecording) { + console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first'); + return new Error('[recordingApi.ts] clear()'); + } + + // update the presentation mode + Doc.UserDoc().presentationMode = 'none'; + // set the previus recording view to the play view + this.playFFView = this.recordingFFView; + + const presCopy = { ...this.currentPresentation }; + + // clear presenation data + this.currentPresentation = RecordingApi.NULL_PRESENTATION; + // clear isRecording + this.isRecording = false; + // clear absoluteStart + this.absoluteStart = -1; + // clear the disposeFunc + this.removeRecordingFFView(); + + return presCopy; + }; + + public pause = (): Error | undefined => { + if (this.isInitPresenation) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first'); + return new Error('[recordingApi.ts] pause(): no presentation'); + } + // don't allow track movments + this.isRecording = false; + + // set adjust absoluteStart to add the time difference + const timestamp = new Date().getTime(); + this.absoluteStart = timestamp - this.absoluteStart; + }; + + public resume = () => { + this.isRecording = true; + // set absoluteStart to the difference in time + this.absoluteStart = new Date().getTime() - this.absoluteStart; + }; + + private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { + // ensure we are recording + if (!this.isRecording) { + return new Error('[recordingApi.ts] trackMovements()'); + } + // check to see if the presetation is init + if (this.isInitPresenation) { + return new Error('[recordingApi.ts] trackMovements(): no presentation'); + } + + // get the time + const time = new Date().getTime() - this.absoluteStart; + // make new movement object + const movement: Movement = { time, panX, panY, scale }; + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements && this.currentPresentation.movements.push(movement); + }; + + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === this.recordingFFView || view == null) return; + + // this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), + res => res.x !== -1 && res.y !== -1 && this.isRecording && this.trackMovements(res.x, res.y, res.scale) + ); + + // for now, set the most recent recordingFFView to the playFFView + this.recordingFFView = view; + }; + + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; + }; + + // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements + private playFFView: CollectionFreeFormView | null; + private timers: NodeJS.Timeout[] | null; + + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view; + }; + + // pausing movements will dispose all timers that are planned to replay the movements + // play movemvents will recreate them when the user resumes the presentation + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view'); + } + + if (!this._isPlaying) { + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return; + } + this._isPlaying = false; + // TODO: set userdoc presentMode to browsing + this.timers?.map(timer => clearTimeout(timer)); + + // this.videoBox = null; + }; + + private videoBox: VideoBox | null = null; + + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause(); + + this.pauseMovements(); + return this.videoBox == null; + }; + + public _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view'); + } + if (this._isPlaying) return; + + this._isPlaying = true; + Doc.UserDoc().presentationMode = 'watching'; + + // TODO: consider this bug at the end of the clip on seek + this.videoBox = videoBox || null; + + // only get the movements that are remaining in the video time left + const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000); + + // helper to replay a movement + const document = this.playFFView; + let preScale = -1; + const zoomAndPan = (movement: Movement) => { + const { panX, panY, scale } = movement; + scale !== -1 && preScale !== scale && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + preScale = scale; + }; + + // set the first frame to be at the start of the pres + zoomAndPan(filteredMovements[0]); + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed * 1000; + return setTimeout(() => { + // replay the movement + zoomAndPan(movement); + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + }, timeDiff); + }); + }; + + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } +} diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx new file mode 100644 index 000000000..df4c8f937 --- /dev/null +++ b/src/client/views/nodes/DataViz.tsx @@ -0,0 +1,20 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { ViewBoxBaseComponent } from '../DocComponent'; +import './DataViz.scss'; +import { FieldView, FieldViewProps } from './FieldView'; + +@observer +export class DataVizBox extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DataVizBox, fieldKey); + } + + render() { + return ( +
+
Hi
+
+ ); + } +} -- cgit v1.2.3-70-g09d2 From 955ec827382a50d0bda2cb657dbd1762b2477e59 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Fri, 1 Jul 2022 16:18:20 -0700 Subject: added resize handlers and linted document decorations --- src/Utils.ts | 15 ++++ src/client/views/DocumentDecorations.scss | 79 ++++++++++------------ src/client/views/DocumentDecorations.tsx | 70 ++++++++++++++----- src/client/views/global/globalEnums.tsx | 1 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 + 5 files changed, 107 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index b87980397..6699aa133 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -677,7 +677,22 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) { e.preventDefault(); } +/** + * Helper method for converting pixel string eg. '32px' into number eg. 32 + * @param value: string with 'px' ending + * @returns value: number + * + * Example: + * '32px' -> 32 + */ +export function numberValue(value: string | undefined):number { + if (value == undefined) return 0; + return parseInt(value); +} +export function numbersAlmostEqual(num1: number, num2: number) { + return Math.abs( num1 - num2 ) < 0.2; +} export function setupMoveUpEvents( target: object, diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 135d6d001..af4ceb0b5 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,6 +1,8 @@ @import 'global/globalCssVariables'; $linkGap: 3px; +$headerHeight: 20px; +$resizeHandler: 8px; .documentDecorations-Dark, .documentDecorations { @@ -16,8 +18,8 @@ $linkGap: 3px; top: 0; left: 0; display: grid; - grid-template-rows: 20px 8px 1fr 8px; - grid-template-columns: 8px 1fr 8px; + grid-template-rows: $headerHeight $resizeHandler 1fr $resizeHandler; + grid-template-columns: $resizeHandler 1fr $resizeHandler; pointer-events: none; .documentDecorations-centerCont { @@ -82,29 +84,44 @@ $linkGap: 3px; grid-column: 3; } - .documentDecorations-rotation, + // Rotation handler + .documentDecorations-rotation { + border-radius: 100%; + height: 30; + width: 30; + right: -10; + top: 50%; + z-index: 1000000; + position: absolute; + pointer-events: all; + cursor: pointer; + background: white; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + font-size: 30px; + } + + // Border radius handler .documentDecorations-borderRadius { - grid-column: 3; - grid-row: 4; + position: absolute; border-radius: 100%; - background: black; - height: 8; - right: -12; - top: 12; - position: relative; + background: $medium-gray; + height: 10; + width: 10; pointer-events: all; cursor: nwse-resize; - - .borderRadiusTooltip { - width: 10px; - height: 10px; - position: absolute; - } } - .documentDecorations-rotation { - background: transparent; - right: -15; + .documentDecorations-rotationPath { + position: absolute; + width: 100%; + height: 0; + transform: translate(0px, -25%); + padding-bottom: 100%; + border-radius: 100%; + border: solid $medium-gray 10px; } .documentDecorations-topLeftResizer, @@ -146,30 +163,6 @@ $linkGap: 3px; grid-column: 3; } - .documentDecorations-rotation, - .documentDecorations-borderRadius { - grid-column: 3; - grid-row: 4; - border-radius: 100%; - background: black; - height: 8; - right: -12; - top: 12; - position: relative; - pointer-events: all; - cursor: nwse-resize; - - .borderRadiusTooltip { - width: 10px; - height: 10px; - position: absolute; - } - } - .documentDecorations-rotation { - background: transparent; - right: -15; - } - .documentDecorations-topLeftResizer, .documentDecorations-bottomRightResizer { cursor: nwse-resize; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 17e135689..59675c986 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -10,7 +10,7 @@ import { InkField } from '../../fields/InkField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; import { Cast, FieldValue, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; -import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../Utils'; +import { emptyFunction, returnFalse, setupMoveUpEvents, numberValue, numbersAlmostEqual } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; @@ -30,6 +30,7 @@ import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); +import { Colors } from './global/globalEnums'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -56,6 +57,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @observable public pushIcon: IconProp = 'arrow-alt-circle-up'; @observable public pullIcon: IconProp = 'arrow-alt-circle-down'; @observable public pullColor: string = 'white'; + @observable private _showRotationPath: boolean = false; constructor(props: any) { super(props); @@ -262,27 +264,43 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P }; onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false); - + + /** + * Handles setting up events when user clicks on the border radius editor + * @param e PointerEvent + */ + @action onRadiusDown = (e: React.PointerEvent): void => { this._resizeUndo = UndoManager.StartBatch('DocDecs set radius'); + // Call util move event function setupMoveUpEvents( - this, - e, + this, // target + e, // pointerEvent (e, down) => { - const dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1])); + const x = this.Bounds.x + 3; + const y = this.Bounds.y + 3; + const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); + let dist = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y)); + if (e.clientX < x && e.clientY < y) dist = 0 SelectionManager.Views() .map(dv => dv.props.Document) - .map(doc => (doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc))) - .map(d => (d.borderRounding = `${Math.max(0, dist < 3 ? 0 : dist)}px`)); + .map(doc => { + const docMax = Math.min(NumCast(doc.width)/2, NumCast(doc.height)/2); + const ratio = dist/maxDist; + const radius = Math.min(1, ratio) * docMax; + doc.borderRounding = `${radius}px`; + } + ); return false; - }, - e => this._resizeUndo?.end(), - e => {} + }, // moveEvent + e => this._resizeUndo?.end(), // upEvent + e => {} // clickEvent ); }; @action onRotateDown = (e: React.PointerEvent): void => { + this._showRotationPath = true; const rotateUndo = UndoManager.StartBatch('rotatedown'); const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke); const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 }; @@ -299,11 +317,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P SelectionManager.Views().forEach(dv => (dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - (angle * 180) / Math.PI)); } return false; - }, + }, // moveEvent () => { + console.log('up') + action(() => this._showRotationPath = false); rotateUndo?.end(); UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); - }, + }, // upEvent emptyFunction ); }; @@ -614,11 +634,20 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); + // Rotation constants: Only allow rotation on ink and images const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox; - const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; - const rotation = NumCast(seldoc.rootDoc._jitterRotation); + + const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; + // Radius constants + const borderRadius = numberValue(StrCast(seldoc.rootDoc.borderRounding)); + const docMax = Math.min(NumCast(seldoc.rootDoc.width)/2, NumCast(seldoc.rootDoc.height)/2); + const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); + const radiusHandle = (borderRadius / docMax) * maxDist; + const radiusHandleLocation = Math.min(radiusHandle, maxDist) + const reachedMax:boolean = numbersAlmostEqual(radiusHandleLocation, maxDist); + console.log(reachedMax); return (
@@ -671,7 +700,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P {'⟲'}
)} -
e.preventDefault()} /> + {this._showRotationPath == true && ( +
+ +
+ )} +
e.preventDefault()} /> )} diff --git a/src/client/views/global/globalEnums.tsx b/src/client/views/global/globalEnums.tsx index 56779c37c..610c2b102 100644 --- a/src/client/views/global/globalEnums.tsx +++ b/src/client/views/global/globalEnums.tsx @@ -8,6 +8,7 @@ export enum Colors { MEDIUM_BLUE_ALT = "#4476f73d", // REDUCED OPACITY LIGHT_BLUE = "#BDDDF5", PINK = "#E0217D", + ERROR_RED = "#ff0033", YELLOW = "#F5D747", DROP_SHADOW = "#32323215", } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index bedc97575..284584a3d 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -170,6 +170,7 @@ export class CollectionFreeFormDocumentView extends DocComponent Date: Sat, 2 Jul 2022 10:41:40 -0400 Subject: fixing many eslint errors --- .eslintrc.json | 23 +- src/client/util/CurrentUserUtils.ts | 1 - .../CollectionSchemaMovableRow.tsx | 102 +- .../collections/collectionSchema/SchemaTable.tsx | 660 +++++++------ src/client/views/nodes/button/FontIconBox.tsx | 12 +- .../nodes/formattedText/DashDocCommentView.tsx | 77 +- .../views/nodes/formattedText/DashDocView.tsx | 181 ++-- .../views/nodes/formattedText/DashFieldView.tsx | 229 +++-- .../views/nodes/formattedText/EquationView.tsx | 99 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 1008 +++++++++++--------- .../formattedText/FormattedTextBoxComment.tsx | 107 ++- .../formattedText/ProsemirrorExampleTransfer.ts | 213 +++-- .../views/nodes/formattedText/RichTextMenu.tsx | 361 ++++--- .../views/nodes/formattedText/RichTextRules.ts | 574 ++++++----- .../views/nodes/formattedText/SummaryView.tsx | 73 +- src/client/views/nodes/formattedText/marks_rts.ts | 382 ++++---- src/client/views/nodes/formattedText/nodes_rts.ts | 361 ++++--- src/fields/Doc.ts | 865 +++++++++-------- src/fields/RichTextUtils.ts | 230 +++-- 19 files changed, 3033 insertions(+), 2525 deletions(-) (limited to 'src') diff --git a/.eslintrc.json b/.eslintrc.json index 5cc0ab6dc..b9f8e1b7a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,14 @@ { - "extends": ["airbnb", "prettier", "plugin:node/recommended"], - "plugins": ["prettier"], - "rules": { - "prettier/prettier": "error", - "no-unused-vars": "warn", - "no-console": "off", - "func-names": "off", - "no-process-exit": "off", - "object-shorthand": "off", - "class-methods-use-this": "off" - } + "extends": ["airbnb", "prettier", "plugin:node/recommended"], + "plugins": ["prettier"], + "rules": { + "prettier/prettier": "error", + "no-unused-vars": "warn", + "no-console": "off", + "func-names": "off", + "no-process-exit": "off", + "object-shorthand": "off", + "class-methods-use-this": "off", + "single-quote": "off" + } } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index dca77250c..b2a5fddcd 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -905,7 +905,6 @@ export class CurrentUserUtils { return doc.clickFuncs as Doc; } - /// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be /// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field /// whether to revert to "default" values, or to leave them as the user/system last set them. diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx index 0e19ef3d9..f872637e5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx @@ -1,17 +1,16 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action } from "mobx"; -import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table"; -import { Doc } from "../../../../fields/Doc"; -import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { Cast, FieldValue, StrCast } from "../../../../fields/Types"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager"; -import { SnappingManager } from "../../../util/SnappingManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { ContextMenu } from "../../ContextMenu"; -import "./CollectionSchemaView.scss"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action } from 'mobx'; +import * as React from 'react'; +import { ReactTableDefaults, RowInfo } from 'react-table'; +import { Doc } from '../../../../fields/Doc'; +import { Cast, FieldValue, StrCast } from '../../../../fields/Types'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { Transform } from '../../../util/Transform'; +import { undoBatch } from '../../../util/UndoManager'; +import { ContextMenu } from '../../ContextMenu'; +import './CollectionSchemaView.scss'; export interface MovableRowProps { rowInfo: RowInfo; @@ -25,7 +24,7 @@ export interface MovableRowProps { addDocTab: any; } -export class MovableRow extends React.Component { +export class MovableRow extends React.Component> { private _header?: React.RefObject = React.createRef(); private _rowDropDisposer?: DragManager.DragDropDisposer; @@ -33,28 +32,27 @@ export class MovableRow extends React.Component { // Create one when the mouse starts hovering... onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = "collectionSchema-row-wrapper"; - document.addEventListener("pointermove", this.onDragMove, true); + this._header!.current!.className = 'collectionSchema-row-wrapper'; + document.addEventListener('pointermove', this.onDragMove, true); } - } + }; // ... and delete it when the mouse leaves onPointerLeave = (e: React.PointerEvent): void => { - this._header!.current!.className = "collectionSchema-row-wrapper"; - document.removeEventListener("pointermove", this.onDragMove, true); - } + this._header!.current!.className = 'collectionSchema-row-wrapper'; + document.removeEventListener('pointermove', this.onDragMove, true); + }; // The method for the event listener, reorders columns when dragged to their new locations. onDragMove = (e: PointerEvent): void => { const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); const rect = this._header!.current!.getBoundingClientRect(); const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); const before = x[1] < bounds[1]; - this._header!.current!.className = "collectionSchema-row-wrapper"; - if (before) this._header!.current!.className += " row-above"; - if (!before) this._header!.current!.className += " row-below"; + this._header!.current!.className = 'collectionSchema-row-wrapper'; + if (before) this._header!.current!.className += ' row-above'; + if (!before) this._header!.current!.className += ' row-below'; e.stopPropagation(); - } + }; componentWillUnmount() { - this._rowDropDisposer?.(); } // @@ -63,7 +61,7 @@ export class MovableRow extends React.Component { if (ele) { this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } - } + }; // Controls what hppens when a row is dragged and dropped rowDrop = (e: Event, de: DragManager.DropEvent) => { this.onPointerLeave(e as any); @@ -81,34 +79,34 @@ export class MovableRow extends React.Component { if (docDragData.draggedDocuments[0] === rowDoc) return true; const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before); const movedDocs = docDragData.draggedDocuments; - return (docDragData.dropAction || docDragData.userDropAction) ? - docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) - : (docDragData.moveDocument) ? - movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false) - : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); + return docDragData.dropAction || docDragData.userDropAction + ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) + : docDragData.moveDocument + ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false) + : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); } return false; - } + }; onRowContextMenu = (e: React.MouseEvent): void => { - const description = this.props.rowWrapped ? "Unwrap text on row" : "Text wrap row"; - ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" }); - } + const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row'; + ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' }); + }; @undoBatch @action move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => { const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection); return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); - } + }; @action onKeyDown = (e: React.KeyboardEvent) => { - console.log("yes"); - if (e.key === "Backspace" || e.key === "Delete") { + console.log('yes'); + if (e.key === 'Backspace' || e.key === 'Delete') { undoBatch(() => this.props.removeDoc(this.props.rowInfo.original)); } - } + }; render() { const { children = null, rowInfo } = this.props; @@ -120,23 +118,29 @@ export class MovableRow extends React.Component { const { original } = rowInfo; const doc = FieldValue(Cast(original, Doc)); - if (!doc) return (null); + if (!doc) return null; const reference = React.createRef(); const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType); - let className = "collectionSchema-row"; - if (this.props.rowFocused) className += " row-focused"; - if (this.props.rowWrapped) className += " row-wrapped"; + let className = 'collectionSchema-row'; + if (this.props.rowFocused) className += ' row-focused'; + if (this.props.rowWrapped) className += ' row-wrapped'; return (
- +
-
this.props.removeDoc(this.props.rowInfo.original))}>
-
-
this.props.addDocTab(this.props.rowInfo.original, "add:right")}>
+
this.props.removeDoc(this.props.rowInfo.original))}> + +
+
+ +
+
this.props.addDocTab(this.props.rowInfo.original, 'add:right')}> + +
{children}
@@ -144,4 +148,4 @@ export class MovableRow extends React.Component {
); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 43266a571..fafea5ce3 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -1,37 +1,47 @@ -import React = require("react"); import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, trace } from "mobx"; -import { observer } from "mobx-react"; -import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table"; -import { DateField } from "../../../../fields/DateField"; -import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { List } from "../../../../fields/List"; -import { listSpec } from "../../../../fields/Schema"; -import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { ComputedField } from "../../../../fields/ScriptField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; -import { ImageField } from "../../../../fields/URLField"; -import { GetEffectiveAcl } from "../../../../fields/util"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../../Utils"; -import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { CompileScript, Transformer, ts } from "../../../util/Scripting"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; -import { ContextMenu } from "../../ContextMenu"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table'; +import { DateField } from '../../../../fields/DateField'; +import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; +import { listSpec } from '../../../../fields/Schema'; +import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { GetEffectiveAcl } from '../../../../fields/util'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils'; +import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { CompileScript, Transformer, ts } from '../../../util/Scripting'; +import { Transform } from '../../../util/Transform'; +import { undoBatch } from '../../../util/UndoManager'; import '../../../views/DocumentDecorations.scss'; -import { DocumentView } from "../../nodes/DocumentView"; -import { DefaultStyleProvider } from "../../StyleProvider"; -import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells"; -import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders"; -import { MovableColumn } from "./CollectionSchemaMovableColumn"; -import { MovableRow } from "./CollectionSchemaMovableRow"; -import "./CollectionSchemaView.scss"; -import { CollectionView } from "../CollectionView"; - +import { ContextMenu } from '../../ContextMenu'; +import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; +import { DocumentView } from '../../nodes/DocumentView'; +import { DefaultStyleProvider } from '../../StyleProvider'; +import { CollectionView } from '../CollectionView'; +import { + CellProps, + CollectionSchemaButtons, + CollectionSchemaCell, + CollectionSchemaCheckboxCell, + CollectionSchemaDateCell, + CollectionSchemaDocCell, + CollectionSchemaImageCell, + CollectionSchemaListCell, + CollectionSchemaNumberCell, + CollectionSchemaStringCell, +} from './CollectionSchemaCells'; +import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders'; +import { MovableColumn } from './CollectionSchemaMovableColumn'; +import { MovableRow } from './CollectionSchemaMovableRow'; +import './CollectionSchemaView.scss'; enum ColumnType { Any, @@ -41,15 +51,22 @@ enum ColumnType { Doc, Image, List, - Date + Date, } // this map should be used for keys that should have a const type of value const columnTypes: Map = new Map([ - ["title", ColumnType.String], - ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number], - ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], - ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number] + ['title', ColumnType.String], + ['x', ColumnType.Number], + ['y', ColumnType.Number], + ['_width', ColumnType.Number], + ['_height', ColumnType.Number], + ['_nativeWidth', ColumnType.Number], + ['_nativeHeight', ColumnType.Number], + ['isPrototype', ColumnType.Boolean], + ['_curPage', ColumnType.Number], + ['_currentTimecode', ColumnType.Number], + ['zIndex', ColumnType.Number], ]); export interface SchemaTableProps { @@ -92,18 +109,24 @@ export interface SchemaTableProps { @observer export class SchemaTable extends React.Component { @observable _cellIsEditing: boolean = false; - @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 }; - @observable _openCollections: Set = new Set; + @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 }; + @observable _openCollections: Set = new Set(); @observable _showDoc: Doc | undefined; - @observable _showDataDoc: any = ""; + @observable _showDataDoc: any = ''; @observable _showDocPos: number[] = []; @observable _showTitleDropdown: boolean = false; - @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); } - @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; } - @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); } + @computed get previewWidth() { + return () => NumCast(this.props.Document.schemaPreviewWidth); + } + @computed get previewHeight() { + return () => this.props.PanelHeight() - 2 * this.borderWidth; + } + @computed get tableWidth() { + return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); + } @computed get childDocs() { if (this.props.childDocs) return this.props.childDocs; @@ -117,17 +140,17 @@ export class SchemaTable extends React.Component { } @computed get textWrappedRows() { - return Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []); + return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); } set textWrappedRows(textWrappedRows: string[]) { this.props.Document.textwrappedSchemaRows = new List(textWrappedRows); } - @computed get resized(): { id: string, value: number }[] { + @computed get resized(): { id: string; value: number }[] { return this.props.columns.reduce((resized, shf) => { - (shf.width > -1) && resized.push({ id: shf.heading, value: shf.width }); + shf.width > -1 && resized.push({ id: shf.heading, value: shf.width }); return resized; - }, [] as { id: string, value: number }[]); + }, [] as { id: string; value: number }[]); } @computed get sorted(): SortingRule[] { return this.props.columns.reduce((sorted, shf) => { @@ -139,12 +162,14 @@ export class SchemaTable extends React.Component { @action changeSorting = (col: any) => { this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true); - } + }; @action - changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown + changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown); - @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } + @computed get borderWidth() { + return Number(COLLECTION_BORDER_WIDTH); + } @computed get tableColumns(): Column[] { const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); const columns: Column[] = []; @@ -154,149 +179,185 @@ export class SchemaTable extends React.Component { const isEditable = !this.props.headerIsEditing; columns.push({ - expander: true, Header: "", width: 58, - Expander: (rowInfo) => { - return rowInfo.original.type !== DocumentType.COL ? (null) : -
(this._openCollections[rowInfo.isExpanded ? "delete" : "add"])(rowInfo.viewIndex))}> - -
; - } + expander: true, + Header: '', + width: 58, + Expander: rowInfo => { + return rowInfo.original.type !== DocumentType.COL ? null : ( +
this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}> + +
+ ); + }, }); - columns.push(...this.props.columns.map(col => { - const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : - this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : - this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : - this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; - - const keysDropdown = c.heading)} - canAddNew={true} - addNew={false} - onSelect={this.props.changeColumns} - setIsEditing={this.props.setHeaderIsEditing} - docs={this.props.childDocs} - Document={this.props.Document} - dataDoc={this.props.dataDoc} - fieldKey={this.props.fieldKey} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - ContainingCollectionView={this.props.ContainingCollectionView} - active={this.props.active} - openHeader={this.props.openHeader} - icon={icon} - col={col} - // try commenting this out - width={"100%"} - />; - - const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up"; - const header =
- {keysDropdown} -
this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "pointer" }}> - -
- {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined :
+ new
} */} -
; - - return { - Header: , - accessor: (doc: Doc) => doc ? Field.toString(doc[col.heading] as Field) : 0, - id: col.heading, - Cell: (rowProps: CellInfo) => { - const rowIndex = rowProps.index; - const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); - const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; - - const props: CellProps = { - row: rowIndex, - col: columnIndex, - rowProps: rowProps, - isFocused: isFocused, - changeFocusedCellByIndex: this.changeFocusedCellByIndex, - CollectionView: this.props.CollectionView, - ContainingCollection: this.props.ContainingCollectionView, - Document: this.props.Document, - fieldKey: this.props.fieldKey, - renderDepth: this.props.renderDepth, - addDocTab: this.props.addDocTab, - pinToPres: this.props.pinToPres, - moveDocument: this.props.moveDocument, - setIsEditing: this.setCellIsEditing, - isEditable: isEditable, - setPreviewDoc: this.props.setPreviewDoc, - setComputed: this.setComputed, - getField: this.getField, - showDoc: this.showDoc, - }; - - - switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) { - case ColumnType.Number: return ; - case ColumnType.String: return ; - case ColumnType.Boolean: return ; - case ColumnType.Doc: return ; - case ColumnType.Image: return ; - case ColumnType.List: return ; - case ColumnType.Date: return ; - default: - return ; - } - }, - minWidth: 200, - }; - })); + columns.push( + ...this.props.columns.map(col => { + const icon: IconProp = + this.getColumnType(col) === ColumnType.Number + ? 'hashtag' + : this.getColumnType(col) === ColumnType.String + ? 'font' + : this.getColumnType(col) === ColumnType.Boolean + ? 'check-square' + : this.getColumnType(col) === ColumnType.Doc + ? 'file' + : this.getColumnType(col) === ColumnType.Image + ? 'image' + : this.getColumnType(col) === ColumnType.List + ? 'list-ul' + : this.getColumnType(col) === ColumnType.Date + ? 'calendar' + : 'align-justify'; + + const keysDropdown = ( + c.heading)} + canAddNew={true} + addNew={false} + onSelect={this.props.changeColumns} + setIsEditing={this.props.setHeaderIsEditing} + docs={this.props.childDocs} + Document={this.props.Document} + dataDoc={this.props.dataDoc} + fieldKey={this.props.fieldKey} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + ContainingCollectionView={this.props.ContainingCollectionView} + active={this.props.active} + openHeader={this.props.openHeader} + icon={icon} + col={col} + // try commenting this out + width={'100%'} + /> + ); + + const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up'; + const header = ( +
+ {keysDropdown} +
this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}> + +
+ {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined :
+ new
} */} +
+ ); + + return { + Header: , + accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0), + id: col.heading, + Cell: (rowProps: CellInfo) => { + const rowIndex = rowProps.index; + const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); + const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; + + const props: CellProps = { + row: rowIndex, + col: columnIndex, + rowProps: rowProps, + isFocused: isFocused, + changeFocusedCellByIndex: this.changeFocusedCellByIndex, + CollectionView: this.props.CollectionView, + ContainingCollection: this.props.ContainingCollectionView, + Document: this.props.Document, + fieldKey: this.props.fieldKey, + renderDepth: this.props.renderDepth, + addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, + moveDocument: this.props.moveDocument, + setIsEditing: this.setCellIsEditing, + isEditable: isEditable, + setPreviewDoc: this.props.setPreviewDoc, + setComputed: this.setComputed, + getField: this.getField, + showDoc: this.showDoc, + }; + + switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) { + case ColumnType.Number: + return ; + case ColumnType.String: + return ; + case ColumnType.Boolean: + return ; + case ColumnType.Doc: + return ; + case ColumnType.Image: + return ; + case ColumnType.List: + return ; + case ColumnType.Date: + return ; + default: + return ; + } + }, + minWidth: 200, + }; + }) + ); columns.push({ Header: , accessor: (doc: Doc) => 0, - id: "add", + id: 'add', Cell: (rowProps: CellInfo) => { const rowIndex = rowProps.index; const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; - return ; + return ( + + ); }, width: 28, - resizable: false + resizable: false, }); return columns; } - constructor(props: SchemaTableProps) { super(props); if (this.props.Document._schemaHeaders === undefined) { - this.props.Document._schemaHeaders = new List([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb", ColumnType.Date), - new SchemaHeaderField("text", "#f1efeb", ColumnType.String), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb", ColumnType.Doc)]); + this.props.Document._schemaHeaders = new List([ + new SchemaHeaderField('title', '#f1efeb'), + new SchemaHeaderField('author', '#f1efeb'), + new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date), + new SchemaHeaderField('text', '#f1efeb', ColumnType.String), + new SchemaHeaderField('type', '#f1efeb'), + new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc), + ]); } } componentDidMount() { - document.addEventListener("keydown", this.onKeyDown); + document.addEventListener('keydown', this.onKeyDown); } componentWillUnmount() { - document.removeEventListener("keydown", this.onKeyDown); + document.removeEventListener('keydown', this.onKeyDown); } tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => { @@ -305,25 +366,27 @@ export class SchemaTable extends React.Component { if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) { doc.context = this.props.Document; - tableDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now())); return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); } return false; - } + }; private getTrProps: ComponentPropsGetterR = (state, rowInfo) => { - return !rowInfo ? {} : { - ScreenToLocalTransform: this.props.ScreenToLocalTransform, - addDoc: this.tableAddDoc, - removeDoc: this.props.deleteDocument, - rowInfo, - rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), - textWrapRow: this.toggleTextWrapRow, - rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, - dropAction: StrCast(this.props.Document.childDropAction), - addDocTab: this.props.addDocTab - }; - } + return !rowInfo + ? {} + : { + ScreenToLocalTransform: this.props.ScreenToLocalTransform, + addDoc: this.tableAddDoc, + removeDoc: this.props.deleteDocument, + rowInfo, + rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), + textWrapRow: this.toggleTextWrapRow, + rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, + dropAction: StrCast(this.props.Document.childDropAction), + addDocTab: this.props.addDocTab, + }; + }; private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => { if (!rowInfo || column) return {}; @@ -334,16 +397,17 @@ export class SchemaTable extends React.Component { const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true); // TODO: editing border doesn't work :( return { - style: { border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb" } + style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' }, }; - } + }; - @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing; + @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing); @action onKeyDown = (e: KeyboardEvent): void => { - if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {// && this.props.isSelected(true)) { - const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : ""; + if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) { + // && this.props.isSelected(true)) { + const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : ''; this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); if (direction) { @@ -353,20 +417,25 @@ export class SchemaTable extends React.Component { } } else if (e.keyCode === 27) { this.props.setPreviewDoc(undefined); - e.stopPropagation(); // stopPropagation for left/right arrows + e.stopPropagation(); // stopPropagation for left/right arrows } - } + }; changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => { switch (direction) { - case "tab": return { row: (curRow + 1 === this.childDocs.length ? 0 : curRow + 1), col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 }; - case "right": return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 }; - case "left": return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 }; - case "up": return { row: curRow === 0 ? curRow : curRow - 1, col: curCol }; - case "down": return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol }; + case 'tab': + return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 }; + case 'right': + return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 }; + case 'left': + return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 }; + case 'up': + return { row: curRow === 0 ? curRow : curRow - 1, col: curCol }; + case 'down': + return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol }; } return this._focusedCell; - } + }; @action changeFocusedCellByIndex = (row: number, col: number): void => { @@ -374,25 +443,25 @@ export class SchemaTable extends React.Component { this._focusedCell = { row: row, col: col }; } this.props.setFocused(this.props.Document); - } + }; @undoBatch createRow = action(() => { - this.props.addDocument?.(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 })); + this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 })); this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col }; }); @undoBatch @action createColumn = () => { - const newFieldName = (index: number) => `New field${index ? ` (${index})` : ""}`; + const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`; for (let index = 0; index < 100; index++) { if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) { - this.props.columns.push(new SchemaHeaderField(newFieldName(index), "#f1efeb")); + this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb')); break; } } - } + }; @action getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => { @@ -407,15 +476,15 @@ export class SchemaTable extends React.Component { return column.type; } if (columnTypes.get(column.heading)) { - return column.type = columnTypes.get(column.heading)!; + return (column.type = columnTypes.get(column.heading)!); } - return column.type = ColumnType.Any; - } + return (column.type = ColumnType.Any); + }; @undoBatch @action toggleTextwrap = async () => { - const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []); + const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); if (textwrappedRows.length) { this.props.Document.textwrappedSchemaRows = new List([]); } else { @@ -423,7 +492,7 @@ export class SchemaTable extends React.Component { const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]); this.props.Document.textwrappedSchemaRows = new List(allRows); } - } + }; @action toggleTextWrapRow = (doc: Doc): void => { @@ -433,41 +502,50 @@ export class SchemaTable extends React.Component { index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]); this.textWrappedRows = textWrapped; - } + }; @computed get reactTable() { const children = this.childDocs; const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false); const expanded: { [name: string]: any } = {}; - Array.from(this._openCollections.keys()).map(col => expanded[col.toString()] = true); + Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true)); const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :(((( - return (row.original.type !== DocumentType.COL) ? (null) : -
} - - />; + return ( + + row.original.type !== DocumentType.COL ? null : ( +
+ +
+ ) + } + /> + ); } onContextMenu = (e: React.MouseEvent): void => { - ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" }); - } + ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' }); + }; getField = (row: number, col?: number) => { const docs = this.childDocs; @@ -484,7 +562,7 @@ export class SchemaTable extends React.Component { return doc[column]; } return undefined; - } + }; createTransformer = (row: number, col: number): Transformer => { const self = this; @@ -498,11 +576,11 @@ export class SchemaTable extends React.Component { const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; if (isntPropAccess && isntPropAssign) { - if (node.text === "$r") { + if (node.text === '$r') { return ts.createNumericLiteral(row.toString()); - } else if (node.text === "$c") { + } else if (node.text === '$c') { return ts.createNumericLiteral(col.toString()); - } else if (node.text === "$") { + } else if (node.text === '$') { if (ts.isCallExpression(node.parent)) { // captures.doc = self.props.Document; // captures.key = self.props.fieldKey; @@ -521,12 +599,11 @@ export class SchemaTable extends React.Component { // return { capturedVariables: captures }; // }; - return { transformer, /*getVars*/ }; - } + return { transformer /*getVars*/ }; + }; setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => { - script = - `const $ = (row:number, col?:number) => { + script = `const $ = (row:number, col?:number) => { const rval = (doc as any)[key][row + ${row}]; return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading]; } @@ -537,7 +614,7 @@ export class SchemaTable extends React.Component { return true; } return false; - } + }; @action showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => { @@ -545,55 +622,72 @@ export class SchemaTable extends React.Component { if (dataDoc && screenX && screenY) { this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY); } - } + }; onOpenClick = () => { - this._showDoc && this.props.addDocTab(this._showDoc, "add:right"); - } + this._showDoc && this.props.addDocTab(this._showDoc, 'add:right'); + }; getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(- this.borderWidth - 4 - this.tableWidth, - this.borderWidth); - } + return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth); + }; render() { - const preview = ""; - return
this.props.active(true) && e.stopPropagation()} - onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > - {this.reactTable} - {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined :
+ new
} - {!this._showDoc ? (null) : -
- 150} - PanelHeight={() => 150} - ScreenToLocalTransform={this.getPreviewTransform} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} - moveDocument={this.props.moveDocument} - whenChildContentsActiveChanged={emptyFunction} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - bringToFront={returnFalse}> - -
} -
; + const preview = ''; + return ( +
this.props.active(true) && e.stopPropagation()} + onDrop={e => this.props.onDrop(e, {})} + onContextMenu={this.onContextMenu}> + {this.reactTable} + {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : ( +
+ + new +
+ )} + {!this._showDoc ? null : ( +
+ 150} + PanelHeight={() => 150} + ScreenToLocalTransform={this.getPreviewTransform} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} + moveDocument={this.props.moveDocument} + whenChildContentsActiveChanged={emptyFunction} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + bringToFront={returnFalse}> +
+ )} +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 85efc67a5..fa3029a98 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -1,14 +1,13 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { StringIterator } from 'lodash'; +import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; -import { createSchema } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { WebField } from '../../../../fields/URLField'; @@ -33,9 +32,6 @@ import { RichTextMenu } from '../formattedText/RichTextMenu'; import { WebBox } from '../WebBox'; import { FontIconBadge } from './FontIconBadge'; import './FontIconBox.scss'; -const FontIconSchema = createSchema({ - icon: "string", -}); export enum ButtonType { TextButton = "textBtn", @@ -628,11 +624,7 @@ ScriptingGlobals.add(function setBulletList(mapStyle: "bullet" | "decimal", chec if (active === mapStyle) return Colors.MEDIUM_BLUE; return "transparent"; } - if (editorView) { - const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle(); - editorView?.state && RichTextMenu.Instance.changeListType( - editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle })); - } + editorView?.state && RichTextMenu.Instance.changeListType(mapStyle); }); // toggle: Set overlay status of selected document diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index 5c75a589a..40dd6fbc7 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -1,37 +1,44 @@ -import { TextSelection } from "prosemirror-state"; +import { TextSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom'; -import { Doc } from "../../../../fields/Doc"; -import { DocServer } from "../../../DocServer"; -import React = require("react"); - +import { Doc } from '../../../../fields/Doc'; +import { DocServer } from '../../../DocServer'; +import React = require('react'); // creates an inline comment in a note when '>>' is typed. // the comment sits on the right side of the note and vertically aligns with its anchor in the text. // the comment can be toggled on/off with the '<-' text anchor. export class DashDocCommentView { - _fieldWrapper: HTMLDivElement; // container for label and value + dom: HTMLDivElement; // container for label and value constructor(node: any, view: any, getPos: any) { - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; - - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.fontWeight = 'bold'; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; + + ReactDOM.render(, this.dom); + (this as any).dom = this.dom; } destroy() { - ReactDOM.unmountComponentAtNode(this._fieldWrapper); + ReactDOM.unmountComponentAtNode(this.dom); } - selectNode() { } + selectNode() {} } interface IDashDocCommentViewInternal { @@ -40,8 +47,7 @@ interface IDashDocCommentViewInternal { getPos: any; } -export class DashDocCommentViewInternal extends React.Component{ - +export class DashDocCommentViewInternal extends React.Component { constructor(props: IDashDocCommentViewInternal) { super(props); this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this); @@ -71,7 +77,9 @@ export class DashDocCommentViewInternal extends React.Component { expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); - try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { } + try { + this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); + } catch (e) {} }, 0); } e.stopPropagation(); @@ -81,32 +89,35 @@ export class DashDocCommentViewInternal extends React.Component { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor + targetNode = () => { + // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor const state = this.props.view.state; for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) { const m = state.doc.nodeAt(i); if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docid === this.props.docid) { - return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; + return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any; pos: number; hidden: boolean }; } } - const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.docid, float: "right" }); + const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: this.props.docid, float: 'right' }); this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc)); - setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0); + setTimeout(() => { + try { + this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); + } catch (e) {} + }, 0); return undefined; - } + }; render() { return ( - + onPointerDown={this.onPointerDownCollapsed}> ); } } diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 1d8e3a2cf..9d203b6cc 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -1,52 +1,54 @@ -import { IReactionDisposer, reaction, observable, action } from "mobx"; -import { NodeSelection } from "prosemirror-state"; -import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc"; -import { Cast, StrCast, NumCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { Transform } from "../../../util/Transform"; -import { DocumentView } from "../DocumentView"; -import { FormattedTextBox } from "./FormattedTextBox"; -import React = require("react"); +import { IReactionDisposer, reaction, observable, action } from 'mobx'; +import { NodeSelection } from 'prosemirror-state'; +import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; +import { Cast, StrCast, NumCast } from '../../../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from '../../../../Utils'; +import { DocServer } from '../../../DocServer'; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { Transform } from '../../../util/Transform'; +import { DocumentView } from '../DocumentView'; +import { FormattedTextBox } from './FormattedTextBox'; +import React = require('react'); import * as ReactDOM from 'react-dom'; -import { observer } from "mobx-react"; -import { ColorScheme } from "../../../util/SettingsManager"; +import { observer } from 'mobx-react'; +import { ColorScheme } from '../../../util/SettingsManager'; export class DashDocView { - _fieldWrapper: HTMLSpanElement; // container for label and value + dom: HTMLSpanElement; // container for label and value constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldWrapper = document.createElement("span"); - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.textIndent = "0"; - this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "lightGray")); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block"; - (this._fieldWrapper.style as any).float = node.attrs.float; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; - - ReactDOM.render(
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index bb3791f1e..940ed6386 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,52 +1,55 @@ -import { action, computed, IReactionDisposer, observable } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, IReactionDisposer, observable } from 'mobx'; +import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom'; -import { DataSym, Doc, DocListCast, Field } from "../../../../fields/Doc"; -import { List } from "../../../../fields/List"; -import { listSpec } from "../../../../fields/Schema"; -import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { ComputedField } from "../../../../fields/ScriptField"; -import { Cast, StrCast } from "../../../../fields/Types"; -import { DocServer } from "../../../DocServer"; -import { CollectionViewType } from "../../collections/CollectionView"; -import "./DashFieldView.scss"; -import { FormattedTextBox } from "./FormattedTextBox"; -import React = require("react"); -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; -import { Tooltip } from "@material-ui/core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; +import { listSpec } from '../../../../fields/Schema'; +import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { DocServer } from '../../../DocServer'; +import { CollectionViewType } from '../../collections/CollectionView'; +import './DashFieldView.scss'; +import { FormattedTextBox } from './FormattedTextBox'; +import React = require('react'); +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { Tooltip } from '@material-ui/core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; export class DashFieldView { - _fieldWrapper: HTMLDivElement; // container for label and value + dom: HTMLDivElement; // container for label and value constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey); - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.textContent = node.attrs.fieldKey.startsWith("#") ? node.attrs.fieldKey : node.attrs.fieldKey + " " + strVal; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.fontWeight = 'bold'; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.textContent = node.attrs.fieldKey.startsWith('#') ? node.attrs.fieldKey : node.attrs.fieldKey + ' ' + strVal; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; - setTimeout(() => ReactDOM.render(, this._fieldWrapper)); - (this as any).dom = this._fieldWrapper; + setTimeout(() => ReactDOM.render(, this.dom)); + (this as any).dom = this.dom; } - destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } - selectNode() { } + destroy() { + ReactDOM.unmountComponentAtNode(this.dom); + } + selectNode() {} } interface IDashFieldViewInternal { @@ -72,8 +75,7 @@ export class DashFieldViewInternal extends React.Component dashDoc instanceof Doc && (this._dashDoc = dashDoc))); + DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); } else { this._dashDoc = this.props.tbox.rootDoc; } @@ -82,11 +84,11 @@ export class DashFieldViewInternal extends React.Component { - if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true); - }} - />; - } - else // field value is a string, so display it as an editable span - { + return ( + { + if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked; + Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true); + }} + /> + ); + } // field value is a string, so display it as an editable span + else { // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't // use React events. Essentially, React events occur after native events have been processed, so corresponding React events // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. - return { - r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r)); - r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); - r?.addEventListener("pointerdown", action(e => e.stopPropagation())); - }} > - {strVal} - ; + return ( + { + r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r)); + r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false)); + r?.addEventListener( + 'pointerdown', + action(e => e.stopPropagation()) + ); + }}> + {strVal} + + ); } } } @@ -126,11 +138,13 @@ export class DashFieldViewInternal extends React.Component { - if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. + if (e.key === 'Enter') { + // handle the enter key by "submitting" the current text to Dash's database. this.updateText(span.textContent!, true); - e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view + e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view } - if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span + if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { + // handle ctrl-A to select all the text within the span if (window.getSelection) { const range = document.createRange(); range.selectNodeContents(span); @@ -139,44 +153,44 @@ export class DashFieldViewInternal extends React.Component { if (nodeText) { - const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; + const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText; // look for a document whose id === the fieldKey being displayed. If there's a match, then that document // holds the different enumerated values for the field in the titles of its collected documents. // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. DocServer.GetRefField(this._fieldKey).then(options => { - let modText = ""; - (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); + let modText = ''; + options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); if (modText) { // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true); } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (nodeText.startsWith(":=")) { + else if (nodeText.startsWith(':=')) { this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); - } else if (nodeText.startsWith("=:=")) { + } else if (nodeText.startsWith('=:=')) { Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); } else { if (Number(newText).toString() === newText) { - if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); + if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true); } else { const splits = newText.split(DashFieldViewInternal.multiValueDelimeter); - if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) { + if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) { const strVal = splits.length > 1 ? new List(splits) : newText; - if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; + if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true); } } } }); } - } + }; createPivotForField = (e: React.MouseEvent) => { let container = this.props.tbox.props.ContainingCollectionView; @@ -190,36 +204,39 @@ export class DashFieldViewInternal extends React.Component(); } - list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); - list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey; - this.props.tbox.props.addDocTab(alias, "add:right"); + list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); + list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); + alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey; + this.props.tbox.props.addDocTab(alias, 'add:right'); } - } - + }; // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label onPointerDownLabelSpan = (e: any) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, (e) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { DashFieldViewMenu.createFieldView = this.createPivotForField; DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16); }); - } + }; render() { - return
- {this.props.hideKey ? (null) : - - {this._fieldKey} - } - - {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent} + return ( +
+ {this.props.hideKey ? null : ( + + {this._fieldKey} + + )} -
; + {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent} +
+ ); } } @observer @@ -234,19 +251,19 @@ export class DashFieldViewMenu extends AntimodeMenu { showFields = (e: React.MouseEvent) => { DashFieldViewMenu.createFieldView(e); DashFieldViewMenu.Instance.fadeOut(true); - } + }; public show = (x: number, y: number) => { this.jumpTo(x, y, true); const hideMenu = () => { this.fadeOut(true); - document.removeEventListener("pointerdown", hideMenu); + document.removeEventListener('pointerdown', hideMenu); }; - document.addEventListener("pointerdown", hideMenu); - } + document.addEventListener('pointerdown', hideMenu); + }; render() { const buttons = [ - {"Remove Link Anchor"}
}> + {'Remove Link Anchor'}
}> @@ -255,4 +272,4 @@ export class DashFieldViewMenu extends AntimodeMenu { return this.getElement(buttons); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index 508500ab6..98d611ca6 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -1,38 +1,38 @@ -import EquationEditor from "equation-editor-react"; -import { IReactionDisposer } from "mobx"; -import { observer } from "mobx-react"; +import EquationEditor from 'equation-editor-react'; +import { IReactionDisposer } from 'mobx'; +import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom'; -import { Doc } from "../../../../fields/Doc"; -import { StrCast } from "../../../../fields/Types"; -import "./DashFieldView.scss"; -import { FormattedTextBox } from "./FormattedTextBox"; -import React = require("react"); +import { Doc } from '../../../../fields/Doc'; +import { StrCast } from '../../../../fields/Types'; +import './DashFieldView.scss'; +import { FormattedTextBox } from './FormattedTextBox'; +import React = require('react'); export class EquationView { - _fieldWrapper: HTMLDivElement; // container for label and value + dom: HTMLDivElement; // container for label and value constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; + ReactDOM.render(, this.dom); + (this as any).dom = this.dom; } _editor: EquationEditor | undefined; - setEditor = (editor?: EquationEditor) => this._editor = editor; - destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } - selectNode() { this._editor?.mathField.focus(); } - deselectNode() { } + setEditor = (editor?: EquationEditor) => (this._editor = editor); + destroy() { + ReactDOM.unmountComponentAtNode(this.dom); + } + selectNode() { + this._editor?.mathField.focus(); + } + deselectNode() {} } interface IEquationViewInternal { @@ -56,24 +56,33 @@ export class EquationViewInternal extends React.Component this._textBoxDoc = this.props.tbox.props.Document; } - componentWillUnmount() { this._reactionDisposer?.(); } - componentDidMount() { this.props.setEditor(this._ref.current ?? undefined); } + componentWillUnmount() { + this._reactionDisposer?.(); + } + componentDidMount() { + this.props.setEditor(this._ref.current ?? undefined); + } render() { - return
- -
; + return ( +
+ Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text - xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView + makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text + xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView yPadding?: number; noSidebar?: boolean; dontScale?: boolean; dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field) } -export const GoogleRef = "googleDocId"; +export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } +export class FormattedTextBox extends ViewBoxAnnotatableComponent() { + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(FormattedTextBox, fieldStr); + } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; public static LiveTextUndo: UndoManager.Batch | undefined; - static _globalHighlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + static _globalHighlights: string[] = ['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']; static _highlightStyleSheet: any = addStyleSheet(); static _bulletStyleSheet: any = addStyleSheet(); static _userStyleSheet: any = addStyleSheet(); @@ -93,7 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _ref: React.RefObject = React.createRef(); private _scrollRef: React.RefObject = React.createRef(); private _editorView: Opt; - private _applyingChange: string = ""; + private _applyingChange: string = ''; private _searchIndex = 0; private _lastTimedMark: Mark | undefined = undefined; private _cachedLinks: Doc[] = []; @@ -102,7 +103,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _dropDisposer?: DragManager.DragDropDisposer; private _recordingStart: number = 0; private _ignoreScroll = false; - private _lastText = ""; + private _lastText = ''; private _focusSpeed: Opt; private _keymap: any = undefined; private _rules: RichTextRules | undefined; @@ -113,21 +114,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _downY = 0; private _break = true; public ProseRef?: HTMLDivElement; - public get EditorView() { return this._editorView; } - public get SidebarKey() { return this.fieldKey + "-sidebar"; } - @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } - - @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } - @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } - @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; } - @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); } - @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); } - @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); } - @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } - @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); } - @computed get _recording() { return this.dataDoc?.mediaState === "recording"; } + public get EditorView() { + return this._editorView; + } + public get SidebarKey() { + return this.fieldKey + '-sidebar'; + } + @computed get allSidebarDocs() { + return DocListCast(this.dataDoc[this.SidebarKey]); + } + + @computed get sidebarWidthPercent() { + return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); + } + @computed get sidebarColor() { + return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4')); + } + @computed get autoHeight() { + return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; + } + @computed get textHeight() { + return NumCast(this.rootDoc[this.fieldKey + '-height']); + } + @computed get scrollHeight() { + return NumCast(this.rootDoc[this.fieldKey + '-scrollHeight']); + } + @computed get sidebarHeight() { + return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '-height']); + } + @computed get titleHeight() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; + } + @computed get autoHeightMargins() { + return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); + } + @computed get _recording() { + return this.dataDoc?.mediaState === 'recording'; + } set _recording(value) { - !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined); + !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined); } @computed get config() { this._keymap = buildKeymap(schema, this.props); @@ -140,28 +165,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp history(), keymap(this._keymap), keymap(baseKeymap), - new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }), - new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) - ] + new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }), + new Plugin({ + view(editorView) { + return new FormattedTextBoxComment(editorView); + }, + }), + ], }; } public static PasteOnLoad: ClipboardEvent | undefined; - public static SelectOnLoad = ""; + public static SelectOnLoad = ''; public static DontSelectInitialText = false; // whether initial text should be selected or not - public static SelectOnLoadChar = ""; - public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } + public static SelectOnLoadChar = ''; + public static IsFragment(html: string) { + return html.indexOf('data-pm-slice') !== -1; + } public static GetHref(html: string): string { const parser = new DOMParser(); const parsedHtml = parser.parseFromString(html, 'text/html'); - if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && - (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; } - return ""; + return ''; } public static GetDocFromUrl(url: string) { - return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid + return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid } constructor(props: any) { @@ -172,7 +202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } // removes all hyperlink anchors for the removed linkDoc - // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. + // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing. public RemoveLinkFromDoc(linkDoc?: Doc) { this.unhighlightSearchTerms(); @@ -195,9 +225,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - // removes all the specified link references from the selection. + // removes all the specified link references from the selection. // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references. - public RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) { + public RemoveAnchorFromSelection(allAnchors: { href: string; title: string; linkId: string; targetId: string }[]) { const state = this._editorView?.state; if (state && this._editorView) { this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors })); @@ -205,11 +235,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection"); + getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection'); @action setupAnchorMenu = () => { - AnchorMenu.Instance.Status = "marquee"; + AnchorMenu.Instance.Status = 'marquee'; AnchorMenu.Instance.OnClick = (e: PointerEvent) => { !this.layoutDoc.showSidebar && this.toggleSidebar(); @@ -222,14 +252,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp AnchorMenu.Instance.onMakeAnchor = this.getAnchor; AnchorMenu.Instance.StartCropDrag = unimplementedFunction; /** - * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. + * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. */ AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); const targetCreator = (annotationOn?: Doc) => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); + const target = CurrentUserUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); FormattedTextBox.SelectOnLoad = target[Id]; return target; }; @@ -238,55 +268,56 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); - } + }; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box - const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype + const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); + const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box + const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); const effectiveAcl = GetEffectiveAcl(this.dataDoc); - const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ? - json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); + const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) { const accumTags = [] as string[]; state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => { - if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) { + if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) { accumTags.push(node.attrs.fieldKey); } }); - const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#")); + const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith('#')); const added = accumTags.filter(tag => !curTags.includes(tag)); const removed = curTags.filter(tag => !accumTags.includes(tag)); - removed.forEach(r => this.dataDoc[r] = undefined); - added.forEach(a => this.dataDoc[a] = a); + removed.forEach(r => (this.dataDoc[r] = undefined)); + added.forEach(a => (this.dataDoc[a] = a)); let unchanged = true; if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { this._applyingChange = this.fieldKey; - (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); - if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) + curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text && (this.dataDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()))); + if ((!curTemp && !curProto) || curText || json.includes('dash')) { + // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (removeSelection(json) !== removeSelection(curLayout?.Data)) { this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + this.dataDoc[this.props.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; } - } else { // if we've deleted all the text in a note driven by a template, then restore the template data + } else { + // if we've deleted all the text in a note driven by a template, then restore the template data this.dataDoc[this.props.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have + this.dataDoc[this.props.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; } - this._applyingChange = ""; + this._applyingChange = ''; if (!unchanged) { this.updateTitle(); this.tryUpdateScrollHeight(); @@ -304,16 +335,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp AnchorMenu.Instance.fadeOut(true); } } - } + }; - // for inserting timestamps + // for inserting timestamps insertTime = () => { let linkTime; let linkAnchor; let link; DocListCast(this.dataDoc.links).forEach((l, i) => { - const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; - if (anchor && (anchor.annotationOn as Doc).mediaState === "recording") { + const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; + if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') { linkTime = NumCast(anchor._timecodeToShow /* audioStart */); linkAnchor = anchor; link = l; @@ -344,7 +375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1)))); } } - } + }; autoLink = () => { if (this._editorView?.state.doc.textContent) { @@ -355,38 +386,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp var tr = this._editorView.state.tr as any; const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; tr = tr.removeMark(0, tr.doc.content.size, autoAnch); - DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); + DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); } - } + }; updateTitle = () => { const title = StrCast(this.dataDoc.title); - if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing - (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] && - (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { + if ( + !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing + (title.startsWith('-') || title.startsWith('@')) && + this._editorView && + !this.dataDoc['title-custom'] && + (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text') + ) { let node = this._editorView.state.doc; - while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; + while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild; const str = node.textContent; - const prefix = str.startsWith("@") ? "" : "-"; + const prefix = str.startsWith('@') ? '' : '-'; const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title)); if (!(cfield instanceof ComputedField)) { - this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); - if (str.startsWith("@") && str.length > 1) { + this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : ''); + if (str.startsWith('@') && str.length > 1) { Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, this.rootDoc); } } } - } + }; // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ''); const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { @@ -397,14 +432,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(sel.from, sel.to, splitter); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { - alink = alink ?? (DocListCast(this.Document.links).find(link => - Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && - Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, - LinkManager.AutoKeywords)!); + alink = + alink ?? + (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || + DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!); newAutoLinks.add(alink); - const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }]; + const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); - const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" }); + const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' }); tr = tr.addMark(pos, pos + node.nodeSize, link); } }); @@ -412,13 +447,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); } return tr; - } + }; @action search = (searchString: string, bwd?: boolean, clear: boolean = false) => { if (clear) this.unhighlightSearchTerms(); else this.highlightSearchTerms([searchString], bwd!); return true; - } + }; highlightSearchTerms = (terms: string[], backward: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); @@ -432,21 +467,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (backward === true) { if (this._searchIndex > 1) { this._searchIndex += -2; - } - else if (this._searchIndex === 1) { + } else if (this._searchIndex === 1) { this._searchIndex = length - 1; - } - else if (this._searchIndex === 0 && length !== 1) { + } else if (this._searchIndex === 0 && length !== 1) { this._searchIndex = length - 2; } - } const lastSel = Math.min(flattened.length - 1, this._searchIndex); - flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); + flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark))); flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); } - } + }; unhighlightSearchTerms = () => { if (window.screen.width < 600) null; @@ -455,20 +487,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const end = this._editorView.state.doc.nodeSize - 2; this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); - } if (FormattedTextBox.PasteOnLoad) { - const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin"); - const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfRegion"); + const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin'); + const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion'); FormattedTextBox.PasteOnLoad = undefined; setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10); } - } + }; adoptAnnotation = (start: number, end: number, mark: Mark) => { const view = this._editorView!; const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail }); view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); - } + }; protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); this.ProseRef = ele; @@ -476,8 +507,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.setupEditor(this.config, this.props.fieldKey); this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc); } - // if (this.autoHeight) this.tryUpdateScrollHeight(); - } + // if (this.autoHeight) this.tryUpdateScrollHeight(); + }; @undoBatch @action @@ -499,18 +530,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", + title: 'dashDoc', docid: target[Id], - float: "unset" + float: 'unset', }); const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); e.stopPropagation(); } // otherwise, fall through to outer collection to handle drop } - } + }; - getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { + getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null { let offset = 0; if (context === node) return { from: offset, to: offset + node.nodeSize }; @@ -521,8 +552,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const result = this.getNodeEndpoints((context.content as any).content[i], node); if (result) { return { - from: result.from + offset + (context.type.name === "doc" ? 0 : 1), - to: result.to + offset + (context.type.name === "doc" ? 0 : 1) + from: result.from + offset + (context.type.name === 'doc' ? 0 : 1), + to: result.to + offset + (context.type.name === 'doc' ? 0 : 1), }; } offset += (context.content as any).content[i].nodeSize; @@ -538,9 +569,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let ret: TextSelection[] = []; if (node.isTextblock) { - let index = 0, foundAt; + let index = 0, + foundAt; const ep = this.getNodeEndpoints(pm.state.doc, node); - const regexp = new RegExp(find.replace("*", ""), "i"); + const regexp = new RegExp(find.replace('*', ''), 'i'); if (regexp) { while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) { const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); @@ -549,7 +581,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } else { - node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); + node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find)))); } return ret; } @@ -557,162 +589,178 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp updateHighlights = () => { const highlights = FormattedTextBox._globalHighlights; clearStyleSheetRules(FormattedTextBox._userStyleSheet); - if (highlights.indexOf("Audio Tags") === -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, ""); + if (highlights.indexOf('Audio Tags') === -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, ''); } - if (highlights.indexOf("Text from Others") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); + if (highlights.indexOf('Text from Others') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' }); } - if (highlights.indexOf("My Text") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); + if (highlights.indexOf('My Text') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' }); } - if (highlights.indexOf("Todo Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" }); + if (highlights.indexOf('Todo Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'todo', { outline: 'black solid 1px' }); } - if (highlights.indexOf("Important Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" }); + if (highlights.indexOf('Important Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'important', { 'font-size': 'larger' }); } - if (highlights.indexOf("Bold Text") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror strong > span", { "font-size": "large" }, ""); - addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror :not(strong > span)", { "font-size": "0px" }, ""); + if (highlights.indexOf('Bold Text') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, ''); + addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, ''); } - if (highlights.indexOf("Disagree Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" }); + if (highlights.indexOf('Disagree Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'disagree', { 'text-decoration': 'line-through' }); } - if (highlights.indexOf("Ignore Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" }); + if (highlights.indexOf('Ignore Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'ignore', { 'font-size': '1' }); } - if (highlights.indexOf("By Recent Minute") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + if (highlights.indexOf('By Recent Minute') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); const min = Math.round(Date.now() / 1000 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); setTimeout(this.updateHighlights); } - if (highlights.indexOf("By Recent Hour") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + if (highlights.indexOf('By Recent Hour') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); const hr = Math.round(Date.now() / 1000 / 60 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } - } + }; @observable _showSidebar = false; - @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } + @computed get SidebarShown() { + return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + } @action toggleSidebar = (preview: boolean = false) => { const prevWidth = this.sidebarWidth(); if (preview) this._showSidebar = true; - else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%"; + else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); - } + }; sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); - } + }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this._ref.current!.getBoundingClientRect(); - this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; - this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; + this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; + this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%'; e.preventDefault(); return false; - } + }; specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; const changeItems: ContextMenuProps[] = []; changeItems.push({ - description: "plain", event: undoBatch(() => { + description: 'plain', + event: undoBatch(() => { Doc.setNativeView(this.rootDoc); this.layoutDoc.autoHeightMargins = undefined; - }), icon: "eye" + }), + icon: 'eye', }); changeItems.push({ - description: "metadata", event: undoBatch(() => { + description: 'metadata', + event: undoBatch(() => { this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout; - this.rootDoc.layoutKey = "layout_meta"; - setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50); - }), icon: "eye" + this.rootDoc.layoutKey = 'layout_meta'; + setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50), 50); + }), + icon: 'eye', }); - const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); + const noteTypesDoc = Cast(Doc.UserDoc()['template-notes'], Doc, null); DocListCast(noteTypesDoc?.data).forEach(note => { const icon: IconProp = StrCast(note.icon) as IconProp; changeItems.push({ - description: StrCast(note.title), event: undoBatch(() => { + description: StrCast(note.title), + event: undoBatch(() => { this.layoutDoc.autoHeightMargins = undefined; Doc.setNativeView(this.rootDoc); DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); - }), icon: icon + }), + icon: icon, }); }); const highlighting: ContextMenuProps[] = []; - const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"]; - const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; + const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text']; + const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour']; (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => highlighting.push({ - description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { + description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option, + event: () => { e.stopPropagation(); if (FormattedTextBox._globalHighlights.indexOf(option) === -1) { FormattedTextBox._globalHighlights.push(option); } else { FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1); } - runInAction(() => this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join("")); + runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join(''))); this.updateHighlights(); - }, icon: "expand-arrows-alt" - })); + }, + icon: 'expand-arrows-alt', + }) + ); const uicontrols: ContextMenuProps[] = []; - !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); - uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); - uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); - uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); - !Doc.noviceMode && uicontrols.push({ - description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => - proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" - }); - cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); + !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' }); + uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' }); + uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' }); + uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); + !Doc.noviceMode && + uicontrols.push({ + description: 'Broadcast Message', + event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), + icon: 'expand-arrows-alt', + }); + cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' }); - const appearance = cm.findByDescription("Appearance..."); - const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; - appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); + const appearance = cm.findByDescription('Appearance...'); + const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; + appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' }); // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); - !Doc.noviceMode && appearanceItems.push({ - description: "Make Default Layout", event: () => { - if (!this.layoutDoc.isTemplateDoc) { - const title = StrCast(this.rootDoc.title); - this.rootDoc.title = "text"; - MakeTemplate(this.rootDoc, true, title); - } else if (!this.rootDoc.isTemplateDoc) { - const title = StrCast(this.rootDoc.title); - this.rootDoc.title = "text"; - this.rootDoc.layout = this.layoutDoc.layout as string; - this.rootDoc.title = this.layoutDoc.isTemplateForField as string; - this.rootDoc.isTemplateDoc = false; - this.rootDoc.isTemplateForField = ""; - this.rootDoc.layoutKey = "layout"; - MakeTemplate(this.rootDoc, true, title); - setTimeout(() => { - this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height - this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template - this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields - this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); - this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null); - }, 10); - } - Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc); - Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); - }, icon: "eye" - }); - cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + !Doc.noviceMode && + appearanceItems.push({ + description: 'Make Default Layout', + event: () => { + if (!this.layoutDoc.isTemplateDoc) { + const title = StrCast(this.rootDoc.title); + this.rootDoc.title = 'text'; + MakeTemplate(this.rootDoc, true, title); + } else if (!this.rootDoc.isTemplateDoc) { + const title = StrCast(this.rootDoc.title); + this.rootDoc.title = 'text'; + this.rootDoc.layout = this.layoutDoc.layout as string; + this.rootDoc.title = this.layoutDoc.isTemplateForField as string; + this.rootDoc.isTemplateDoc = false; + this.rootDoc.isTemplateForField = ''; + this.rootDoc.layoutKey = 'layout'; + MakeTemplate(this.rootDoc, true, title); + setTimeout(() => { + this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height + this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template + this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields + this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null); + this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, 'string', null); + }, 10); + } + Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc); + Doc.AddDocToList(Cast(Doc.UserDoc()['template-notes'], Doc, null), 'data', this.rootDoc); + }, + icon: 'eye', + }); + cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); - const options = cm.findByDescription("Options..."); - const optionItems = options && "subitems" in options ? options.subitems : []; - optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); + const options = cm.findByDescription('Options...'); + const optionItems = options && 'subitems' in options ? options.subitems : []; + optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' }); + optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); + !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; - } + }; breakupDictation = () => { if (this._editorView && this._recording) { @@ -721,12 +769,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const state = this._editorView.state; const to = state.selection.to; const updated = TextSelection.create(state.doc, to, to); - this._editorView.dispatch(state.tr.setSelection(updated).insertText("\n", to)); + this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to)); if (this._recording) { this.recordDictation(); } } - } + }; recordDictation = () => { DictationManager.Controls.listen({ interimHandler: this.setDictationContent, @@ -736,26 +784,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DictationManager.Controls.stop(); } }); - } + }; stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort); setDictationContent = (value: string) => { if (this._editorView && this._recordingStart) { if (this._break) { - const textanchor = Docs.Create.TextanchorDocument({ title: "dictation anchor" }); + const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' }); this.addDocument(textanchor); const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement(); link && (Doc.GetProto(link).isDictation = true); if (!link) return; const audioanchor = Cast(link.anchor2, Doc, null); if (!audioanchor) return; - audioanchor.backgroundColor = "tan"; + audioanchor.backgroundColor = 'tan'; const audiotag = this._editorView.state.schema.nodes.audiotag.create({ timeCode: NumCast(audioanchor._timecodeToShow), audioId: audioanchor[Id], - textId: textanchor[Id] + textId: textanchor[Id], }); - Doc.GetProto(textanchor).title = "dictation:" + audiotag.attrs.timeCode; + Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode; const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag); const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size)); this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size))); @@ -765,7 +813,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const tr = this._editorView.state.tr.insertText(value); this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView()); } - } + }; // TODO: nda -- Look at how link anchors are added makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) { @@ -775,7 +823,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); if (sel.from !== sel.to) { - const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); + const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); const href = targetHref ?? Doc.localServerPath(anchor); if (anchor !== anchorDoc) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { @@ -786,7 +834,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(pos, pos + node.nodeSize, link); } }); - this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents + this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; return anchor; @@ -797,7 +845,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } scrollFocus = (textAnchor: Doc, smooth: boolean) => { - if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) { + if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) { this.toggleSidebar(!smooth); } const textAnchorId = textAnchor[Id]; @@ -827,7 +875,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor); - return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined; + return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined; }; let start = 0; @@ -845,59 +893,69 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId; - addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow", transform: "scale(3)", "transform-origin": "left bottom" }); - setTimeout(() => this._focusSpeed = undefined, this._focusSpeed); + addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' }); + setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed); setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000)); } } return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed - } + }; getScrollHeight = () => this.scrollHeight; // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. // Since we also monitor all component height changes, this will update the document's height. resetNativeHeight = (scrollHeight: number) => { const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); - this.rootDoc[this.fieldKey + "-height"] = scrollHeight; + this.rootDoc[this.fieldKey + '-height'] = scrollHeight; if (nh) this.layoutDoc._nativeHeight = scrollHeight; - } + }; - @computed get contentScaling() { return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;} + @computed get contentScaling() { + return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1; + } componentDidMount() { !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._cachedLinks = DocListCast(this.Document.links); this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation); - this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight()); - this._disposers.scrollHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), + this._disposers.autoHeight = reaction( + () => this.autoHeight, + autoHeight => autoHeight && this.tryUpdateScrollHeight() + ); + this._disposers.scrollHeight = reaction( + () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), ({ width, scrollHeight, autoHeight }) => { width && autoHeight && this.resetNativeHeight(scrollHeight); - }, { fireImmediately: true } + }, + { fireImmediately: true } ); - this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on + this._disposers.componentHeights = reaction( + // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight))); - }, { fireImmediately: true }); - this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks + }, + { fireImmediately: true } + ); + this._disposers.links = reaction( + () => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); this._cachedLinks = newLinks; - }); + } + ); this._disposers.buttonBar = reaction( () => DocumentButtonBar.Instance, instance => { if (instance) { this.pullFromGoogleDoc(this.checkState); - this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true); + this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true)); } } ); this._disposers.editorState = reaction( () => { - const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : - this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ? - this.dataDoc : this.layoutDoc; + const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : this.dataDoc?.[this.props.fieldKey + '-noTemplate'] || !this.layoutDoc[this.props.fieldKey] ? this.dataDoc : this.layoutDoc; return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) }; }, incomingValue => { @@ -912,7 +970,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); } } - }, + } ); this._disposers.pullDoc = reaction( () => this.props.Document[Pulls], @@ -933,13 +991,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } ); - this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc), - search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(), - { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }); + this._disposers.search = reaction( + () => Doc.IsSearchMatch(this.rootDoc), + search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()), + { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false } + ); - this._disposers.selected = reaction(() => this.props.isSelected(), + this._disposers.selected = reaction( + () => this.props.isSelected(), action(selected => { - this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join("") : ""; + this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : ''; if (RichTextMenu.Instance?.view === this._editorView && !selected) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } @@ -947,21 +1008,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); this.autoLink(); } - }), { fireImmediately: true }); + }), + { fireImmediately: true } + ); if (!this.props.dontRegisterView) { - this._disposers.record = reaction(() => this._recording, + this._disposers.record = reaction( + () => this._recording, () => { this.stopDictation(true); if (this._recording) { this.recordDictation(); } - }, + } ); if (this._recording) setTimeout(this.recordDictation); } - var quickScroll: string | undefined = ""; - this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), + var quickScroll: string | undefined = ''; + this._disposers.scroll = reaction( + () => NumCast(this.layoutDoc._scrollTop), pos => { if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) { const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); @@ -974,7 +1039,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._scrollRef.current.scrollTo({ top: pos }); } } - }, { fireImmediately: true } + }, + { fireImmediately: true } ); quickScroll = undefined; this.tryUpdateScrollHeight(); @@ -984,7 +1050,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { const modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); + let reference: Opt = Cast(this.dataDoc[GoogleRef], 'string'); if (!reference) { mode = modes.Insert; reference = { title: StrCast(this.dataDoc.title) }; @@ -994,7 +1060,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); - const pushSuccess = response !== undefined && !("errors" in response); + const pushSuccess = response !== undefined && !('errors' in response); dataDoc.googleDocUnchanged = pushSuccess; DocumentButtonBar.Instance.startPushOutcome(pushSuccess); } @@ -1003,15 +1069,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (exportState && reference) { const content: GoogleApiClientUtils.Docs.Content = { text: exportState.text, - requests: [] + requests: [], }; GoogleApiClientUtils.Docs.write({ reference, content, mode }); } }; - UndoManager.AddEvent({ undo, redo, prop: "" }); + UndoManager.AddEvent({ undo, redo, prop: '' }); redo(); }); - } + }; pullFromGoogleDoc = async (handler: PullHandler) => { const dataDoc = this.dataDoc; @@ -1021,7 +1087,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); } exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); - } + }; updateState = (exportState: Opt, dataDoc: Doc) => { let pullSuccess = false; @@ -1036,13 +1102,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } }, 0); dataDoc.title = exportState.title; - this.dataDoc["title-custom"] = true; + this.dataDoc['title-custom'] = true; dataDoc.googleDocUnchanged = true; } else { delete dataDoc[GoogleRef]; } DocumentButtonBar.Instance.startPullOutcome(pullSuccess); - } + }; checkState = (exportState: Opt, dataDoc: Doc) => { if (exportState && this._editorView) { @@ -1052,56 +1118,61 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp dataDoc.googleDocUnchanged = unchanged; DocumentButtonBar.Instance.setPullState(unchanged); } - } + }; clipboardTextSerializer = (slice: Slice): string => { - let text = "", separated = true; - const from = 0, to = slice.content.size; - slice.content.nodesBetween(from, to, (node, pos) => { - if (node.isText) { - text += node.text!.slice(Math.max(from, pos) - pos, to - pos); - separated = false; - } else if (!separated && node.isBlock) { - text += "\n"; - separated = true; - } else if (node.type.name === "hard_break") { - text += "\n"; - } - }, 0); + let text = '', + separated = true; + const from = 0, + to = slice.content.size; + slice.content.nodesBetween( + from, + to, + (node, pos) => { + if (node.isText) { + text += node.text!.slice(Math.max(from, pos) - pos, to - pos); + separated = false; + } else if (!separated && node.isBlock) { + text += '\n'; + separated = true; + } else if (node.type.name === 'hard_break') { + text += '\n'; + } + }, + 0 + ); return text; - } + }; handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { const cbe = event as ClipboardEvent; - const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin"); - const pdfRegionId = cbe.clipboardData?.getData("dash/pdfRegion"); + const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin'); + const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion'); return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false; - } + }; addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => { const view = this._editorView!; if (pdfDocId && pdfRegionId) { DocServer.GetRefField(pdfDocId).then(pdfDoc => { DocServer.GetRefField(pdfRegionId).then(pdfRegion => { - if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { + if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) { setTimeout(async () => { const targetField = Doc.LayoutFieldKey(pdfDoc); - const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations + const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations if (targetAnnotations) targetAnnotations.push(pdfRegion); - else Doc.AddDocToList(pdfDoc[DataSym], targetField + "-annotations", pdfRegion); + else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion); }); - const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted"); + const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted'); if (link) { const linkId = link[Id]; - const quote = view.state.schema.nodes.blockquote.create(); - quote.content = addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); + const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) }); const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0); if (slice) { - view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); + view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste')); } else { selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView())); - } } } @@ -1111,31 +1182,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } return false; - function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { const nodes: Node[] = []; frag.forEach(node => nodes.push(marker(node))); return Fragment.fromArray(nodes); } - function addLinkMark(node: Node, title: string, linkId: string) { if (!node.isText) { const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); return node.copy(content); } const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type.name === "link"); + const linkIndex = marks.findIndex(mark => mark.type.name === 'link'); const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }]; - const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true }); + const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true }); marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); return node.mark(marks); } - } + }; isActiveTab(el: Element | null | undefined) { while (el && el !== document.body) { - if (getComputedStyle(el).display === "none") return false; + if (getComputedStyle(el).display === 'none') return false; el = el.parentNode as any; } return true; @@ -1147,7 +1216,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp view(newView) { runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); return new RichTextMenuPlugin({ editorProps: this.props }); - } + }, }); } _didScroll = false; @@ -1159,7 +1228,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView?.destroy(); this._editorView = new EditorView(this.ProseRef, { state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), - handleScrollToSelection: (editorView) => { + handleScrollToSelection: editorView => { const docPos = editorView.coordsAtPos(editorView.state.selection.to); const viewRect = self._ref.current!.getBoundingClientRect(); const scrollRef = self._scrollRef.current; @@ -1179,13 +1248,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, dispatchTransaction: this.dispatchTransaction, nodeViews: { - dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, - dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, - dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, - equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, - summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, - ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, - footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); } + dashComment(node: any, view: any, getPos: any) { + return new DashDocCommentView(node, view, getPos); + }, + dashDoc(node: any, view: any, getPos: any) { + return new DashDocView(node, view, getPos, self); + }, + dashField(node: any, view: any, getPos: any) { + return new DashFieldView(node, view, getPos, self); + }, + equation(node: any, view: any, getPos: any) { + return new EquationView(node, view, getPos, self); + }, + summary(node: any, view: any, getPos: any) { + return new SummaryView(node, view, getPos); + }, + //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, + footnote(node: any, view: any, getPos: any) { + return new FootnoteView(node, view, getPos); + }, }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, @@ -1196,9 +1277,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (startupText) { dispatch(state.tr.insertText(startupText)); } - const textAlign = StrCast(this.dataDoc["text-align"], StrCast(Doc.UserDoc().textAlign, "left")); - if (textAlign !== "left") { - selectAll(this._editorView.state, (tr) => { + const textAlign = StrCast(this.dataDoc['text-align'], StrCast(Doc.UserDoc().textAlign, 'left')); + if (textAlign !== 'left') { + selectAll(this._editorView.state, tr => { this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign }))); }); } @@ -1209,14 +1290,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())); if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { const selLoadChar = FormattedTextBox.SelectOnLoadChar; - FormattedTextBox.SelectOnLoad = ""; + FormattedTextBox.SelectOnLoad = ''; this.props.select(false); if (selLoadChar && this._editorView) { const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; - const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks); + const tr = this._editorView.state.tr + .setStoredMarks(storedMarks) + .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size) + .setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { selectAll(this._editorView.state, this._editorView?.dispatch); @@ -1229,14 +1313,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selectOnLoad && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) { - this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), - schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), - ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), - ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []), - ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []), - ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), - ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), - ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])]; + this._editorView.state.storedMarks = [ + ...(this._editorView.state.storedMarks ?? []), + schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), + ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), + ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), + ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), + ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), + ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), + ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), + ]; } } @@ -1247,13 +1333,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.unhighlightSearchTerms(); this._editorView?.destroy(); RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none'); } onPointerDown = (e: React.PointerEvent): void => { if (this.Document.forceActive) e.stopPropagation(); this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view. - if ((e.target as any).tagName === "AUDIOTAG") { + if ((e.target as any).tagName === 'AUDIOTAG') { e.preventDefault(); e.stopPropagation(); const timecode = Number((e.target as any)?.dataset?.timecode); @@ -1264,10 +1350,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const func = () => { const docView = DocumentManager.Instance.getDocumentView(audiodoc); if (!docView) { - this.props.addDocTab(audiodoc, "add:bottom"); + this.props.addDocTab(audiodoc, 'add:bottom'); setTimeout(func); - } - else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that + } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that }; func(); } @@ -1283,29 +1368,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._downEvent = true; FormattedTextBoxComment.textBox = this; if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { + // stop propagation if not in sidebar // bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView, // but that's changed, so this shouldn't be needed. //e.stopPropagation(); // if the text box is selected, then it consumes all down events - document.addEventListener("pointerup", this.onSelectEnd); - document.addEventListener("pointermove", this.onSelectMove); + document.addEventListener('pointerup', this.onSelectEnd); + document.addEventListener('pointermove', this.onSelectMove); } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { e.preventDefault(); } - } + }; onSelectMove = (e: PointerEvent) => e.stopPropagation(); onSelectEnd = (e: PointerEvent) => { - document.removeEventListener("pointerup", this.onSelectEnd); - document.removeEventListener("pointermove", this.onSelectMove); - } + document.removeEventListener('pointerup', this.onSelectEnd); + document.removeEventListener('pointermove', this.onSelectMove); + }; onPointerUp = (e: React.PointerEvent): void => { if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; if ((e.nativeEvent as any).formattedHandled) { - console.log("handled"); + console.log('handled'); } if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) { const editor = this._editorView!; @@ -1320,13 +1406,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } - } + }; @action onDoubleClick = (e: React.MouseEvent): void => { FormattedTextBoxComment.textBox = this; if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar - e.stopPropagation(); // if the text box is selected, then it consumes all click events + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { + // stop propagation if not in sidebar + e.stopPropagation(); // if the text box is selected, then it consumes all click events } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -1339,18 +1426,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } - } + }; setFocus = () => { const pos = this._editorView?.state.selection.$from.pos || 1; (this.ProseRef?.children?.[0] as any).focus(); setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); - } + }; @action onFocused = (e: React.FocusEvent): void => { //applyDevTools.applyDevTools(this._editorView); FormattedTextBox.Focused = this; this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); - } + }; @observable public static Focused: FormattedTextBox | undefined; onClick = (e: React.MouseEvent): void => { @@ -1358,7 +1445,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._forceDownNode = undefined; return; } - if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. + if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) { + // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { @@ -1368,13 +1456,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!node && this.ProseRef) { const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div const boundsRect = lastNode?.getBoundingClientRect(); - if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && - e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document + if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && e.clientY > boundsRect.bottom) { + // if we clicked below the last prosemirror div, then set the selection to be the end of the document this._editorView?.focus(); this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size))); } - } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) && - node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) { + } else if (node && [this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node.type) && node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos))); } } @@ -1382,7 +1469,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); return; } - if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events + if (this.props.isSelected(true)) { + // if text box is selected, then it consumes all click events (e.nativeEvent as any).formattedHandled = true; if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it. // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment) @@ -1390,7 +1478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed; this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node; - } + }; // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) { this._forceUncollapse = false; @@ -1420,7 +1508,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos))); } } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' }); } } } @@ -1432,20 +1520,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. if (view.mouseDown) { const originalUpHandler = view.mouseDown.up; - view.root.removeEventListener("mouseup", originalUpHandler); + view.root.removeEventListener('mouseup', originalUpHandler); view.mouseDown.up = (e: MouseEvent) => { if (!(e as any).formattedHandled) { originalUpHandler(e); (e as any).formattedHandled = true; } else { - console.log("prehandled"); + console.log('prehandled'); } }; - view.root.addEventListener("mouseup", view.mouseDown.up); + view.root.addEventListener('mouseup', view.mouseDown.up); } - } + }; startUndoTypingBatch() { - !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); + !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping')); } public endUndoTypingBatch() { const wasUndoing = this._undoTyping; @@ -1461,33 +1549,39 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } - FormattedTextBox._hadSelection = window.getSelection()?.toString() !== ""; + FormattedTextBox._hadSelection = window.getSelection()?.toString() !== ''; this.endUndoTypingBatch(); FormattedTextBox.LiveTextUndo?.end(); FormattedTextBox.LiveTextUndo = undefined; const state = this._editorView!.state; - const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) { + const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); + if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) { try { - translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => { - setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => { - this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0]; - }), 1000); + translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => { + setTimeout( + () => + translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => { + this.dataDoc[this.fieldKey + '-translation'] = result1 + '\r\n\r\n' + result[0]; + }), + 1000 + ); }); - } catch (e: any) { console.log(e.message); } + } catch (e: any) { + console.log(e.message); + } this._lastText = curText; } - if (StrCast(this.rootDoc.title).startsWith("@") && !this.dataDoc["title-custom"]) { + if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc['title-custom']) { UndoManager.RunInBatch(() => { - this.dataDoc["title-custom"] = true; - this.dataDoc.showTitle = "title"; + this.dataDoc['title-custom'] = true; + this.dataDoc.showTitle = 'title'; const tr = this._editorView!.state.tr; this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection()); - }, "titler"); + }, 'titler'); } - } + }; onKeyDown = (e: React.KeyboardEvent) => { if (e.altKey) { @@ -1495,7 +1589,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return; } const state = this._editorView!.state; - if (!state.selection.empty && e.key === "%") { + if (!state.selection.empty && e.key === '%') { this._rules!.EnteringStyle = true; e.preventDefault(); e.stopPropagation(); @@ -1508,32 +1602,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); for (var i = state.selection.from; i <= state.selection.to; i++) { const node = state.doc.resolve(i); - if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && - mark.attrs.userid !== Doc.CurrentUserEmail) && - [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) { + if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) { e.preventDefault(); } } switch (e.key) { - case "Escape": + case 'Escape': this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); (document.activeElement as any).blur?.(); SelectionManager.DeselectAll(); RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); return; - case "Enter": this.insertTime(); - case "Tab": e.preventDefault(); break; - default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; - case " ": - [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})) - .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); + case 'Enter': + this.insertTime(); + case 'Tab': + e.preventDefault(); + break; + default: + if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; + case ' ': + [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } this.startUndoTypingBatch(); - } + }; ondrop = (e: React.DragEvent) => { this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. - } + }; onScroll = (e: React.UIEvent) => { if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { if (!this.props.dontSelectOnLoad) { @@ -1542,15 +1638,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._ignoreScroll = false; } } - } + }; tryUpdateScrollHeight = () => { const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; if (children) { - const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); + const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins); const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); - if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; + if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { + // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight); if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); } else { @@ -1558,7 +1655,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - } + }; fitContentsToBox = () => this.props.Document._fitContentsToBox; sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { @@ -1566,40 +1663,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // console.log("printting allSideBarDocs"); // console.log(this.allSidebarDocs); return this.addDocument(doc, sidebarKey); - } + }; sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); - setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height; - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); + setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '-height'] = height); + sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); + sidebarScreenToLocal = () => + this.props + .ScreenToLocalTransform() + .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0) + .scale(1 / NumCast(this.layoutDoc._viewScale, 1)); @computed get audioHandle() { - return
this._recording = !this._recording)} > - -
; + return ( +
(this._recording = !this._recording))}> + +
+ ); } @computed get sidebarHandle() { TraceMobx(); const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; const color = !annotated ? Colors.WHITE : Colors.BLACK; - const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")); + const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : '')); - return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) : -
- -
; + opacity: annotated ? 1 : undefined, + }}> + +
+ ); } @computed get sidebarCollection() { const renderComponent = (tag: string) => { - const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView; - return ComponentTag === CollectionStackingView ? - : + /> + ) : ( ; + fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} + /> + ); }; - return
- {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} -
; + return ( +
+ {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} +
+ ); } render() { TraceMobx(); const selected = this.props.isSelected() || this.Document.forceActive; const active = this.props.isContentActive(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); - const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : ''; const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); - const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0); - const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : ""; - const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., - return (styleFromString?.height === "0px" ? (null) : -
= 10 ? '-selected' : ''; + const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., + return styleFromString?.height === '0px' ? null : ( +
this.props.isContentActive() && e.stopPropagation()} style={{ transform: this.props.dontScale ? undefined : `scale(${scale})`, - transformOrigin: this.props.dontScale ? undefined : "top left", + transformOrigin: this.props.dontScale ? undefined : 'top left', width: this.props.dontScale ? undefined : `${100 / scale}%`, height: this.props.dontScale ? undefined : `${100 / scale}%`, // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, - ...styleFromString + ...styleFromString, }}> -
-
+
-
+
- {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection} - {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle} - {!this.layoutDoc._showAudio ? (null) : this.audioHandle} + {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} + {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} + {!this.layoutDoc._showAudio ? null : this.audioHandle}
-
+
); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 3e673c0b2..bdf59863b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -1,18 +1,25 @@ -import { Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../../fields/Doc"; -import { DocServer } from "../../../DocServer"; -import { LinkDocPreview } from "../LinkDocPreview"; -import { FormattedTextBox } from "./FormattedTextBox"; +import { Mark, ResolvedPos } from 'prosemirror-model'; +import { EditorState } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view'; +import { Doc } from '../../../../fields/Doc'; +import { DocServer } from '../../../DocServer'; +import { LinkDocPreview } from '../LinkDocPreview'; +import { FormattedTextBox } from './FormattedTextBox'; import './FormattedTextBoxComment.scss'; -import { schema } from "./schema_rts"; +import { schema } from './schema_rts'; -export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); } -export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); } -export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); } -export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let before = 0, nbef = rpos.nodeBefore; +export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); +} +export function findUserMark(marks: readonly Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid); +} +export function findLinkMark(marks: readonly Mark[]): Mark | undefined { + return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); +} +export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) { + let before = 0, + nbef = rpos.nodeBefore; while (nbef && finder(nbef.marks)) { before += nbef.nodeSize; rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); @@ -20,8 +27,9 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma } return before; } -export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let after = 0, naft = rpos.nodeAfter; +export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) { + let after = 0, + naft = rpos.nodeAfter; while (naft && finder(naft.marks)) { after += naft.nodeSize; rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); @@ -32,7 +40,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark // this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target. // this will also display metadata information about text when the view is configured to display things like other people who authored text. -// +// export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; @@ -43,11 +51,11 @@ export class FormattedTextBoxComment { constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { - const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div"); - const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div"); - tooltip.className = "FormattedTextBox-tooltip"; - tooltipText.className = "FormattedTextBox-tooltipText"; - tooltip.style.display = "none"; + const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div')); + const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div')); + tooltip.className = 'FormattedTextBox-tooltip'; + tooltipText.className = 'FormattedTextBox-tooltipText'; + tooltip.style.display = 'none'; tooltip.appendChild(tooltipText); tooltip.onpointerdown = (e: PointerEvent) => { const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment; @@ -55,38 +63,47 @@ export class FormattedTextBoxComment { e.stopPropagation(); e.preventDefault(); }; - document.getElementById("root")?.appendChild(tooltip); + document.getElementById('root')?.appendChild(tooltip); } } public static Hide() { FormattedTextBoxComment.textBox = undefined; - FormattedTextBoxComment.tooltip.style.display = "none"; + FormattedTextBoxComment.tooltip.style.display = 'none'; } public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) { FormattedTextBoxComment.textBox = textBox; FormattedTextBoxComment.startUserMarkRegion = start; FormattedTextBoxComment.endUserMarkRegion = end; FormattedTextBoxComment.userMark = mark; - FormattedTextBoxComment.tooltip.style.display = ""; + FormattedTextBoxComment.tooltip.style.display = ''; } static showCommentbox(view: EditorView, nbef: number) { const state = view.state; // These are in screen coordinates - const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + const start = view.coordsAtPos(state.selection.from - nbef), + end = view.coordsAtPos(state.selection.from - nbef); // The box in which the tooltip is positioned, to use as base - const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); + const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect(); // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left) const left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; - FormattedTextBoxComment.tooltip.style.display = ""; + FormattedTextBoxComment.tooltip.style.left = left - box.left + 'px'; + FormattedTextBoxComment.tooltip.style.bottom = box.bottom - start.top + 'px'; + FormattedTextBoxComment.tooltip.style.display = ''; } - static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "", linkDoc: string = "") { + static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '') { FormattedTextBoxComment.textBox = textBox; - if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { - FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h), linkDoc); + if (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection)) { + FormattedTextBoxComment.setupPreview( + view, + textBox, + hrefs + ?.trim() + .split(' ') + .filter(h => h), + linkDoc + ); } } @@ -104,25 +121,27 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } if (mark && child && ((nbef && naft) || !noselection)) { - FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString(); + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + ' on ' + new Date(mark.attrs.modified * 1000).toLocaleString(); FormattedTextBoxComment.showCommentbox(view, nbef); } else FormattedTextBoxComment.Hide(); } - // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. if (state.selection.$from && hrefs?.length) { const nbef = findStartOfMark(state.selection.$from, view, findLinkMark); const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; - nbef && naft && LinkDocPreview.SetLinkInfo({ - docProps: textBox.props, - linkSrc: textBox.rootDoc, - linkDoc: linkDoc ? DocServer.GetCachedRefField(linkDoc) as Doc : undefined, - location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))), - hrefs, - showHeader: true - }); + nbef && + naft && + LinkDocPreview.SetLinkInfo({ + docProps: textBox.props, + linkSrc: textBox.rootDoc, + linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined, + location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))), + hrefs, + showHeader: true, + }); } } - destroy() { } -} \ No newline at end of file + destroy() {} +} diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index c66cb502e..31552cf1b 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -1,17 +1,17 @@ -import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { Schema } from "prosemirror-model"; -import { splitListItem, wrapInList } from "prosemirror-schema-list"; -import { EditorState, TextSelection, Transaction } from "prosemirror-state"; -import { liftTarget } from "prosemirror-transform"; -import { AclAugment, AclSelfEdit, Doc } from "../../../../fields/Doc"; -import { GetEffectiveAcl } from "../../../../fields/util"; -import { Utils } from "../../../../Utils"; -import { Docs } from "../../../documents/Documents"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; - -const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; +import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands'; +import { redo, undo } from 'prosemirror-history'; +import { Schema } from 'prosemirror-model'; +import { splitListItem, wrapInList } from 'prosemirror-schema-list'; +import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; +import { liftTarget } from 'prosemirror-transform'; +import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc'; +import { GetEffectiveAcl } from '../../../../fields/util'; +import { Utils } from '../../../../Utils'; +import { Docs } from '../../../documents/Documents'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { liftListItem, sinkListItem } from './prosemirrorPatches.js'; + +const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; export type KeyMap = { [key: string]: any }; @@ -20,12 +20,12 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: tx2.doc.descendants((node: any, offset: any, index: any) => { if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) { const path = (tx2.doc.resolve(offset) as any).path; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) { if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle; depth++; } - tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth, }, node.marks); + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth }, node.marks); } }); return tx2; @@ -45,7 +45,8 @@ export function buildKeymap>(schema: S, props: any, mapKey const canEdit = (state: any) => { switch (GetEffectiveAcl(props.DataDoc)) { - case AclAugment: return false; + case AclAugment: + return false; case AclSelfEdit: for (var i = state.selection.from; i < state.selection.to; i++) { const marks = state.doc.resolve(i)?.marks?.(); @@ -58,95 +59,102 @@ export function buildKeymap>(schema: S, props: any, mapKey return true; }; - const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch); + const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch); //History commands - bind("Mod-z", undo); - bind("Shift-Mod-z", redo); - !mac && bind("Mod-y", redo); + bind('Mod-z', undo); + bind('Shift-Mod-z', redo); + !mac && bind('Mod-y', redo); //Commands to modify Mark - bind("Mod-b", toggleEditableMark(schema.marks.strong)); - bind("Mod-B", toggleEditableMark(schema.marks.strong)); + bind('Mod-b', toggleEditableMark(schema.marks.strong)); + bind('Mod-B', toggleEditableMark(schema.marks.strong)); - bind("Mod-e", toggleEditableMark(schema.marks.em)); - bind("Mod-E", toggleEditableMark(schema.marks.em)); + bind('Mod-e', toggleEditableMark(schema.marks.em)); + bind('Mod-E', toggleEditableMark(schema.marks.em)); - bind("Mod-*", toggleEditableMark(schema.marks.code)); + bind('Mod-*', toggleEditableMark(schema.marks.code)); - bind("Mod-u", toggleEditableMark(schema.marks.underline)); - bind("Mod-U", toggleEditableMark(schema.marks.underline)); + bind('Mod-u', toggleEditableMark(schema.marks.underline)); + bind('Mod-U', toggleEditableMark(schema.marks.underline)); //Commands for lists - bind("Ctrl-i", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); + bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); - bind("Ctrl-Tab", () => props.onKey?.(event, props) ? true : true); - bind("Alt-Tab", () => props.onKey?.(event, props) ? true : true); - bind("Meta-Tab", () => props.onKey?.(event, props) ? true : true); - bind("Meta-Enter", () => props.onKey?.(event, props) ? true : true); - bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true)); + bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true)); + bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true)); + bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true)); + bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const ref = state.selection; const range = ref.$from.blockRange(ref.$to); const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { // couldn't sink into an existing list, so wrap in a new one - const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); - if (!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => { + if ( + !sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { const tx3 = updateBullets(tx2, schema); - // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); dispatch(tx3); - })) { - console.log("bullet promote fail"); + }) + ) { + // couldn't sink into an existing list, so wrap in a new one + const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); + if ( + !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + // when promoting to a list, assume list will format things so don't copy the stored marks. + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + }) + ) { + console.log('bullet promote fail'); } } }); - bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { - console.log("bullet demote fail"); + if ( + !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + }) + ) { + console.log('bullet demote fail'); } }); //Command to create a new Tab with a PDF of all the command shortcuts - bind("Mod-/", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }); - props.addDocTab(newDoc, "add:right"); + bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => { + const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); + props.addDocTab(newDoc, 'add:right'); }); //Commands to modify BlockType - bind("Ctrl->", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any))); - bind("Alt-\\", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any)); - bind("Shift-Ctrl-\\", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any)); + bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any))); + bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any)); + bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any)); - bind("Ctrl-m", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() })))); + bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() })))); for (let i = 1; i <= 6; i++) { - bind("Shift-Ctrl-" + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any)); + bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any)); } //Command to create a horizontal break line const hr = schema.nodes.horizontal_rule; - bind("Mod-_", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())); + bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())); //Command to unselect all - bind("Escape", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => { dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); (document.activeElement as any).blur?.(); SelectionManager.DeselectAll(); @@ -158,24 +166,29 @@ export function buildKeymap>(schema: S, props: any, mapKey return tx; }; - - bind("Alt-Enter", () => props.onKey?.(event, props) ? true : true); - bind("Ctrl-Enter", () => props.onKey?.(event, props) ? true : true); + bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true)); + bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true)); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); - bind("Backspace", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; - if (!deleteSelection(state, (tx: Transaction) => { - dispatch(updateBullets(tx, schema)); - })) { - if (!joinBackward(state, (tx: Transaction) => { + if ( + !deleteSelection(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); - })) { - if (!selectNodeBackward(state, (tx: Transaction) => { + }) + ) { + if ( + !joinBackward(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); - })) { + }) + ) { + if ( + !selectNodeBackward(state, (tx: Transaction) => { + dispatch(updateBullets(tx, schema)); + }) + ) { return false; } } @@ -185,8 +198,7 @@ export function buildKeymap>(schema: S, props: any, mapKey //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock //command to break line - bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { - + bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; @@ -200,25 +212,29 @@ export function buildKeymap>(schema: S, props: any, mapKey } const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - const cr = state.selection.$from.node().textContent.endsWith("\n"); + const cr = state.selection.$from.node().textContent.endsWith('\n'); if (cr || !newlineInCode(state, dispatch as any)) { - if (!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { + if ( + !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + }) + ) { const fromattrs = state.selection.$from.node().attrs; - if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - const tonode = tx3.selection.$to.node(); - if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { - const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); - splitMetadata(marks, tx4); - if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as ((tx: Transaction>) => void))) { - dispatch(tx4); - } - } else dispatch(tx3.insertText("\r\n")); - })) { + if ( + !splitBlockKeepMarks(state, (tx3: Transaction) => { + const tonode = tx3.selection.$to.node(); + if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { + const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); + splitMetadata(marks, tx4); + if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) { + dispatch(tx4); + } + } else dispatch(tx3.insertText('\r\n')); + }) + ) { return false; } } @@ -227,16 +243,16 @@ export function buildKeymap>(schema: S, props: any, mapKey }); //Command to create a blank space - bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); dispatch(splitMetadata(marks, state.tr)); return false; }); - bind("Alt-ArrowUp", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any)); - bind("Alt-ArrowDown", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any)); - bind("Mod-BracketLeft", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any)); + bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any)); + bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any)); + bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any)); const cmd = chainCommands(exitCode, (state, dispatch) => { if (dispatch) { @@ -246,8 +262,7 @@ export function buildKeymap>(schema: S, props: any, mapKey return false; }); - bind("Shift-Enter", cmd); + bind('Shift-Enter', cmd); return keys; } - diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 98343a261..22ca76b2e 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,36 +1,35 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { lift, wrapIn } from "prosemirror-commands"; -import { Mark, MarkType, Node as ProsNode, ResolvedPos } from "prosemirror-model"; -import { wrapInList } from "prosemirror-schema-list"; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../../fields/Doc"; -import { Cast, StrCast } from "../../../../fields/Types"; -import { DocServer } from "../../../DocServer"; -import { LinkManager } from "../../../util/LinkManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; -import { FieldViewProps } from "../FieldView"; -import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox"; -import { updateBullets } from "./ProsemirrorExampleTransfer"; -import "./RichTextMenu.scss"; -import { schema } from "./schema_rts"; -const { toggleMark } = require("prosemirror-commands"); - +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { lift, wrapIn } from 'prosemirror-commands'; +import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model'; +import { wrapInList } from 'prosemirror-schema-list'; +import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view'; +import { Doc } from '../../../../fields/Doc'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { DocServer } from '../../../DocServer'; +import { LinkManager } from '../../../util/LinkManager'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { FieldViewProps } from '../FieldView'; +import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox'; +import { updateBullets } from './ProsemirrorExampleTransfer'; +import './RichTextMenu.scss'; +import { schema } from './schema_rts'; +const { toggleMark } = require('prosemirror-commands'); @observer -export class RichTextMenu extends AntimodeMenu { +export class RichTextMenu extends AntimodeMenu { @observable static Instance: RichTextMenu; public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef(); @observable public view?: EditorView; - public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined; public _brushMap: Map> = new Map(); @@ -43,21 +42,21 @@ export class RichTextMenu extends AntimodeMenu { @observable private _subscriptActive: boolean = false; @observable private _superscriptActive: boolean = false; - @observable private _activeFontSize: string = "13px"; - @observable private _activeFontFamily: string = ""; - @observable private activeListType: string = ""; - @observable private _activeAlignment: string = "left"; + @observable private _activeFontSize: string = '13px'; + @observable private _activeFontFamily: string = ''; + @observable private activeListType: string = ''; + @observable private _activeAlignment: string = 'left'; @observable private brushMarks: Set = new Set(); @observable private showBrushDropdown: boolean = false; - @observable private _activeFontColor: string = "black"; + @observable private _activeFontColor: string = 'black'; @observable private showColorDropdown: boolean = false; - @observable private activeHighlightColor: string = "transparent"; + @observable private activeHighlightColor: string = 'transparent'; @observable private showHighlightDropdown: boolean = false; - @observable private currentLink: string | undefined = ""; + @observable private currentLink: string | undefined = ''; @observable private showLinkDropdown: boolean = false; _reaction: IReactionDisposer | undefined; @@ -72,24 +71,44 @@ export class RichTextMenu extends AntimodeMenu { } componentDidMount() { - this._reaction = reaction(() => SelectionManager.Views(), - () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true)); + this._reaction = reaction( + () => SelectionManager.Views(), + () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true) + ); } componentWillUnmount() { this._reaction?.(); } - @computed get noAutoLink() { return this._noLinkActive; } - @computed get bold() { return this._boldActive; } - @computed get underline() { return this._underlineActive; } - @computed get italics() { return this._italicsActive; } - @computed get strikeThrough() { return this._strikethroughActive; } - @computed get fontColor() { return this._activeFontColor; } - @computed get fontFamily() { return this._activeFontFamily; } - @computed get fontSize() { return this._activeFontSize; } - @computed get textAlign() { return this._activeAlignment; } + @computed get noAutoLink() { + return this._noLinkActive; + } + @computed get bold() { + return this._boldActive; + } + @computed get underline() { + return this._underlineActive; + } + @computed get italics() { + return this._italicsActive; + } + @computed get strikeThrough() { + return this._strikethroughActive; + } + @computed get fontColor() { + return this._activeFontColor; + } + @computed get fontFamily() { + return this._activeFontFamily; + } + @computed get fontSize() { + return this._activeFontSize; + } + @computed get textAlign() { + return this._activeAlignment; + } - public delayHide = () => this._delayHide = true; + public delayHide = () => (this._delayHide = true); @action public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) { @@ -118,16 +137,16 @@ export class RichTextMenu extends AntimodeMenu { this.activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; - this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, "10px")) : activeSizes[0]; - this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "..."; - this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "..."; + this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; + this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...'; + this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle)); } - setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => { + setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => { if (mark) { const node = (state.selection as NodeSelection).node; if (node?.type === schema.nodes.ordered_list) { @@ -140,7 +159,8 @@ export class RichTextMenu extends AntimodeMenu { } else if (dontToggle) { toggleMark(mark.type, mark.attrs)(state, (tx: any) => { const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { // hack -- should have just set the mark in the first place + if (!tx.doc.rangeHasMark(from, to, mark.type)) { + // hack -- should have just set the mark in the first place toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); } else dispatch(tx); }); @@ -148,7 +168,7 @@ export class RichTextMenu extends AntimodeMenu { toggleMark(mark.type, mark.attrs)(state, dispatch); } } - } + }; // finds font sizes and families in selection getActiveAlignment() { @@ -156,11 +176,11 @@ export class RichTextMenu extends AntimodeMenu { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { - return path[i].attrs.align || "left"; + return path[i].attrs.align || 'left'; } } } - return "left"; + return 'left'; } // finds font sizes and families in selection @@ -176,7 +196,7 @@ export class RichTextMenu extends AntimodeMenu { return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle; } } - return ""; + return ''; } // finds font sizes and families in selection @@ -190,7 +210,7 @@ export class RichTextMenu extends AntimodeMenu { if (this.TextView.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; - const marks: Mark[] = [...(state.storedMarks ?? [])]; + const marks: Mark[] = [...(state.storedMarks ?? [])]; if (state.selection.empty) { const ref_node = this.reference_node(pos); marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); @@ -209,10 +229,10 @@ export class RichTextMenu extends AntimodeMenu { return { activeFamilies, activeSizes, activeColors, activeHighlights }; } - getMarksInSelection(state: EditorState) { + getMarksInSelection(state: EditorState) { const found = new Set(); const { from, to } = state.selection as TextSelection; - state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m))); + state.doc.nodesBetween(from, to, node => node.marks.forEach(m => found.add(m))); return found; } @@ -234,14 +254,12 @@ export class RichTextMenu extends AntimodeMenu { } return false; }); - } - else { + } else { const pos = this.view.state.selection.$from; const ref_node: ProsNode | null = this.reference_node(pos); if (ref_node !== null && ref_node !== this.view.state.doc) { if (ref_node.isText) { - } - else { + } else { return []; } activeMarks = markGroup.filter(mark_type => { @@ -275,13 +293,27 @@ export class RichTextMenu extends AntimodeMenu { activeMarks.forEach(mark => { switch (mark.name) { - case "noAutoLinkAnchor": this._noLinkActive = true; break; - case "strong": this._boldActive = true; break; - case "em": this._italicsActive = true; break; - case "underline": this._underlineActive = true; break; - case "strikethrough": this._strikethroughActive = true; break; - case "subscript": this._subscriptActive = true; break; - case "superscript": this._superscriptActive = true; break; + case 'noAutoLinkAnchor': + this._noLinkActive = true; + break; + case 'strong': + this._boldActive = true; + break; + case 'em': + this._italicsActive = true; + break; + case 'underline': + this._underlineActive = true; + break; + case 'strikethrough': + this._strikethroughActive = true; + break; + case 'subscript': + this._subscriptActive = true; + break; + case 'superscript': + this._superscriptActive = true; + break; } }); } @@ -293,14 +325,14 @@ export class RichTextMenu extends AntimodeMenu { this.TextView.autoLink(); this.view.focus(); } - } + }; toggleBold = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } + }; toggleUnderline = () => { if (this.view) { @@ -308,7 +340,7 @@ export class RichTextMenu extends AntimodeMenu { this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } + }; toggleItalics = () => { if (this.view) { @@ -316,13 +348,11 @@ export class RichTextMenu extends AntimodeMenu { this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } - + }; setFontSize = (fontSize: string) => { if (this.view) { - if (this.view.state.selection.from === 1 && this.view.state.selection.empty && - (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { + if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { this.TextView.dataDoc.fontSize = fontSize; this.view.focus(); this.updateMenu(this.view, undefined, this.props); @@ -333,7 +363,7 @@ export class RichTextMenu extends AntimodeMenu { this.updateMenu(this.view, undefined, this.props); } } - } + }; setFontFamily = (family: string) => { if (this.view) { @@ -342,7 +372,7 @@ export class RichTextMenu extends AntimodeMenu { this.view.focus(); this.updateMenu(this.view, undefined, this.props); } - } + }; setHighlight(color: String, view: EditorView, dispatch: any) { const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color }); @@ -362,8 +392,10 @@ export class RichTextMenu extends AntimodeMenu { // TODO: remove doesn't work // remove all node type and apply the passed-in one to the selected text - changeListType = (nodeType: Node | undefined) => { - if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return; + changeListType = (mapStyle: string) => { + const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle(); + const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }); + if (!this.view || nodeType?.attrs.mapStyle === '') return; const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list; let inList: any = undefined; @@ -377,17 +409,19 @@ export class RichTextMenu extends AntimodeMenu { } const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); - if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); + if ( + inList || + !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); - this.view!.dispatch(tx2); - })) { + this.view!.dispatch(tx2); + }) + ) { const tx2 = this.view.state.tr; if (nodeType && (inList || nextIsOL)) { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, - inList ? fromList + inList.nodeSize : this.view.state.selection.to); + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); this.view.dispatch(tx3); @@ -395,9 +429,9 @@ export class RichTextMenu extends AntimodeMenu { } this.view.focus(); this.updateMenu(this.view, undefined, this.props); - } + }; - insertSummarizer(state: EditorState, dispatch: any) { + insertSummarizer(state: EditorState, dispatch: any) { if (state.selection.empty) return false; const mark = state.schema.marks.summarize.create(); const tr = state.tr; @@ -408,7 +442,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - align = (view: EditorView, dispatch: any, alignment: "left" | "right" | "center") => { + align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { if (this.TextView.props.isSelected(true)) { var tr = view.state.tr; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { @@ -422,9 +456,9 @@ export class RichTextMenu extends AntimodeMenu { view.focus(); dispatch?.(tr); } - } + }; - insetParagraph(state: EditorState, dispatch: any) { + insetParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -437,7 +471,7 @@ export class RichTextMenu extends AntimodeMenu { dispatch?.(tr); return true; } - outsetParagraph(state: EditorState, dispatch: any) { + outsetParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -451,7 +485,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - indentParagraph(state: EditorState, dispatch: any) { + indentParagraph(state: EditorState, dispatch: any) { var tr = state.tr; const heading = false; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { @@ -467,7 +501,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - hangingIndentParagraph(state: EditorState, dispatch: any) { + hangingIndentParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -482,7 +516,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - insertBlockquote(state: EditorState, dispatch: any) { + insertBlockquote(state: EditorState, dispatch: any) { const path = (state.selection.$from as any).path; if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) { lift(state, dispatch); @@ -492,20 +526,22 @@ export class RichTextMenu extends AntimodeMenu { return true; } - insertHorizontalRule(state: EditorState, dispatch: any) { + insertHorizontalRule(state: EditorState, dispatch: any) { dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView()); return true; } - @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } + @action toggleBrushDropdown() { + this.showBrushDropdown = !this.showBrushDropdown; + } // todo: add brushes to brushMap to save with a style name onBrushNameKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); - this._brushNameRef.current!.style.background = "lightGray"; + this._brushNameRef.current!.style.background = 'lightGray'; } - } + }; _brushNameRef = React.createRef(); @action @@ -514,7 +550,7 @@ export class RichTextMenu extends AntimodeMenu { } @action - fillBrush(state: EditorState, dispatch: any) { + fillBrush(state: EditorState, dispatch: any) { if (!this.view) return; if (!Array.from(this.brushMarks.keys()).length) { @@ -522,68 +558,81 @@ export class RichTextMenu extends AntimodeMenu { if (selected_marks.size >= 0) { this.brushMarks = selected_marks; } - } - else { + } else { const { from, to, $from } = this.view.state.selection; if (!this.view.state.selection.empty && $from && $from.nodeAfter) { if (to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { - this.setMark(mark, this.view!.state, this.view!.dispatch); - }); + Array.from(this.brushMarks) + .filter(m => m.type !== schema.marks.user_mark) + .forEach((mark: Mark) => { + this.setMark(mark, this.view!.state, this.view!.dispatch); + }); } } } } - get TextView() { return (this.view as any)?.TextView as FormattedTextBox; } - get TextViewFieldKey() { return this.TextView?.props.fieldKey; } - - - - @action setActiveHighlight(color: string) { this.activeHighlightColor = color; } + get TextView() { + return (this.view as any)?.TextView as FormattedTextBox; + } + get TextViewFieldKey() { + return this.TextView?.props.fieldKey; + } + @action setActiveHighlight(color: string) { + this.activeHighlightColor = color; + } - @action setCurrentLink(link: string) { this.currentLink = link; } + @action setCurrentLink(link: string) { + this.currentLink = link; + } createLinkButton() { const self = this; function onLinkChange(e: React.ChangeEvent) { self.TextView?.endUndoTypingBatch(); - UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change"); + UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change'); } - const link = this.currentLink ? this.currentLink : ""; + const link = this.currentLink ? this.currentLink : ''; - const button = set hyperlink
} placement="bottom"> - - ; + const button = ( + set hyperlink
} placement="bottom"> + + + ); - const dropdownContent = + const dropdownContent = (

Linked to:

- +
- -
; + +
+ ); - return ; + return ; } async getTextLinkTargetTitle() { if (!this.view) return; const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); + const link = node && node.marks.find(m => m.type.name === 'link'); if (link) { const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined; if (href) { if (href.indexOf(Doc.localServerPath()) === 0) { - const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0]; + const linkclicked = href.replace(Doc.localServerPath(), '').split('?')[0]; if (linkclicked) { const linkDoc = await DocServer.GetRefField(linkclicked); if (linkDoc instanceof Doc) { @@ -612,8 +661,8 @@ export class RichTextMenu extends AntimodeMenu { // TODO: should check for valid URL @undoBatch makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target); - } + ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target); + }; @undoBatch @action @@ -624,13 +673,15 @@ export class RichTextMenu extends AntimodeMenu { const allAnchors = linkAnchor.attrs.allAnchors.slice(); this.TextView.RemoveAnchorFromSelection(allAnchors); // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected. - allAnchors.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0).forEach((aref: any) => { - const anchorId = aref.href.replace(Doc.localServerPath(), "").split("?")[0]; - anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); - }); + allAnchors + .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0) + .forEach((aref: any) => { + const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0]; + anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); + }); } } - } + }; linkExtend($start: ResolvedPos, href: string) { const mark = this.view!.state.schema.marks.linkAnchor; @@ -651,7 +702,7 @@ export class RichTextMenu extends AntimodeMenu { return { from: startPos, to: endPos }; } - reference_node(pos: ResolvedPos): ProsNode | null { + reference_node(pos: ResolvedPos): ProsNode | null { if (!this.view) return null; let ref_node: ProsNode = this.view.state.doc; @@ -671,7 +722,6 @@ export class RichTextMenu extends AntimodeMenu { ref_node = node; skip = true; } - }); } } @@ -755,21 +805,19 @@ interface ButtonDropdownProps { openDropdownOnButton?: boolean; link?: boolean; pdf?: boolean; - } @observer export class ButtonDropdown extends React.Component { - @observable private showDropdown: boolean = false; private ref: HTMLDivElement | null = null; componentDidMount() { - document.addEventListener("pointerdown", this.onBlur); + document.addEventListener('pointerdown', this.onBlur); } componentWillUnmount() { - document.removeEventListener("pointerdown", this.onBlur); + document.removeEventListener('pointerdown', this.onBlur); } @action @@ -785,7 +833,7 @@ export class ButtonDropdown extends React.Component { e.preventDefault(); e.stopPropagation(); this.toggleDropdown(); - } + }; onBlur = (e: PointerEvent) => { setTimeout(() => { @@ -793,37 +841,40 @@ export class ButtonDropdown extends React.Component { this.setShowDropdown(false); } }, 0); - } - + }; render() { return ( -
this.ref = node}> - {!this.props.pdf ? +
(this.ref = node)}> + {!this.props.pdf ? (
{this.props.button} -
+
- : + ) : ( <> {this.props.button} - } - {this.showDropdown ? this.props.dropdownContent : (null)} + + )} + {this.showDropdown ? this.props.dropdownContent : null}
); } } - interface RichTextMenuPluginProps { editorProps: any; } export class RichTextMenuPlugin extends React.Component { - render() { return null; } - update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); } -} \ No newline at end of file + render() { + return null; + } + update(view: EditorView, lastState: EditorState | undefined) { + RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); + } +} diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 8851d52e4..1916b94bf 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,17 +1,17 @@ -import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules"; -import { NodeSelection, TextSelection } from "prosemirror-state"; -import { DataSym, Doc } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { ComputedField } from "../../../../fields/ScriptField"; -import { NumCast, StrCast } from "../../../../fields/Types"; -import { normalizeEmail } from "../../../../fields/util"; -import { returnFalse, Utils } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; -import { FormattedTextBox } from "./FormattedTextBox"; -import { wrappingInputRule } from "./prosemirrorPatches"; -import { RichTextMenu } from "./RichTextMenu"; -import { schema } from "./schema_rts"; +import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; +import { NodeSelection, TextSelection } from 'prosemirror-state'; +import { DataSym, Doc } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { NumCast, StrCast } from '../../../../fields/Types'; +import { normalizeEmail } from '../../../../fields/util'; +import { returnFalse, Utils } from '../../../../Utils'; +import { DocServer } from '../../../DocServer'; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { FormattedTextBox } from './FormattedTextBox'; +import { wrappingInputRule } from './prosemirrorPatches'; +import { RichTextMenu } from './RichTextMenu'; +import { schema } from './schema_rts'; export class RichTextRules { public Document: Doc; @@ -34,9 +34,9 @@ export class RichTextRules { wrappingInputRule( /^1\.\s$/, schema.nodes.ordered_list, - () => ({ mapStyle: "decimal", bulletStyle: 1 }), + () => ({ mapStyle: 'decimal', bulletStyle: 1 }), (match: any, node: any) => node.childCount + node.attrs.order === +match[1], - ((type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })) as any + ((type: any) => ({ type: type, attrs: { mapStyle: 'decimal', bulletStyle: 1 } })) as any ), // A. create alphabetical ordered list @@ -45,360 +45,324 @@ export class RichTextRules { schema.nodes.ordered_list, // match => { () => { - return ({ mapStyle: "multi", bulletStyle: 1 }); + return { mapStyle: 'multi', bulletStyle: 1 }; // return ({ order: +match[1] }) }, (match: any, node: any) => { return node.childCount + node.attrs.order === +match[1]; }, - ((type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })) as any + ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any ), // * + - create bullet list - wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list, + wrappingInputRule( + /^\s*([-+*])\s$/, + schema.nodes.ordered_list, // match => { - () => ({ mapStyle: "bullet" }), // ({ order: +match[1] }) + () => ({ mapStyle: 'bullet' }), // ({ order: +match[1] }) (match: any, node: any) => node.childCount + node.attrs.order === +match[1], - ((type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })) as any + ((type: any) => ({ type: type, attrs: { mapStyle: 'bullet' } })) as any ), // ``` create code block textblockTypeInputRule(/^```$/, schema.nodes.code_block), - // % set the font size - new InputRule( - new RegExp(/%([0-9]+)\s$/), - (state, match, start, end) => { - const size = Number(match[1]); - return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); - }), + // % set the font size + new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => { + const size = Number(match[1]); + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); + }), //Create annotation to a field on the text document - new InputRule( - new RegExp(/>>$/), - (state, match, start, end) => { - const textDoc = this.Document[DataSym]; - const numInlines = NumCast(textDoc.inlineTextCount); - textDoc.inlineTextCount = numInlines + 1; - const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to - const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation - const textDocInline = Docs.Create.TextDocument("", { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9px", title: "inline comment" }); - textDocInline.title = inlineFieldKey; // give the annotation its own title - textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc - textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point - textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] - textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); - textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text - textDoc[inlineFieldKey] = ""; // set a default value for the annotation - const node = (state.doc.resolve(start) as any).nodeAfter; - const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] }); - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" }); - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced; - }), - + new InputRule(new RegExp(/>>$/), (state, match, start, end) => { + const textDoc = this.Document[DataSym]; + const numInlines = NumCast(textDoc.inlineTextCount); + textDoc.inlineTextCount = numInlines + 1; + const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to + const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation + const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' }); + textDocInline.title = inlineFieldKey; // give the annotation its own title + textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc + textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point + textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] + textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); + textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text + textDoc[inlineFieldKey] = ''; // set a default value for the annotation + const node = (state.doc.resolve(start) as any).nodeAfter; + const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: textDocInline[Id], float: 'right' }); + const sm = state.storedMarks || undefined; + const replaced = node + ? state.tr + .insert(start, newNode) + .replaceRangeWith(start + 1, end + 1, dashDoc) + .insertText(' ', start + 2) + .setStoredMarks([...node.marks, ...(sm ? sm : [])]) + : state.tr; + return replaced; + }), // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/(%d|d)$/), - (state, match, start, end) => { - if (!match[0].startsWith("%") && !this.EnteringStyle) return null; - const pos = (state.doc.resolve(start) as any); - for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { - const node = pos.node(depth); - if (node.type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); - const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); - return match[0].startsWith("%") ? result.deleteRange(start, end) : result; - } + new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => { + if (!match[0].startsWith('%') && !this.EnteringStyle) return null; + const pos = state.doc.resolve(start) as any; + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith('%') ? result.deleteRange(start, end) : result; } - return null; - }), + } + return null; + }), // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/(%h|h)$/), - (state, match, start, end) => { - if (!match[0].startsWith("%") && !this.EnteringStyle) return null; - const pos = (state.doc.resolve(start) as any); - for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { - const node = pos.node(depth); - if (node.type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); - const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); - return match[0].startsWith("%") ? result.deleteRange(start, end) : result; - } + new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => { + if (!match[0].startsWith('%') && !this.EnteringStyle) return null; + const pos = state.doc.resolve(start) as any; + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith('%') ? result.deleteRange(start, end) : result; } - return null; - }), + } + return null; + }), // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/(%q|q)$/), - (state, match, start, end) => { - if (!match[0].startsWith("%") && !this.EnteringStyle) return null; - const pos = (state.doc.resolve(start) as any); - if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { - const node = state.selection.node; - return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); - } - for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { - const node = pos.node(depth); - if (node.type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); - const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); - return match[0].startsWith("%") ? result.deleteRange(start, end) : result; - } + new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => { + if (!match[0].startsWith('%') && !this.EnteringStyle) return null; + const pos = state.doc.resolve(start) as any; + if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { + const node = state.selection.node; + return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); + } + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith('%') ? result.deleteRange(start, end) : result; } - return null; - }), + } + return null; + }), // center justify text - new InputRule( - new RegExp(/%\^/), - (state, match, start, end) => { - const resolved = state.doc.resolve(start) as any; - if (resolved?.parent.type.name === "paragraph") { - return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "center" }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); - } - }), + new InputRule(new RegExp(/%\^/), (state, match, start, end) => { + const resolved = state.doc.resolve(start) as any; + if (resolved?.parent.type.name === 'paragraph') { + return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + } + }), // left justify text - new InputRule( - new RegExp(/%\[/), - (state, match, start, end) => { - const resolved = state.doc.resolve(start) as any; - if (resolved?.parent.type.name === "paragraph") { - return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "left" }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); - } - }), + new InputRule(new RegExp(/%\[/), (state, match, start, end) => { + const resolved = state.doc.resolve(start) as any; + if (resolved?.parent.type.name === 'paragraph') { + return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + } + }), // right justify text - new InputRule( - new RegExp(/%\]/), - (state, match, start, end) => { - const resolved = state.doc.resolve(start) as any; - if (resolved?.parent.type.name === "paragraph") { - return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "right" }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); - } - }), - + new InputRule(new RegExp(/%\]/), (state, match, start, end) => { + const resolved = state.doc.resolve(start) as any; + if (resolved?.parent.type.name === 'paragraph') { + return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + } + }), // %f create footnote - new InputRule( - new RegExp(/%f$/), - (state, match, start, end) => { - const newNode = schema.nodes.footnote.create({}); - const tr = state.tr; - tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. - return tr.setSelection(new NodeSelection( // select the footnote node to open its display - tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) - tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)))); - }), + new InputRule(new RegExp(/%f$/), (state, match, start, end) => { + const newNode = schema.nodes.footnote.create({}); + const tr = state.tr; + tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. + return tr.setSelection( + new NodeSelection( // select the footnote node to open its display + tr.doc.resolve( + // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0) + ) + ) + ); + }), // activate a style by name using prefix '%' - new InputRule( - new RegExp(/%[a-z]+$/), - (state, match, start, end) => { - - const color = match[0].substring(1, match[0].length); - const marks = RichTextMenu.Instance._brushMap.get(color); + new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => { + const color = match[0].substring(1, match[0].length); + const marks = RichTextMenu.Instance._brushMap.get(color); - if (marks) { - const tr = state.tr.deleteRange(start, end); - return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; - } + if (marks) { + const tr = state.tr.deleteRange(start, end); + return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; + } - const isValidColor = (strColor: string) => { - const s = new Option().style; - s.color = strColor; - return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned - }; + const isValidColor = (strColor: string) => { + const s = new Option().style; + s.color = strColor; + return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned + }; - if (isValidColor(color)) { - return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); - } + if (isValidColor(color)) { + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); + } - return null; - }), + return null; + }), // stop using active style - new InputRule( - new RegExp(/%%$/), - (state, match, start, end) => { - - const tr = state.tr.deleteRange(start, end); - const marks = state.tr.selection.$anchor.nodeBefore?.marks; - - return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr; - }), - - // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document - // [[ : ]] - // [[:Doc]] => hyperlink - // [[fieldKey]] => show field + new InputRule(new RegExp(/%%$/), (state, match, start, end) => { + const tr = state.tr.deleteRange(start, end); + const marks = state.tr.selection.$anchor.nodeBefore?.marks; + + return marks + ? Array.from(marks) + .filter(m => m.type !== state.schema.marks.user_mark) + .reduce((tr, m) => tr.removeStoredMark(m), tr) + : tr; + }), + + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // [[ : ]] + // [[:Doc]] => hyperlink + // [[fieldKey]] => show field // [[fieldKey=value]] => show field and also set its value // [[fieldKey:Doc]] => show field of doc - new InputRule( - new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), - (state, match, start, end) => { - const fieldKey = match[1]; - const rawdocid = match[3]; - const docid = rawdocid ? normalizeEmail((!rawdocid.includes("@") ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1))) : undefined; - const value = match[2]?.substring(1); - if (!fieldKey) { - if (docid) { - DocServer.GetRefField(docid).then(docx => { - const rstate = this.TextBox.EditorView?.state; - const selection = rstate?.selection.$from.pos; - if (rstate) { - this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); - } - const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid); - DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to:portal from", undefined); - - const fstate = this.TextBox.EditorView?.state; - if (fstate && selection) { - this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); - } - }); - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); - } - return state.tr; + new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { + const fieldKey = match[1]; + const rawdocid = match[3]; + const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined; + const value = match[2]?.substring(1); + if (!fieldKey) { + if (docid) { + DocServer.GetRefField(docid).then(docx => { + const rstate = this.TextBox.EditorView?.state; + const selection = rstate?.selection.$from.pos; + if (rstate) { + this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); + } + const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid); + DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined); + + const fstate = this.TextBox.EditorView?.state; + if (fstate && selection) { + this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); + } + }); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); } - if (value !== "" && value !== undefined) { - const num = value.match(/^[0-9.]$/); - this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value); - } - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); - return state.tr.deleteRange(start, end).insert(start, fieldView); - }), - + return state.tr; + } + if (value !== '' && value !== undefined) { + const num = value.match(/^[0-9.]$/); + this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + } + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), - // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // wiki:title - new InputRule( - new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), - (state, match, start, end) => { - const title = match[1]; - this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))); - - this.TextBox.makeLinkAnchor(undefined, "add:right", `https://en.wikipedia.org/wiki/${title.trim()}`, "wikipedia reference"); - - const fstate = this.TextBox.EditorView?.state; - if (fstate) { - const tr = fstate?.tr.deleteRange(start, start + 5); - return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(" "); - } - return state.tr; - }), + new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => { + const title = match[1]; + this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))); + + this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference'); + + const fstate = this.TextBox.EditorView?.state; + if (fstate) { + const tr = fstate?.tr.deleteRange(start, start + 5); + return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' '); + } + return state.tr; + }), - // create an inline view of a document {{ : }} - // {{:Doc}} => show default view of document - // {{}} => show layout for this doc + // create an inline view of a document {{ : }} + // {{:Doc}} => show default view of document + // {{}} => show layout for this doc // {{ : Doc}} => show layout for another doc - new InputRule( - new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), - (state, match, start, end) => { - const fieldKey = match[1] || ""; - const fieldParam = match[2]?.replace("…", "...") || ""; - const rawdocid = match[3]?.substring(1); - const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined; - if (!fieldKey && !docid) return state.tr; - docid && DocServer.GetRefField(docid).then(docx => { + new InputRule(new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), (state, match, start, end) => { + const fieldKey = match[1] || ''; + const fieldParam = match[2]?.replace('…', '...') || ''; + const rawdocid = match[3]?.substring(1); + const docid = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined; + if (!fieldKey && !docid) return state.tr; + docid && + DocServer.GetRefField(docid).then(docx => { if (!(docx instanceof Doc && docx)) { Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docid); } }); - const node = (state.doc.resolve(start) as any).nodeAfter; - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() }); - const sm = state.storedMarks || undefined; - return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - }), + const node = (state.doc.resolve(start) as any).nodeAfter; + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docid, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() }); + const sm = state.storedMarks || undefined; + return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + }), // create an inline view of a tag stored under the '#' field - new InputRule( - new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), - (state, match, start, end) => { - const tag = match[1]; - if (!tag) return state.tr; - this.Document[DataSym]["#" + tag] = "#" + tag; - const tags = StrCast(this.Document[DataSym].tags, ":"); - if (!tags.includes(`#${tag}:`)) { - this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`; - } - const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag }); - return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(" "); - }), - + new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { + const tag = match[1]; + if (!tag) return state.tr; + this.Document[DataSym]['#' + tag] = '#' + tag; + const tags = StrCast(this.Document[DataSym].tags, ':'); + if (!tags.includes(`#${tag}:`)) { + this.Document[DataSym].tags = `${tags + '#' + tag + ':'}`; + } + const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); + return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(' '); + }), // # heading - textblockTypeInputRule( - new RegExp(/^(#{1,6})\s$/), - schema.nodes.heading, - match => { - return ({ level: match[1].length }); - } - ), + textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => { + return { level: match[1].length }; + }), // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/[ti!x]$/), - (state, match, start, end) => { - - if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; - - const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??"; - const node = (state.doc.resolve(start) as any).nodeAfter; + new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => { + if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); + const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??'; + const node = (state.doc.resolve(start) as any).nodeAfter; - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), + if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - new InputRule( - new RegExp(/%\(/), - (state, match, start, end) => { - const node = (state.doc.resolve(start) as any).nodeAfter; - const sm = state.storedMarks || []; - const mark = state.schema.marks.summarizeInclusive.create(); + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + }), - sm.push(mark); - const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); - const content = selected.selection.content(); - const replaced = node ? selected.replaceRangeWith(start, end, - schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : - state.tr; + new InputRule(new RegExp(/%\(/), (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks?.slice() || []; + const mark = state.schema.marks.summarizeInclusive.create(); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]); - }), + sm.push(mark); + const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); + const content = selected.selection.content(); + const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr; - new InputRule( - new RegExp(/%\)/), - (state, match, start, end) => { - return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); - }), + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]); + }), - ] + new InputRule(new RegExp(/%\)/), (state, match, start, end) => { + return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); + }), + ], }; } diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index c017db034..01acc3de9 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -1,35 +1,49 @@ -import { TextSelection } from "prosemirror-state"; -import { Fragment, Node, Slice } from "prosemirror-model"; +import { TextSelection } from 'prosemirror-state'; +import { Fragment, Node, Slice } from 'prosemirror-model'; import * as ReactDOM from 'react-dom'; -import React = require("react"); +import React = require('react'); // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked. // this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't // really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering // method instead of changing prosemirror's text when the expand/elide buttons are clicked. export class SummaryView { - _fieldWrapper: HTMLSpanElement; // container for label and value + dom: HTMLSpanElement; // container for label and value constructor(node: any, view: any, getPos: any) { const self = this; - this._fieldWrapper = document.createElement("span"); - this._fieldWrapper.className = this.className(node.attrs.visibility); - this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); }; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + this.dom = document.createElement('span'); + this.dom.className = this.className(node.attrs.visibility); + this.dom.onpointerdown = function (e: any) { + self.onPointerDown(e, node, view, getPos); + }; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; const js = node.toJSON; - node.toJSON = function () { return js.apply(this, arguments); }; + node.toJSON = function () { + return js.apply(this, arguments); + }; - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; + ReactDOM.render(, this.dom); + (this as any).dom = this.dom; } - className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); - destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } - selectNode() { } + className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed'); + destroy() { + ReactDOM.unmountComponentAtNode(this.dom); + } + selectNode() {} updateSummarizedText(start: any, view: any) { const mtype = view.state.schema.marks.summarize; @@ -44,8 +58,7 @@ export class SummaryView { if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { visited.add(node); endPos = i + node.nodeSize - 1; - } - else skip = true; + } else skip = true; } }); } @@ -56,26 +69,28 @@ export class SummaryView { const visible = !node.attrs.visibility; const attrs = { ...node.attrs, visibility: visible }; let textSelection = TextSelection.create(view.state.doc, getPos() + 1); - if (!visible) { // update summarized text and save in attrs + if (!visible) { + // update summarized text and save in attrs textSelection = this.updateSummarizedText(getPos() + 1, view); attrs.text = textSelection.content(); attrs.textslice = attrs.text.toJSON(); } - view.dispatch(view.state.tr. - setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) - replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it - setNodeMarkup(getPos(), undefined, attrs)); // update the attrs + view.dispatch( + view.state.tr + .setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed) + .replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it + .setNodeMarkup(getPos(), undefined, attrs) + ); // update the attrs e.preventDefault(); e.stopPropagation(); - this._fieldWrapper.className = this.className(visible); - } + this.dom.className = this.className(visible); + }; } -interface ISummaryView { -} +interface ISummaryView {} // currently nothing needs to be rendered for the internal view of a summary. export class SummaryViewInternal extends React.Component { render() { return <> ; } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 2fde5c7ba..00c41e187 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -1,66 +1,70 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { Doc } from "../../../../fields/Doc"; +import React = require('react'); +import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model'; +import { Doc } from '../../../../fields/Doc'; - -const emDOM: DOMOutputSpecArray = ["em", 0]; -const strongDOM: DOMOutputSpecArray = ["strong", 0]; -const codeDOM: DOMOutputSpecArray = ["code", 0]; +const emDOM: DOMOutputSpec = ['em', 0]; +const strongDOM: DOMOutputSpec = ['strong', 0]; +const codeDOM: DOMOutputSpec = ['code', 0]; // :: Object [Specs](#model.MarkSpec) for the marks in the schema. export const marks: { [index: string]: MarkSpec } = { splitter: { attrs: { - id: { default: "" } + id: { default: '' }, }, toDOM(node: any) { - return ["div", { className: "dummy" }, 0]; - } + return ['div', { className: 'dummy' }, 0]; + }, }, - // :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the - // published document's title. + // published document's title. // NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because - // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since + // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since // multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to // disambiguate links from one another. // Rendered and parsed as an `` // element. autoLinkAnchor: { attrs: { - allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] }, + allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, location: { default: null }, title: { default: null }, }, inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { - location: dom.getAttribute("location"), - title: dom.getAttribute("title") - }; - } - }], + parseDOM: [ + { + tag: 'a[href]', + getAttrs(dom: any) { + return { + location: dom.getAttribute('location'), + title: dom.getAttribute('title'), + }; + }, + }, + ], toDOM(node: any) { - const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, ""); - const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, ""); - return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; - } + const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), ''); + const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); + return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; + }, }, noAutoLinkAnchor: { attrs: {}, inclusive: false, - parseDOM: [{ - tag: "div", getAttrs(dom: any) { - return { - noAutoLink: dom.getAttribute("data-noAutoLink"), - }; - } - }], + parseDOM: [ + { + tag: 'div', + getAttrs(dom: any) { + return { + noAutoLink: dom.getAttribute('data-noAutoLink'), + }; + }, + }, + ], toDOM(node: any) { - return ["span", { "data-noAutoLink": "true" }, 0]; - } + return ['span', { 'data-noAutoLink': 'true' }, 0]; + }, }, // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text, // and a title for use in menus and hover. `title` @@ -68,31 +72,46 @@ export const marks: { [index: string]: MarkSpec } = { // element. linkAnchor: { attrs: { - allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] }, + allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, location: { default: null }, title: { default: null }, - docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text + docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text }, inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { - location: dom.getAttribute("location"), - title: dom.getAttribute("title") - }; - } - }], + parseDOM: [ + { + tag: 'a[href]', + getAttrs(dom: any) { + return { + location: dom.getAttribute('location'), + title: dom.getAttribute('title'), + }; + }, + }, + ], toDOM(node: any) { - const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, ""); - const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, ""); - return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { - ...node.attrs, - class: "prosemirror-attribution", - href: node.attrs.allAnchors[0].href, - }, node.attrs.title], ["br"]] : - //node.attrs.allLinks.length === 1 ? - ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0]; + const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), ''); + const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); + return node.attrs.docref && node.attrs.title + ? [ + 'div', + ['span', `"`], + ['span', 0], + ['span', `"`], + ['br'], + [ + 'a', + { + ...node.attrs, + class: 'prosemirror-attribution', + href: node.attrs.allAnchors[0].href, + }, + node.attrs.title, + ], + ['br'], + ] + : //node.attrs.allLinks.length === 1 ? + ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0]; // ["div", { class: "prosemirror-anchor" }, // ["span", { class: "prosemirror-linkBtn" }, // ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0], @@ -102,254 +121,273 @@ export const marks: { [index: string]: MarkSpec } = { // ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title] // )] // ]; - } + }, }, /** FONT SIZES */ pFontSize: { - attrs: { fontSize: { default: "10px" } }, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" }; - } - }], - toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0] + attrs: { fontSize: { default: '10px' } }, + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' }; + }, + }, + ], + toDOM: node => (node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]), }, /* FONTS */ pFontFamily: { - attrs: { family: { default: "" } }, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - const cstyle = getComputedStyle(dom); - if (cstyle.font) { - if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; - if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; - if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; - if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; - if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; - if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; - } - } - }], - toDOM: (node) => node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0] + attrs: { family: { default: '' } }, + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + const cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' }; + if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' }; + if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' }; + if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' }; + if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' }; + if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' }; + } + return { family: '' }; + }, + }, + ], + toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]), }, // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. pFontColor: { - attrs: { color: { default: "" } }, + attrs: { color: { default: '' } }, inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { color: dom.getAttribute("color") }; - } - }], - toDOM: (node) => node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0] + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + return { color: dom.getAttribute('color') }; + }, + }, + ], + toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]), }, marker: { attrs: { - highlight: { default: "transparent" } + highlight: { default: 'transparent' }, }, inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { highlight: dom.getAttribute("backgroundColor") }; - } - }], + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + return { highlight: dom.getAttribute('backgroundColor') }; + }, + }, + ], toDOM(node: any) { return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; - } + }, }, // :: MarkSpec An emphasis mark. Rendered as an `` element. // Has parse rules that also match `` and `font-style: italic`. em: { - parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }], - toDOM() { return emDOM; } + parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style: italic' }], + toDOM() { + return emDOM; + }, }, // :: MarkSpec A strong mark. Rendered as ``, parse rules // also match `` and `font-weight: bold`. strong: { - parseDOM: [{ tag: "strong" }, - { tag: "b" }, - { style: "font-weight" }], - toDOM() { return strongDOM; } + parseDOM: [{ tag: 'strong' }, { tag: 'b' }, { style: 'font-weight' }], + toDOM() { + return strongDOM; + }, }, strikethrough: { - parseDOM: [ - { tag: 'strike' }, - { style: 'text-decoration=line-through' }, - { style: 'text-decoration-line=line-through' } + parseDOM: [{ tag: 'strike' }, { style: 'text-decoration=line-through' }, { style: 'text-decoration-line=line-through' }], + toDOM: () => [ + 'span', + { + style: 'text-decoration-line:line-through', + }, ], - toDOM: () => ['span', { - style: 'text-decoration-line:line-through' - }] }, subscript: { excludes: 'superscript', - parseDOM: [ - { tag: 'sub' }, - { style: 'vertical-align=sub' } - ], - toDOM: () => ['sub'] + parseDOM: [{ tag: 'sub' }, { style: 'vertical-align=sub' }], + toDOM: () => ['sub'], }, superscript: { excludes: 'subscript', - parseDOM: [ - { tag: 'sup' }, - { style: 'vertical-align=super' } - ], - toDOM: () => ['sup'] + parseDOM: [{ tag: 'sup' }, { style: 'vertical-align=super' }], + toDOM: () => ['sup'], }, mbulletType: { attrs: { - bulletType: { default: "decimal" } + bulletType: { default: 'decimal' }, }, toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` - }]; - } + return [ + 'span', + { + style: `background: ${node.attrs.bulletType === 'decimal' ? 'yellow' : node.attrs.bulletType === 'upper-alpha' ? 'blue' : 'green'}`, + }, + ]; + }, }, metadata: { toDOM() { return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; - } + }, }, metadataKey: { toDOM() { return ['span', { style: 'font-style:italic; ' }]; - } + }, }, metadataVal: { toDOM() { return ['span']; - } + }, }, summarizeInclusive: { parseDOM: [ { - tag: "span", + tag: 'span', getAttrs: (p: any) => { - if (typeof (p) !== "string") { + if (typeof p !== 'string') { const style = getComputedStyle(p); - if (style.textDecoration === "underline") return null; - if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) { + if (style.textDecoration === 'underline') return null; + if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: solid') !== -1) { return null; } } return false; - } + }, }, ], inclusive: true, toDOM() { - return ['span', { - style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' - }]; - } + return [ + 'span', + { + style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)', + }, + ]; + }, }, summarize: { inclusive: false, parseDOM: [ { - tag: "span", + tag: 'span', getAttrs: (p: any) => { - if (typeof (p) !== "string") { + if (typeof p !== 'string') { const style = getComputedStyle(p); - if (style.textDecoration === "underline") return null; - if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { + if (style.textDecoration === 'underline') return null; + if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: dotted') !== -1) { return null; } } return false; - } + }, }, ], toDOM() { - return ['span', { - style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' - }]; - } + return [ + 'span', + { + style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)', + }, + ]; + }, }, underline: { parseDOM: [ { - tag: "span", + tag: 'span', getAttrs: (p: any) => { - if (typeof (p) !== "string") { + if (typeof p !== 'string') { const style = getComputedStyle(p); - if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { + if (style.textDecoration === 'underline' || p.parentElement.outerHTML.indexOf('text-decoration-style:line') !== -1) { return null; } } return false; - } - } + }, + }, // { style: "text-decoration=underline" } ], - toDOM: () => ['span', { - style: 'text-decoration:underline;text-decoration-style:line' - }] + toDOM: () => [ + 'span', + { + style: 'text-decoration:underline;text-decoration-style:line', + }, + ], }, search_highlight: { attrs: { - selected: { default: false } + selected: { default: false }, }, parseDOM: [{ style: 'background: yellow' }], toDOM(node: any) { - return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }]; - } + return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }]; + }, }, // the id of the user who entered the text user_mark: { attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 + userid: { default: '' }, + modified: { default: 'when?' }, // 1 second intervals since 1970 }, - excludes: "user_mark", - group: "inline", + excludes: 'user_mark', + group: 'inline', toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); + const uid = node.attrs.userid.replace('.', '').replace('@', ''); const min = Math.round(node.attrs.modified / 12); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); - const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : ""; - return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0]; - } + const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : ''; + return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0]; + }, }, // the id of the user who entered the text user_tag: { attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 - tag: { default: "" } + userid: { default: '' }, + modified: { default: 'when?' }, // 1 second intervals since 1970 + tag: { default: '' }, }, - group: "inline", + group: 'inline', inclusive: false, toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); - return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0]; - } + const uid = node.attrs.userid.replace('.', '').replace('@', ''); + return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0]; + }, }, - // :: MarkSpec Code font mark. Represented as a `` element. code: { - parseDOM: [{ tag: "code" }], - toDOM() { return codeDOM; } + parseDOM: [{ tag: 'code' }], + toDOM() { + return codeDOM; + }, }, }; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 2fe0a67cb..5142b7da6 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -1,15 +1,18 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec"; +import React = require('react'); +import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model'; +import { listItem, orderedList } from 'prosemirror-schema-list'; +import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec'; -const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], - preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; +const blockquoteDOM: DOMOutputSpec = ['blockquote', 0], + hrDOM: DOMOutputSpec = ['hr'], + preDOM: DOMOutputSpec = ['pre', ['code', 0]], + brDOM: DOMOutputSpec = ['br'], + ulDOM: DOMOutputSpec = ['ul', 0]; function formatAudioTime(time: number) { time = Math.round(time); const hours = Math.floor(time / 60 / 60); - const minutes = Math.floor(time / 60) - (hours * 60); + const minutes = Math.floor(time / 60) - hours * 60; const seconds = time % 60; return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0'); @@ -19,67 +22,70 @@ function formatAudioTime(time: number) { export const nodes: { [index: string]: NodeSpec } = { // :: NodeSpec The top level document node. doc: { - content: "block+" + content: 'block+', }, paragraph: ParagraphNodeSpec, audiotag: { - group: "block", + group: 'block', attrs: { timeCode: { default: 0 }, - audioId: { default: "" }, - textId: { default: "" } + audioId: { default: '' }, + textId: { default: '' }, }, toDOM(node) { - return ['audiotag', + return [ + 'audiotag', { class: node.attrs.textId, // style: see FormattedTextBox.scss - "data-timecode": node.attrs.timeCode, - "data-audioid": node.attrs.audioId, - "data-textid": node.attrs.textId, + 'data-timecode': node.attrs.timeCode, + 'data-audioid': node.attrs.audioId, + 'data-textid': node.attrs.textId, }, - formatAudioTime(node.attrs.timeCode.toString()) + formatAudioTime(node.attrs.timeCode.toString()), ]; }, parseDOM: [ { - tag: "audiotag", getAttrs(dom: any) { + tag: 'audiotag', + getAttrs(dom: any) { return { - timeCode: dom.getAttribute("data-timecode"), - audioId: dom.getAttribute("data-audioid"), - textId: dom.getAttribute("data-textid") + timeCode: dom.getAttribute('data-timecode'), + audioId: dom.getAttribute('data-audioid'), + textId: dom.getAttribute('data-textid'), }; - } + }, }, - ] + ], }, footnote: { - group: "inline", - content: "inline*", + group: 'inline', + content: 'inline*', inline: true, attrs: { - visibility: { default: false } + visibility: { default: false }, }, // This makes the view treat the node as a leaf, even though it // technically has content atom: true, - toDOM: () => ["footnote", 0], - parseDOM: [{ tag: "footnote" }] + toDOM: () => ['footnote', 0], + parseDOM: [{ tag: 'footnote' }], }, // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. blockquote: { - content: "block*", - group: "block", + content: 'block*', + group: 'block', defining: true, - parseDOM: [{ tag: "blockquote" }], - toDOM() { return blockquoteDOM; } + parseDOM: [{ tag: 'blockquote' }], + toDOM() { + return blockquoteDOM; + }, }, - // blockquote: { // ...ParagraphNodeSpec, // defining: true, @@ -97,9 +103,11 @@ export const nodes: { [index: string]: NodeSpec } = { // :: NodeSpec A horizontal rule (`
`). horizontal_rule: { - group: "block", - parseDOM: [{ tag: "hr" }], - toDOM() { return hrDOM; } + group: 'block', + parseDOM: [{ tag: 'hr' }], + toDOM() { + return hrDOM; + }, }, // :: NodeSpec A heading textblock, with a `level` attribute that @@ -112,12 +120,14 @@ export const nodes: { [index: string]: NodeSpec } = { level: { default: 1 }, }, defining: true, - parseDOM: [{ tag: "h1", attrs: { level: 1 } }, - { tag: "h2", attrs: { level: 2 } }, - { tag: "h3", attrs: { level: 3 } }, - { tag: "h4", attrs: { level: 4 } }, - { tag: "h5", attrs: { level: 5 } }, - { tag: "h6", attrs: { level: 6 } }], + parseDOM: [ + { tag: 'h1', attrs: { level: 1 } }, + { tag: 'h2', attrs: { level: 2 } }, + { tag: 'h3', attrs: { level: 3 } }, + { tag: 'h4', attrs: { level: 4 } }, + { tag: 'h5', attrs: { level: 5 } }, + { tag: 'h6', attrs: { level: 6 } }, + ], toDOM(node) { const dom = toParagraphDOM(node) as any; const level = node.attrs.level || 1; @@ -129,36 +139,38 @@ export const nodes: { [index: string]: NodeSpec } = { const level = Number(dom.nodeName.substring(1)) || 1; attrs.level = level; return attrs; - } + }, }, // :: NodeSpec A code listing. Disallows marks or non-text inline // nodes by default. Represented as a `
` element with a
     // `` element inside of it.
     code_block: {
-        content: "inline*",
-        marks: "_",
-        group: "block",
+        content: 'inline*',
+        marks: '_',
+        group: 'block',
         code: true,
         defining: true,
-        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
-        toDOM() { return preDOM; }
+        parseDOM: [{ tag: 'pre', preserveWhitespace: 'full' }],
+        toDOM() {
+            return preDOM;
+        },
     },
 
     // :: NodeSpec The text node.
     text: {
-        group: "inline"
+        group: 'inline',
     },
 
     dashComment: {
         attrs: {
-            docid: { default: "" },
+            docid: { default: '' },
         },
         inline: true,
-        group: "inline",
+        group: 'inline',
         toDOM(node) {
             const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }, "←"];
+            return ['span', { ...node.attrs, ...attrs }, '←'];
         },
     },
 
@@ -169,10 +181,10 @@ export const nodes: { [index: string]: NodeSpec } = {
             text: { default: undefined },
             textslice: { default: undefined },
         },
-        group: "inline",
+        group: 'inline',
         toDOM(node) {
             const attrs = { style: `width: 40px` };
-            return ["span", { ...node.attrs, ...attrs }];
+            return ['span', { ...node.attrs, ...attrs }];
         },
     },
 
@@ -187,27 +199,30 @@ export const nodes: { [index: string]: NodeSpec } = {
             width: { default: 100 },
             alt: { default: null },
             title: { default: null },
-            float: { default: "left" },
-            location: { default: "add:right" },
-            docid: { default: "" }
+            float: { default: 'left' },
+            location: { default: 'add:right' },
+            docid: { default: '' },
         },
-        group: "inline",
+        group: 'inline',
         draggable: true,
-        parseDOM: [{
-            tag: "img[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
+        parseDOM: [
+            {
+                tag: 'img[src]',
+                getAttrs(dom: any) {
+                    return {
+                        src: dom.getAttribute('src'),
+                        title: dom.getAttribute('title'),
+                        alt: dom.getAttribute('alt'),
+                        width: Math.min(100, Number(dom.getAttribute('width'))),
+                    };
+                },
+            },
+        ],
         // TODO if we don't define toDom, dragging the image crashes. Why?
         toDOM(node) {
             const attrs = { style: `width: ${node.attrs.width}` };
-            return ["img", { ...node.attrs, ...attrs }];
-        }
+            return ['img', { ...node.attrs, ...attrs }];
+        },
     },
 
     dashDoc: {
@@ -216,82 +231,87 @@ export const nodes: { [index: string]: NodeSpec } = {
             width: { default: 200 },
             height: { default: 100 },
             title: { default: null },
-            float: { default: "right" },
+            float: { default: 'right' },
             hidden: { default: false }, // whether dashComment node has toggle the dashDoc's display off
-            fieldKey: { default: "" },
-            docid: { default: "" },
-            alias: { default: "" }
+            fieldKey: { default: '' },
+            docid: { default: '' },
+            alias: { default: '' },
         },
-        group: "inline",
+        group: 'inline',
         draggable: false,
         toDOM(node) {
             const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
+            return ['div', { ...node.attrs, ...attrs }];
+        },
     },
 
     dashField: {
         inline: true,
         attrs: {
-            fieldKey: { default: "" },
-            docid: { default: "" },
-            hideKey: { default: false }
+            fieldKey: { default: '' },
+            docid: { default: '' },
+            hideKey: { default: false },
         },
-        group: "inline",
+        group: 'inline',
         draggable: false,
         toDOM(node) {
             const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
+            return ['div', { ...node.attrs, ...attrs }];
+        },
     },
 
     equation: {
         inline: true,
         attrs: {
-            fieldKey: { default: "" },
+            fieldKey: { default: '' },
         },
         atom: true,
-        group: "inline",
+        group: 'inline',
         draggable: false,
         toDOM(node) {
             const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
-            return ["div", { ...node.attrs, ...attrs }];
-        }
+            return ['div', { ...node.attrs, ...attrs }];
+        },
     },
 
     video: {
         inline: true,
         attrs: {
             src: {},
-            width: { default: "100px" },
+            width: { default: '100px' },
             alt: { default: null },
-            title: { default: null }
+            title: { default: null },
         },
-        group: "inline",
+        group: 'inline',
         draggable: true,
-        parseDOM: [{
-            tag: "video[src]", getAttrs(dom: any) {
-                return {
-                    src: dom.getAttribute("src"),
-                    title: dom.getAttribute("title"),
-                    alt: dom.getAttribute("alt"),
-                    width: Math.min(100, Number(dom.getAttribute("width"))),
-                };
-            }
-        }],
+        parseDOM: [
+            {
+                tag: 'video[src]',
+                getAttrs(dom: any) {
+                    return {
+                        src: dom.getAttribute('src'),
+                        title: dom.getAttribute('title'),
+                        alt: dom.getAttribute('alt'),
+                        width: Math.min(100, Number(dom.getAttribute('width'))),
+                    };
+                },
+            },
+        ],
         toDOM(node) {
             const attrs = { style: `width: ${node.attrs.width}` };
-            return ["video", { ...node.attrs, ...attrs }];
-        }
+            return ['video', { ...node.attrs, ...attrs }];
+        },
     },
 
     // :: NodeSpec A hard line break, represented in the DOM as `
`. hard_break: { inline: true, - group: "inline", + group: 'inline', selectable: false, - parseDOM: [{ tag: "br" }], - toDOM() { return brDOM; } + parseDOM: [{ tag: 'br' }], + toDOM() { + return brDOM; + }, }, ordered_list: { @@ -300,85 +320,108 @@ export const nodes: { [index: string]: NodeSpec } = { group: 'block', attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" },// "decimal", "multi", "bullet" - fontColor: { default: "inherit" }, + mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet" + fontColor: { default: 'inherit' }, fontSize: { default: undefined }, fontFamily: { default: undefined }, visibility: { default: true }, - indent: { default: undefined } + indent: { default: undefined }, }, parseDOM: [ { - tag: "ul", getAttrs(dom: any) { + tag: 'ul', + getAttrs(dom: any) { return { - bulletStyle: dom.getAttribute("data-bulletStyle"), - mapStyle: dom.getAttribute("data-mapStyle"), + bulletStyle: dom.getAttribute('data-bulletStyle'), + mapStyle: dom.getAttribute('data-mapStyle'), fontColor: dom.style.color, - fontSize: dom.style["font-size"], - fontFamily: dom.style["font-family"], - indent: dom.style["margin-left"] + fontSize: dom.style['font-size'], + fontFamily: dom.style['font-family'], + indent: dom.style['margin-left'], }; - } + }, }, { - style: 'list-style-type=disc', getAttrs(dom: any) { - return { mapStyle: "bullet" }; - } + style: 'list-style-type=disc', + getAttrs(dom: any) { + return { mapStyle: 'bullet' }; + }, }, { - tag: "ol", getAttrs(dom: any) { + tag: 'ol', + getAttrs(dom: any) { return { - bulletStyle: dom.getAttribute("data-bulletStyle"), - mapStyle: dom.getAttribute("data-mapStyle"), + bulletStyle: dom.getAttribute('data-bulletStyle'), + mapStyle: dom.getAttribute('data-mapStyle'), fontColor: dom.style.color, - fontSize: dom.style["font-size"], - fontFamily: dom.style["font-family"], - indent: dom.style["margin-left"] + fontSize: dom.style['font-size'], + fontFamily: dom.style['font-family'], + indent: dom.style['margin-left'], }; - } - }], - toDOM(node: Node) { - const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : ""; - const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : ""; - const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : ""; - const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ""; - if (node.attrs.mapStyle === "bullet") { - return ['ul', { - "data-mapStyle": node.attrs.mapStyle, - "data-bulletStyle": node.attrs.bulletStyle, - style: `${fsize} ${ffam} ${fcol} ${marg}` - }, 0]; + }, + }, + ], + toDOM(node: Node) { + const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; + const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : ''; + const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : ''; + const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : ''; + const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ''; + if (node.attrs.mapStyle === 'bullet') { + return [ + 'ul', + { + 'data-mapStyle': node.attrs.mapStyle, + 'data-bulletStyle': node.attrs.bulletStyle, + style: `${fsize} ${ffam} ${fcol} ${marg}`, + }, + 0, + ]; } - return node.attrs.visibility ? - ['ol', { - class: `${map}-ol`, - "data-mapStyle": node.attrs.mapStyle, - "data-bulletStyle": node.attrs.bulletStyle, - style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}` - }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; - } + return node.attrs.visibility + ? [ + 'ol', + { + class: `${map}-ol`, + 'data-mapStyle': node.attrs.mapStyle, + 'data-bulletStyle': node.attrs.bulletStyle, + style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`, + }, + 0, + ] + : ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; + }, }, list_item: { ...listItem, attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet" - visibility: { default: true } + mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet" + visibility: { default: true }, }, content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)', - parseDOM: [{ - tag: "li", getAttrs(dom: any) { - return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") }; - } - }], + parseDOM: [ + { + tag: 'li', + getAttrs(dom: any) { + return { mapStyle: dom.getAttribute('data-mapStyle'), bulletStyle: dom.getAttribute('data-bulletStyle') }; + }, + }, + ], toDOM(node: any) { - const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - return ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, node.attrs.visibility ? 0 : - ["span", { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== "bullet" ? "inline-block" : "list-item"}; text-overflow: ellipsis; white-space: pre` }, - `${node.firstChild?.textContent}...`]]; - } + const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; + return [ + 'li', + { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle }, + node.attrs.visibility + ? 0 + : [ + 'span', + { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` }, + `${node.firstChild?.textContent}...`, + ], + ]; + }, }, -}; \ No newline at end of file +}; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index b30ca644d..31a50301a 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,59 +1,54 @@ -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { saveAs } from "file-saver"; -import { action, computed, observable, ObservableMap, runInAction } from "mobx"; -import { computedFn } from "mobx-utils"; -import { alias, map, serializable } from "serializr"; -import { DocServer } from "../client/DocServer"; -import { DocumentType } from "../client/documents/DocumentTypes"; -import { CurrentUserUtils } from "../client/util/CurrentUserUtils"; -import { LinkManager } from "../client/util/LinkManager"; -import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals"; -import { SelectionManager } from "../client/util/SelectionManager"; -import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; -import { UndoManager } from "../client/util/UndoManager"; -import { DashColor, incrementTitleCopy, intersectRect, Utils } from "../Utils"; -import { DateField } from "./DateField"; -import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; -import { List } from "./List"; -import { ObjectField } from "./ObjectField"; -import { PrefetchProxy, ProxyField } from "./Proxy"; -import { FieldId, RefField } from "./RefField"; -import { RichTextField } from "./RichTextField"; -import { listSpec } from "./Schema"; -import { ComputedField, ScriptField } from "./ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; -import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField"; -import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; -import JSZip = require("jszip"); +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { saveAs } from 'file-saver'; +import { action, computed, observable, ObservableMap, runInAction } from 'mobx'; +import { computedFn } from 'mobx-utils'; +import { alias, map, serializable } from 'serializr'; +import { DocServer } from '../client/DocServer'; +import { DocumentType } from '../client/documents/DocumentTypes'; +import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; +import { LinkManager } from '../client/util/LinkManager'; +import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; +import { SelectionManager } from '../client/util/SelectionManager'; +import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; +import { UndoManager } from '../client/util/UndoManager'; +import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; +import { DateField } from './DateField'; +import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols'; +import { List } from './List'; +import { ObjectField } from './ObjectField'; +import { PrefetchProxy, ProxyField } from './Proxy'; +import { FieldId, RefField } from './RefField'; +import { RichTextField } from './RichTextField'; +import { listSpec } from './Schema'; +import { ComputedField, ScriptField } from './ScriptField'; +import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; +import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from './URLField'; +import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util'; +import JSZip = require('jszip'); export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key); const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - return !Field.IsField(field) ? "" : (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); + return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); } export function toScriptString(field: Field): string { - if (typeof field === "string") return `"${field}"`; - if (typeof field === "number" || typeof field === "boolean") return String(field); - if (field === undefined || field === null) return "null"; + if (typeof field === 'string') return `"${field}"`; + if (typeof field === 'number' || typeof field === 'boolean') return String(field); + if (field === undefined || field === null) return 'null'; return field[ToScriptString](); } export function toString(field: Field): string { - if (typeof field === "string") return field; - if (typeof field === "number" || typeof field === "boolean") return String(field); + if (typeof field === 'string') return field; + if (typeof field === 'number' || typeof field === 'boolean') return String(field); if (field instanceof ObjectField) return field[ToString](); if (field instanceof RefField) return field[ToString](); - return ""; + return ''; } export function IsField(field: any): field is Field; export function IsField(field: any, includeUndefined: true): field is Field | undefined; export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined { - return (typeof field === "string") - || (typeof field === "number") - || (typeof field === "boolean") - || (field instanceof ObjectField) - || (field instanceof RefField) - || (includeUndefined && field === undefined); + return typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean' || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined); } export function Copy(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; @@ -77,40 +72,50 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); } -export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } - -export function NumListCast(field: FieldResult) { return Cast(field, listSpec("number"), []); } -export function StrListCast(field: FieldResult) { return Cast(field, listSpec("string"), []); } -export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; } -export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; } - -export const WidthSym = Symbol("Width"); -export const HeightSym = Symbol("Height"); -export const DataSym = Symbol("Data"); -export const LayoutSym = Symbol("Layout"); -export const FieldsSym = Symbol("Fields"); -export const AclSym = Symbol("Acl"); -export const DirectLinksSym = Symbol("DirectLinks"); -export const AclUnset = Symbol("AclUnset"); -export const AclPrivate = Symbol("AclOwnerOnly"); -export const AclReadonly = Symbol("AclReadOnly"); -export const AclAugment = Symbol("AclAugment"); -export const AclSelfEdit = Symbol("AclSelfEdit"); -export const AclEdit = Symbol("AclEdit"); -export const AclAdmin = Symbol("AclAdmin"); -export const UpdatingFromServer = Symbol("UpdatingFromServer"); -export const Initializing = Symbol("Initializing"); -export const ForceServerWrite = Symbol("ForceServerWrite"); -export const CachedUpdates = Symbol("Cached updates"); +export async function DocCastAsync(field: FieldResult): Promise> { + return Cast(field, Doc); +} + +export function NumListCast(field: FieldResult) { + return Cast(field, listSpec('number'), []); +} +export function StrListCast(field: FieldResult) { + return Cast(field, listSpec('string'), []); +} +export function DocListCast(field: FieldResult) { + return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; +} +export function DocListCastOrNull(field: FieldResult) { + return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; +} + +export const WidthSym = Symbol('Width'); +export const HeightSym = Symbol('Height'); +export const DataSym = Symbol('Data'); +export const LayoutSym = Symbol('Layout'); +export const FieldsSym = Symbol('Fields'); +export const AclSym = Symbol('Acl'); +export const DirectLinksSym = Symbol('DirectLinks'); +export const AclUnset = Symbol('AclUnset'); +export const AclPrivate = Symbol('AclOwnerOnly'); +export const AclReadonly = Symbol('AclReadOnly'); +export const AclAugment = Symbol('AclAugment'); +export const AclSelfEdit = Symbol('AclSelfEdit'); +export const AclEdit = Symbol('AclEdit'); +export const AclAdmin = Symbol('AclAdmin'); +export const UpdatingFromServer = Symbol('UpdatingFromServer'); +export const Initializing = Symbol('Initializing'); +export const ForceServerWrite = Symbol('ForceServerWrite'); +export const CachedUpdates = Symbol('Cached updates'); const AclMap = new Map([ - ["None", AclUnset], + ['None', AclUnset], [SharingPermissions.None, AclPrivate], [SharingPermissions.View, AclReadonly], [SharingPermissions.Augment, AclAugment], [SharingPermissions.SelfEdit, AclSelfEdit], [SharingPermissions.Edit, AclEdit], - [SharingPermissions.Admin, AclAdmin] + [SharingPermissions.Admin, AclAdmin], ]); // caches the document access permissions for the current user. @@ -120,7 +125,7 @@ export function updateCachedAcls(doc: Doc) { const permissions: { [key: string]: symbol } = {}; doc[UpdatingFromServer] = true; - Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!)); + Object.keys(doc).filter(key => key.startsWith('acl') && (permissions[key] = AclMap.get(StrCast(doc[key]))!)); doc[UpdatingFromServer] = false; if (Object.keys(permissions).length) { @@ -134,7 +139,7 @@ export function updateCachedAcls(doc: Doc) { } @scriptingGlobal -@Deserializable("Doc", updateCachedAcls).withFields(["id"]) +@Deserializable('Doc', updateCachedAcls).withFields(['id']) export class Doc extends RefField { constructor(id?: FieldId, forceSave?: boolean) { super(id); @@ -146,24 +151,26 @@ export class Doc extends RefField { ownKeys: target => { const obj = {} as any; if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys); - runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); + runInAction(() => (obj.__LAYOUT__ = target.__LAYOUT__)); return Object.keys(obj); }, getOwnPropertyDescriptor: (target, prop) => { - if (prop.toString() === "__LAYOUT__") { + if (prop.toString() === '__LAYOUT__') { return Reflect.getOwnPropertyDescriptor(target, prop); } if (prop in target.__fieldKeys) { return { - configurable: true,//TODO Should configurable be true? + configurable: true, //TODO Should configurable be true? enumerable: true, - value: 0//() => target.__fields[prop]) + value: 0, //() => target.__fields[prop]) }; } return Reflect.getOwnPropertyDescriptor(target, prop); }, deleteProperty: deleteProperty, - defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, + defineProperty: () => { + throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); + }, }); this[SelfProxy] = doc; if (!id || forceSave) { @@ -175,24 +182,30 @@ export class Doc extends RefField { proto: Opt; [key: string]: FieldResult; - @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize }))) - private get __fields() { return this.___fields; } + @serializable(alias('fields', map(autoObject(), { afterDeserialize: afterDocDeserialize }))) + private get __fields() { + return this.___fields; + } private set __fields(value) { this.___fields = value; for (const key in value) { const field = value[key]; - (field !== undefined) && (this.__fieldKeys[key] = true); + field !== undefined && (this.__fieldKeys[key] = true); if (!(field instanceof ObjectField)) continue; field[Parent] = this[Self]; field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); } } - private get __fieldKeys() { return this.___fieldKeys; } - private set __fieldKeys(value) { this.___fieldKeys = value; } + private get __fieldKeys() { + return this.___fieldKeys; + } + private set __fieldKeys(value) { + this.___fieldKeys = value; + } @observable private ___fields: any = {}; @observable private ___fieldKeys: any = {}; - @observable public [AclSym]: { [key: string]: symbol }; + @observable public [AclSym]: { [key: string]: symbol } = {}; @observable public [DirectLinksSym]: Set = new Set(); private [UpdatingFromServer]: boolean = false; @@ -201,7 +214,7 @@ export class Doc extends RefField { private [Update] = (diff: any) => { (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff); - } + }; private [Self] = this; private [SelfProxy]: any; @@ -209,42 +222,52 @@ export class Doc extends RefField { public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`; - public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? "-inaccessible-" : this[SelfProxy].title})`; - public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } + public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`; + public get [LayoutSym]() { + return this[SelfProxy].__LAYOUT__; + } public get [DataSym]() { const self = this[SelfProxy]; - return self.resolvedDataDoc && !self.isTemplateForField ? self : - Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); + return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } @computed get __LAYOUT__(): Doc | undefined { const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); if (templateLayoutDoc) { let renderFieldKey: any; - const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")]; - if (typeof layoutField === "string") { - renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1]; + const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, 'layout')]; + if (typeof layoutField === 'string') { + renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1]; } else { return Cast(layoutField, Doc, null); } - return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc; + return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; } return undefined; - } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; - public static get noviceMode() { return Doc.UserDoc().noviceMode as boolean; } - public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } - public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } - public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; } - public static CurrentUserEmail: string = ""; - public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); } + public static get noviceMode() { + return Doc.UserDoc().noviceMode as boolean; + } + public static set noviceMode(val) { + Doc.UserDoc().noviceMode = val; + } + public static get defaultAclPrivate() { + return Doc.UserDoc().defaultAclPrivate; + } + public static set defaultAclPrivate(val) { + Doc.UserDoc().defaultAclPrivate = val; + } + public static CurrentUserEmail: string = ''; + public static get CurrentUserEmailNormalized() { + return normalizeEmail(Doc.CurrentUserEmail); + } public async [HandleUpdate](diff: any) { const set = diff.$set; const sameAuthor = this.author === Doc.CurrentUserEmail; if (set) { for (const key in set) { - const fprefix = "fields."; + const fprefix = 'fields.'; if (!key.startsWith(fprefix)) { continue; } @@ -255,7 +278,7 @@ export class Doc extends RefField { this[UpdatingFromServer] = true; this[fKey] = value; this[UpdatingFromServer] = false; - if (fKey.startsWith("acl")) { + if (fKey.startsWith('acl')) { updateCachedAcls(this); } if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { @@ -263,7 +286,7 @@ export class Doc extends RefField { } }; const writeMode = DocServer.getFieldWriteMode(fKey); - if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) { + if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; await fn(); } else { @@ -274,7 +297,7 @@ export class Doc extends RefField { const unset = diff.$unset; if (unset) { for (const key in unset) { - if (!key.startsWith("fields.")) { + if (!key.startsWith('fields.')) { continue; } const fKey = key.substring(7); @@ -326,7 +349,7 @@ export namespace Doc { return { end() { makeEditable(); - } + }, }; } @@ -341,16 +364,16 @@ export namespace Doc { return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult; } export function IsPrototype(doc: Doc) { - return GetT(doc, "isPrototype", "boolean", true); + return GetT(doc, 'isPrototype', 'boolean', true); } export function IsBaseProto(doc: Doc) { - return GetT(doc, "baseProto", "boolean", true); + return GetT(doc, 'baseProto', 'boolean', true); } export function IsSystem(doc: Doc) { - return GetT(doc, "system", "boolean", true); + return GetT(doc, 'system', 'boolean', true); } export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { - if (key.startsWith("_")) key = key.substring(1); + if (key.startsWith('_')) key = key.substring(1); const hasProto = doc.proto instanceof Doc; const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1; @@ -359,7 +382,7 @@ export namespace Doc { } else doc.proto![key] = value; } export async function SetOnPrototype(doc: Doc, key: string, value: Field) { - const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc; + const proto = Object.getOwnPropertyNames(doc).indexOf('isPrototype') === -1 ? doc.proto : doc; if (proto) { proto[key] = value; @@ -391,7 +414,8 @@ export namespace Doc { for (const key in fields) { if (fields.hasOwnProperty(key)) { const value = fields[key]; - if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds? + if (!skipUndefineds || value !== undefined) { + // Do we want to filter out undefineds? doc[key] = value; } } @@ -403,10 +427,10 @@ export namespace Doc { // compare whether documents or their protos match export function AreProtosEqual(doc?: Doc, other?: Doc) { if (!doc || !other) return false; - const r = (doc === other); - const r2 = (Doc.GetProto(doc) === other); - const r3 = (Doc.GetProto(other) === doc); - const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined); + const r = doc === other; + const r2 = Doc.GetProto(doc) === other; + const r3 = Doc.GetProto(other) === doc; + const r4 = Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined; return r || r2 || r3 || r4; } @@ -417,7 +441,7 @@ export namespace Doc { if (doc instanceof Promise) { // console.log("GetProto: warning: got Promise insead of Doc"); } - const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc)); + const proto = doc && (Doc.GetT(doc, 'isPrototype', 'boolean', true) ? doc : doc.proto || doc); return proto === doc ? proto : Doc.GetProto(proto); } export function GetDataDoc(doc: Doc): Doc { @@ -426,7 +450,7 @@ export namespace Doc { } export function allKeys(doc: Doc): string[] { - const results: Set = new Set; + const results: Set = new Set(); let proto: Doc | undefined = doc; while (proto) { @@ -441,8 +465,8 @@ export namespace Doc { * @returns the index of doc toFind in list of docs, -1 otherwise */ export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) { - let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1); - index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1); + let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind ? i : p), -1); + index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind) ? i : p), -1); return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); } @@ -478,7 +502,7 @@ export namespace Doc { const list = Cast(listDoc[key], listSpec(Doc)); if (list) { if (allowDuplicates !== true) { - const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1); + const pind = list.reduce((l, d, i) => (d instanceof Doc && d[Id] === doc[Id] ? i : l), -1); if (pind !== -1) { return true; //list.splice(pind, 1); // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection @@ -486,15 +510,13 @@ export namespace Doc { } if (first) { list.splice(0, 0, doc); - } - else { + } else { const ind = relativeTo ? list.indexOf(relativeTo) : -1; if (ind === -1) { if (reversed) list.splice(0, 0, doc); else list.push(doc); - } - else { - if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc); + } else { + if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc); else list.splice(before ? ind : ind + 1, 0, doc); } } @@ -507,19 +529,24 @@ export namespace Doc { * Computes the bounds of the contents of a set of documents. */ export function ComputeContentBounds(docList: Doc[]) { - const bounds = docList.reduce((bounds, doc) => { - const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }); + const bounds = docList.reduce( + (bounds, doc) => { + const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; + const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + return { + x: Math.min(sptX, bounds.x), + y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), + b: Math.max(bptY, bounds.b), + }; + }, + { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE } + ); return bounds; } export function MakeAlias(doc: Doc, id?: string) { - const alias = !GetT(doc, "isPrototype", "boolean", true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); + const alias = !GetT(doc, 'isPrototype', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); const layout = Doc.LayoutField(alias); if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) { Doc.SetLayout(alias, Doc.MakeAlias(layout)); @@ -529,71 +556,73 @@ export namespace Doc { alias.title = ComputedField.MakeFunction(`renameAlias(this)`); alias.author = Doc.CurrentUserEmail; - Doc.AddDocToList(Doc.GetProto(doc)[DataSym], "aliases", alias); + Doc.AddDocToList(Doc.GetProto(doc)[DataSym], 'aliases', alias); return alias; } - export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise { + export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; - const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true); + const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true); cloneMap.set(doc[Id], copy); - const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions; - const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])]; - await Promise.all(Object.keys(doc).map(async key => { - if (filter.includes(key)) return; - const assignKey = (val: any) => !dontCreate && (copy[key] = val); - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = ProxyField.WithoutProxy(() => doc[key]); - const copyObjectField = async (field: ObjectField) => { - const list = await Cast(doc[key], listSpec(Doc)); - const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); - if (docs !== undefined && docs.length) { - const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch))); - !dontCreate && assignKey(new List(clones)); - } else if (doc[key] instanceof Doc) { - assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields - } else { - !dontCreate && assignKey(ObjectField.MakeCopy(field)); - if (field instanceof RichTextField) { - if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) { - rtfs.push({ copy, key, field }); + const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions; + const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; + await Promise.all( + Object.keys(doc).map(async key => { + if (filter.includes(key)) return; + const assignKey = (val: any) => !dontCreate && (copy[key] = val); + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const field = ProxyField.WithoutProxy(() => doc[key]); + const copyObjectField = async (field: ObjectField) => { + const list = await Cast(doc[key], listSpec(Doc)); + const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); + if (docs !== undefined && docs.length) { + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch))); + !dontCreate && assignKey(new List(clones)); + } else if (doc[key] instanceof Doc) { + assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields + } else { + !dontCreate && assignKey(ObjectField.MakeCopy(field)); + if (field instanceof RichTextField) { + if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) { + rtfs.push({ copy, key, field }); + } } } - } - }; - if (key === "proto") { - if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); - } - } else if (key === "anchor1" || key === "anchor2") { - if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch)); - } - } else { - if (field instanceof RefField) { - assignKey(field); - } else if (cfield instanceof ComputedField) { - !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript)); - } else if (field instanceof ObjectField) { - await copyObjectField(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happen... + }; + if (key === 'proto') { + if (doc[key] instanceof Doc) { + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); + } + } else if (key === 'anchor1' || key === 'anchor2') { + if (doc[key] instanceof Doc) { + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch)); + } } else { - assignKey(field); + if (field instanceof RefField) { + assignKey(field); + } else if (cfield instanceof ComputedField) { + !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript)); + } else if (field instanceof ObjectField) { + await copyObjectField(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happen... + } else { + assignKey(field); + } } - } - })); + }) + ); for (const link of Array.from(doc[DirectLinksSym])) { const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); linkMap.set(link, linkClone); } if (!dontCreate) { - Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true); + Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc); if (!Doc.IsPrototype(copy)) { - Doc.AddDocToList(doc, "branches", Doc.GetProto(copy)); + Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy)); } cloneMap.set(doc[Id], copy); } @@ -601,20 +630,20 @@ export namespace Doc { } export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { const linkMap = new Map(); - const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], dontCreate, asBranch); + const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch); Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); - return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; + return attr + '"' + (mapped ? mapped[Id] : id) + '"'; }; const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); return href + (mapped ? mapped[Id] : id); }; const regex = `(${Doc.localServerPath()})([^"]*)`; - const re = new RegExp(regex, "g"); + const re = new RegExp(regex, 'g'); copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); }); return { clone: copy, map: cloneMap }; @@ -628,37 +657,36 @@ export namespace Doc { // a.click(); const { clone, map } = await Doc.MakeClone(doc, true); function replacer(key: any, value: any) { - if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined; + if (['branchOf', 'cloneOf', 'context', 'cursors'].includes(key)) return undefined; else if (value instanceof Doc) { - if (key !== "field" && Number.isNaN(Number(key))) { + if (key !== 'field' && Number.isNaN(Number(key))) { const __fields = value[FieldsSym](); - return { id: value[Id], __type: "Doc", fields: __fields }; + return { id: value[Id], __type: 'Doc', fields: __fields }; } else { - return { fieldId: value[Id], __type: "proxy" }; + return { fieldId: value[Id], __type: 'proxy' }; } - } - else if (value instanceof ScriptField) return { script: value.script, __type: "script" }; - else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" }; - else if (value instanceof ImageField) return { url: value.url.href, __type: "image" }; - else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" }; - else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" }; - else if (value instanceof VideoField) return { url: value.url.href, __type: "video" }; - else if (value instanceof WebField) return { url: value.url.href, __type: "web" }; - else if (value instanceof MapField) return { url: value.url.href, __type: "map" }; - else if (value instanceof DateField) return { date: value.toString(), __type: "date" }; - else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" }; - else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" }; - else if (value instanceof ComputedField) return { script: value.script, __type: "computed" }; + } else if (value instanceof ScriptField) return { script: value.script, __type: 'script' }; + else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: 'RichTextField' }; + else if (value instanceof ImageField) return { url: value.url.href, __type: 'image' }; + else if (value instanceof PdfField) return { url: value.url.href, __type: 'pdf' }; + else if (value instanceof AudioField) return { url: value.url.href, __type: 'audio' }; + else if (value instanceof VideoField) return { url: value.url.href, __type: 'video' }; + else if (value instanceof WebField) return { url: value.url.href, __type: 'web' }; + else if (value instanceof MapField) return { url: value.url.href, __type: 'map' }; + else if (value instanceof DateField) return { date: value.toString(), __type: 'date' }; + else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: 'proxy' }; + else if (value instanceof Array && key !== 'fields') return { fields: value, __type: 'list' }; + else if (value instanceof ComputedField) return { script: value.script, __type: 'computed' }; else return value; } const docs: { [id: string]: any } = {}; - Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]); + Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); const docString = JSON.stringify({ id: doc[Id], docs }, replacer); const zip = new JSZip(); - zip.file("doc.json", docString); + zip.file('doc.json', docString); // // Generate a directory within the Zip file structure // var img = zip.folder("images"); @@ -667,11 +695,10 @@ export namespace Doc { // img.file("smile.gif", imgData, {base64: true}); // Generate the zip file asynchronously - zip.generateAsync({ type: "blob" }) - .then((content: any) => { - // Force down of the Zip file - saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title? - }); + zip.generateAsync({ type: 'blob' }).then((content: any) => { + // Force down of the Zip file + saveAs(content, doc.title + '.zip'); // glr: Possibly change the name of the document to match the title? + }); } // // Determines whether the layout needs to be expanded (as a template). @@ -694,46 +721,47 @@ export namespace Doc { // layout_mytemplate(somparam=somearg). // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) { - const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS); - if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc; + const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || StrCast(templateLayoutDoc.PARAMS); + if ((!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc)) || !targetDoc) return templateLayoutDoc; - const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders + const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc // using the template layout doc's id as the field key. // If it doesn't find the expanded layout, then it makes a delegate of the template layout and // saves it on the data doc indexed by the template layout's id. // - const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS"; + const params = args.split('=').length > 1 ? args.split('=')[0] : 'PARAMS'; const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc); - const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]"; + const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']'; let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; if (templateLayoutDoc.resolvedDataDoc instanceof Promise) { expandedTemplateLayout = undefined; _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); - } - else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { + } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype if (!targetDoc[expandedLayoutFieldKey]) { _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true); - setTimeout(action(() => { - const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"); - // the template's arguments are stored in params which is derefenced to find - // the actual field key where the parameterized template data is stored. - newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances - newLayoutDoc.rootDocument = targetDoc; - const dataDoc = Doc.GetProto(targetDoc); - newLayoutDoc.resolvedDataDoc = dataDoc; - if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) { - dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc }); - } - targetDoc[expandedLayoutFieldKey] = newLayoutDoc; - - _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args); - })); + setTimeout( + action(() => { + const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, '[' + templateLayoutDoc.title + ']'); + // the template's arguments are stored in params which is derefenced to find + // the actual field key where the parameterized template data is stored. + newLayoutDoc[params] = args !== '...' ? args : ''; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances + newLayoutDoc.rootDocument = targetDoc; + const dataDoc = Doc.GetProto(targetDoc); + newLayoutDoc.resolvedDataDoc = dataDoc; + if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) { + dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc }); + } + targetDoc[expandedLayoutFieldKey] = newLayoutDoc; + + _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args); + }) + ); } } } @@ -744,17 +772,17 @@ export namespace Doc { // otherwise, it just returns the childDoc export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt, childDoc: Doc) { if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) { - console.log("No, no, no!"); + console.log('No, no, no!'); return { layout: childDoc, data: childDoc }; } - const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc); - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc }; + const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc; + return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc }; } export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { Object.keys(doc).forEach(key => { const field = ProxyField.WithoutProxy(() => doc[key]); - if (key === "proto" && copyProto) { + if (key === 'proto' && copyProto) { if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) { overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto); } @@ -776,12 +804,12 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc { const copy = new Doc(copyProtoId, true); - const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []); + const exclude = Cast(doc.cloneFieldFilter, listSpec('string'), []); Object.keys(doc).forEach(key => { if (exclude.includes(key)) return; const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); - if (key === "proto" && copyProto) { + if (key === 'proto' && copyProto) { if (doc[key] instanceof Doc) { copy[key] = Doc.MakeCopy(doc[key]!, false); } @@ -789,11 +817,14 @@ export namespace Doc { if (field instanceof RefField) { copy[key] = field; } else if (cfield instanceof ComputedField) { - copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript); + copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript); } else if (field instanceof ObjectField) { - copy[key] = doc[key] instanceof Doc ? - key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields - ObjectField.MakeCopy(field); + copy[key] = + doc[key] instanceof Doc + ? key.includes('layout[') + ? undefined + : doc[key] // reference documents except remove documents that are expanded teplate fields + : ObjectField.MakeCopy(field); } else if (field instanceof Promise) { debugger; //This shouldn't happend... } else { @@ -806,17 +837,16 @@ export namespace Doc { Doc.GetProto(copy).context = undefined; Doc.GetProto(copy).aliases = new List([copy]); } else { - Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy); + Doc.AddDocToList(Doc.GetProto(copy)[DataSym], 'aliases', copy); } copy.context = undefined; - Doc.defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared'); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } return copy; } - export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc; export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt; export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt { @@ -825,8 +855,10 @@ export namespace Doc { delegate[Initializing] = true; delegate.proto = doc; delegate.author = Doc.CurrentUserEmail; - Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => delegate[key] = doc[key]); - if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate); + Object.keys(doc) + .filter(key => key.startsWith('acl')) + .forEach(key => (delegate[key] = doc[key])); + if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], 'aliases', delegate); title && (delegate.title = title); delegate[Initializing] = false; return delegate; @@ -849,7 +881,7 @@ export namespace Doc { delegate[Initializing] = true; delegate.proto = delegateProto; delegate.author = Doc.CurrentUserEmail; - Doc.AddDocToList(delegateProto[DataSym], "aliases", delegate); + Doc.AddDocToList(delegateProto[DataSym], 'aliases', delegate); delegate[Initializing] = false; delegateProto[Initializing] = false; return delegate; @@ -861,11 +893,11 @@ export namespace Doc { const proto = new Doc(); proto.author = Doc.CurrentUserEmail; const target = Doc.MakeDelegate(proto); - const targetKey = StrCast(templateDoc.layoutKey, "layout"); - const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")"); + const targetKey = StrCast(templateDoc.layoutKey, 'layout'); + const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')'); target.layoutKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.defaultAclPrivate && (applied["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared'); return applied; } return undefined; @@ -888,9 +920,8 @@ export namespace Doc { // metadata field indicated by the title of the template field (not the default field that it was rendering) // export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt): boolean { - // find the metadata field key that this template field doc will display (indicated by its title) - const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, ""); + const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, ''); // update the original template to mark it as a template templateField.isTemplateForField = metadataFieldKey; @@ -904,7 +935,7 @@ export namespace Doc { // note 2: this will not overwrite any field that already exists on the template doc at the field key if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) { Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc)); - (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue)); + Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue); } // get the layout string that the template uses to specify its layout const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField))); @@ -913,19 +944,18 @@ export namespace Doc { Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`); // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino) - Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc); + Doc.Layout(templateField)[metadataFieldKey + '_ext'] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + '_ext'] as Doc); return true; } - // converts a document id to a url path on the server - export function globalServerPath(doc: Doc | string = ""): string { - return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc)); + export function globalServerPath(doc: Doc | string = ''): string { + return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc)); } // converts a document id to a url path on the server export function localServerPath(doc?: Doc): string { - return "/doc/" + (doc ? doc[Id] : ""); + return '/doc/' + (doc ? doc[Id] : ''); } export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { @@ -958,47 +988,70 @@ export namespace Doc { export class DocData { @observable _user_doc: Doc = undefined!; @observable _sharing_doc: Doc = undefined!; - @observable _searchQuery: string = ""; + @observable _searchQuery: string = ''; } // the document containing the view layout information - will be the Document itself unless the Document has // a layout field or 'layout' is given. export function Layout(doc: Doc, layout?: Doc): Doc { - const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null); + const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}-layout[` + layout[Id] + ']'], Doc, null); return overrideLayout || doc[LayoutSym] || doc; } - export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; } - export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } - export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; } + export function SetLayout(doc: Doc, layout: Doc | string) { + doc[StrCast(doc.layoutKey, 'layout')] = layout; + } + export function LayoutField(doc: Doc) { + return doc[StrCast(doc.layoutKey, 'layout')]; + } + export function LayoutFieldKey(doc: Doc): string { + return StrCast(Doc.Layout(doc).layout).split("'")[1]; + } export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); } - export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeWidth"], useWidth ? doc[WidthSym]() : 0)); } + export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { + return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0)); + } export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { - const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0) : 0; - const nheight = doc ? Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]() / doc[WidthSym]() : 0; + const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0) : 0; + const nheight = doc ? (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]() : 0; return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight); } - export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeWidth"] = width; } - export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeHeight"] = height; } - + export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { + doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width; + } + export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { + doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeHeight'] = height; + } const manager = new DocData(); - export function SearchQuery(): string { return manager._searchQuery; } - export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } - export function UserDoc(): Doc { return manager._user_doc; } - export function SharingDoc(): Doc { return CurrentUserUtils.MySharedDocs; } - export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); } - export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); } + export function SearchQuery(): string { + return manager._searchQuery; + } + export function SetSearchQuery(query: string) { + runInAction(() => (manager._searchQuery = query)); + } + export function UserDoc(): Doc { + return manager._user_doc; + } + export function SharingDoc(): Doc { + return CurrentUserUtils.MySharedDocs; + } + export function LinkDBDoc(): Doc { + return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); + } + export function SetUserDoc(doc: Doc) { + return (manager._user_doc = doc); + } const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) { - return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : - brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; + return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; }); - export function IsSearchMatch(doc: Doc) { return isSearchMatchCache(doc); } + export function IsSearchMatch(doc: Doc) { + return isSearchMatchCache(doc); + } export function IsSearchMatchUnmemoized(doc: Doc) { - return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : - brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; + return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; } export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) { if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) { @@ -1017,8 +1070,12 @@ export namespace Doc { brushManager.SearchMatchDoc.clear(); } - const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); }); - export function IsBrushed(doc: Doc) { return isBrushedCache(doc); } + const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { + return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); + }); + export function IsBrushed(doc: Doc) { + return isBrushedCache(doc); + } export enum DocBrushStatus { unbrushed = 0, @@ -1037,9 +1094,7 @@ export namespace Doc { for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) { const a1 = Cast(link.anchor1, Doc, null); const a2 = Cast(link.anchor2, Doc, null); - if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || - (Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc)) || - (Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc))) { + if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc) || Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc)) { return DocBrushStatus.linkHighlighted; } } @@ -1070,22 +1125,21 @@ export namespace Doc { } export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { - return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || - Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? "1" : "2"; + return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? '1' : '2'; } export function linkFollowUnhighlight() { Doc.UnhighlightAll(); - document.removeEventListener("pointerdown", linkFollowUnhighlight); + document.removeEventListener('pointerdown', linkFollowUnhighlight); } let _lastDate = 0; export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true) { linkFollowUnhighlight(); (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs)); - document.removeEventListener("pointerdown", linkFollowUnhighlight); - document.addEventListener("pointerdown", linkFollowUnhighlight); - const lastDate = _lastDate = Date.now(); + document.removeEventListener('pointerdown', linkFollowUnhighlight); + document.addEventListener('pointerdown', linkFollowUnhighlight); + const lastDate = (_lastDate = Date.now()); window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000); } @@ -1116,53 +1170,57 @@ export namespace Doc { const targetDoc = docEntry.value; targetDoc && Doc.UnHighlightDoc(targetDoc); } - } export function UnBrushAllDocs() { brushManager.BrushedDoc.clear(); } export function getDocTemplate(doc?: Doc) { - return !doc ? undefined : - doc.isTemplateDoc ? doc : - Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory : - Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ? - (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) : - undefined; + return !doc + ? undefined + : doc.isTemplateDoc + ? doc + : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc + ? doc.dragFactory + : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc + ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc + ? Doc.Layout(doc).proto + : Doc.Layout(doc) + : undefined; } export function matchFieldValue(doc: Doc, key: string, value: any): boolean { if (Utils.HasTransparencyFilter(value)) { - const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1); + const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1; return isTransparent(StrCast(doc[key])); } - if (typeof value === "string") { - value = value.replace(`,${Utils.noRecursionHack}`, ""); + if (typeof value === 'string') { + value = value.replace(`,${Utils.noRecursionHack}`, ''); } - const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key]; - if (Cast(fieldVal, listSpec("string"), []).length) { - const vals = Cast(fieldVal, listSpec("string"), []); + const fieldVal = key === '#' ? (StrCast(doc.tags).includes(':#' + value + ':') ? StrCast(doc.tags) : undefined) : doc[key]; + if (Cast(fieldVal, listSpec('string'), []).length) { + const vals = Cast(fieldVal, listSpec('string'), []); const docs = vals.some(v => (v as any) instanceof Doc); if (docs) return value === Field.toString(fieldVal as Field); - return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } const fieldStr = Field.toString(fieldVal as Field); return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { - StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); + StrCast(doc.layoutKey).split('_')[1] === 'icon' && setNativeView(doc); } export function setNativeView(doc: any) { - const prevLayout = StrCast(doc.layoutKey).split("_")[1]; - const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : ""; - prevLayout === "icon" && (doc.deiconifyLayout = undefined); - doc.layoutKey = deiconify || "layout"; + const prevLayout = StrCast(doc.layoutKey).split('_')[1]; + const deiconify = prevLayout === 'icon' && StrCast(doc.deiconifyLayout) ? 'layout_' + StrCast(doc.deiconifyLayout) : ''; + prevLayout === 'icon' && (doc.deiconifyLayout = undefined); + doc.layoutKey = deiconify || 'layout'; } export function setDocRangeFilter(container: Opt, key: string, range?: number[]) { if (!container) return; - const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []); + const docRangeFilters = Cast(container._docRangeFilters, listSpec('string'), []); for (let i = 0; i < docRangeFilters.length; i += 3) { if (docRangeFilters[i] === key) { docRangeFilters.splice(i, 3); @@ -1180,16 +1238,16 @@ export namespace Doc { // filters document in a container collection: // all documents with the specified value for the specified key are included/excluded // based on the modifiers :"check", "x", undefined - export function setDocFilter(container: Opt, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) { + export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldSuffix?: string, append: boolean = true) { if (!container) return; - const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters"; - const docFilters = Cast(container[filterField], listSpec("string"), []); + const filterField = '_' + (fieldSuffix ? fieldSuffix + '-' : '') + 'docFilters'; + const docFilters = Cast(container[filterField], listSpec('string'), []); runInAction(() => { for (let i = 0; i < docFilters.length; i++) { - const fields = docFilters[i].split(":"); // split key:value:modifier - if (fields[0] === key && (fields[1] === value || modifiers === "match")) { + const fields = docFilters[i].split(':'); // split key:value:modifier + if (fields[0] === key && (fields[1] === value || modifiers === 'match')) { if (fields[2] === modifiers && modifiers && fields[1] === value) { - if (toggle) modifiers = "remove"; + if (toggle) modifiers = 'remove'; else return; } docFilters.splice(i, 1); @@ -1197,17 +1255,17 @@ export namespace Doc { break; } } - if (!docFilters.length && modifiers === "match" && value === undefined) { + if (!docFilters.length && modifiers === 'match' && value === undefined) { container[filterField] = undefined; - } else if (modifiers !== "remove") { + } else if (modifiers !== 'remove') { !append && (docFilters.length = 0); - docFilters.push(key + ":" + value + ":" + modifiers); + docFilters.push(key + ':' + value + ':' + modifiers); container[filterField] = new List(docFilters); } }); } export function readDocRangeFilter(doc: Doc, key: string) { - const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []); + const docRangeFilters = Cast(doc._docRangeFilters, listSpec('string'), []); for (let i = 0; i < docRangeFilters.length; i += 3) { if (docRangeFilters[i] === key) { return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])]; @@ -1225,8 +1283,7 @@ export namespace Doc { layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale; layoutDoc._nativeWidth = undefined; layoutDoc._nativeHeight = undefined; - } - else { + } else { layoutDoc._autoHeight = false; if (!Doc.NativeWidth(layoutDoc)) { layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth); @@ -1239,44 +1296,61 @@ export namespace Doc { export function isDocPinned(doc: Doc) { //add this new doc to props.Document const curPres = CurrentUserUtils.ActivePresentation; - return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; + return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1; } export function toIcon(doc?: Doc, isOpen?: boolean) { switch (StrCast(doc?.type)) { - case DocumentType.IMG: return "image"; - case DocumentType.COMPARISON: return "columns"; - case DocumentType.RTF: return "sticky-note"; + case DocumentType.IMG: + return 'image'; + case DocumentType.COMPARISON: + return 'columns'; + case DocumentType.RTF: + return 'sticky-note'; case DocumentType.COL: - const folder: IconProp = isOpen ? "folder-open" : "folder"; - const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right"; + const folder: IconProp = isOpen ? 'folder-open' : 'folder'; + const chevron: IconProp = isOpen ? 'chevron-down' : 'chevron-right'; return !doc?.isFolder ? folder : chevron; - case DocumentType.WEB: return "globe-asia"; - case DocumentType.SCREENSHOT: return "photo-video"; - case DocumentType.WEBCAM: return "video"; - case DocumentType.AUDIO: return "microphone"; - case DocumentType.BUTTON: return "bolt"; - case DocumentType.PRES: return "tv"; - case DocumentType.SCRIPTING: return "terminal"; - case DocumentType.IMPORT: return "cloud-upload-alt"; - case DocumentType.VID: return "video"; - case DocumentType.INK: return "pen-nib"; - case DocumentType.PDF: return "file-pdf"; - case DocumentType.LINK: return "link"; - case DocumentType.MAP: return "map-marker-alt"; - default: return "question"; + case DocumentType.WEB: + return 'globe-asia'; + case DocumentType.SCREENSHOT: + return 'photo-video'; + case DocumentType.WEBCAM: + return 'video'; + case DocumentType.AUDIO: + return 'microphone'; + case DocumentType.BUTTON: + return 'bolt'; + case DocumentType.PRES: + return 'tv'; + case DocumentType.SCRIPTING: + return 'terminal'; + case DocumentType.IMPORT: + return 'cloud-upload-alt'; + case DocumentType.VID: + return 'video'; + case DocumentType.INK: + return 'pen-nib'; + case DocumentType.PDF: + return 'file-pdf'; + case DocumentType.LINK: + return 'link'; + case DocumentType.MAP: + return 'map-marker-alt'; + default: + return 'question'; } } - export async function importDocument(file:File) { - const upload = Utils.prepend("/uploadDoc"); + export async function importDocument(file: File) { + const upload = Utils.prepend('/uploadDoc'); const formData = new FormData(); if (file) { formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); + formData.append('remap', 'true'); + const response = await fetch(upload, { method: 'POST', body: formData }); const json = await response.json(); - if (json !== "error") { + if (json !== 'error') { const doc = await DocServer.GetRefField(json); return doc; } @@ -1285,17 +1359,16 @@ export namespace Doc { } export namespace Get { - - const primitives = ["string", "number", "boolean"]; + const primitives = ['string', 'number', 'boolean']; export interface JsonConversionOpts { data: any; title?: string; - appendToExisting?: { targetDoc: Doc, fieldKey?: string }; + appendToExisting?: { targetDoc: Doc; fieldKey?: string }; excludeEmptyObjects?: boolean; } - const defaultKey = "json"; + const defaultKey = 'json'; /** * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily @@ -1340,17 +1413,17 @@ export namespace Doc { if (excludeEmptyObjects === undefined) { excludeEmptyObjects = true; } - if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) { + if (data === undefined || data === null || ![...primitives, 'object'].includes(typeof data)) { return undefined; } let resolved: any; try { - resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data)); + resolved = JSON.parse(typeof data === 'string' ? data : JSON.stringify(data)); } catch (e) { return undefined; } let output: Opt; - if (typeof resolved === "object" && !(resolved instanceof Array)) { + if (typeof resolved === 'object' && !(resolved instanceof Array)) { output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc); } else { // give the proper types to the data extracted from the JSON @@ -1358,7 +1431,7 @@ export namespace Doc { if (appendToExisting) { (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result; } else { - (output = new Doc).json = result; + (output = new Doc()).json = result; } } title && output && (output.title = title); @@ -1375,14 +1448,14 @@ export namespace Doc { const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt => { const hasEntries = Object.keys(object).length; if (hasEntries || !excludeEmptyObjects) { - const resolved = target ?? new Doc; + const resolved = target ?? new Doc(); if (hasEntries) { let result: Opt; Object.keys(object).map(key => { // if excludeEmptyObjects is true, any qualifying conversions from toField will // be undefined, and thus the results that would have // otherwise been empty (List or Doc)s will just not be written - if (result = toField(object[key], excludeEmptyObjects, key)) { + if ((result = toField(object[key], excludeEmptyObjects, key))) { resolved[key] = result; } }); @@ -1418,40 +1491,78 @@ export namespace Doc { if (primitives.includes(typeof data)) { return data; } - if (typeof data === "object") { + if (typeof data === 'object') { return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined); } throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`); }; } - } -ScriptingGlobals.add(function idToDoc(id: string): any { return DocServer.GetCachedRefField(id); }); -ScriptingGlobals.add(function renameAlias(doc: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${doc.aliasNumber})`; }); -ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); }); -ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); -ScriptingGlobals.add(function getAlias(doc: any) { return Doc.MakeAlias(doc); }); -ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); -ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); -ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); -ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); -ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); -ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); -ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); }); -ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; }); -ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); }); -ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); }); +ScriptingGlobals.add(function idToDoc(id: string): any { + return DocServer.GetCachedRefField(id); +}); +ScriptingGlobals.add(function renameAlias(doc: any) { + return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.aliasNumber})`; +}); +ScriptingGlobals.add(function getProto(doc: any) { + return Doc.GetProto(doc); +}); +ScriptingGlobals.add(function getDocTemplate(doc?: any) { + return Doc.getDocTemplate(doc); +}); +ScriptingGlobals.add(function getAlias(doc: any) { + return Doc.MakeAlias(doc); +}); +ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { + return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); +}); +ScriptingGlobals.add(function copyField(field: any) { + return Field.Copy(field); +}); +ScriptingGlobals.add(function docList(field: any) { + return DocListCast(field); +}); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added: Doc) { + return Doc.AddDocToList(doc, field, added); +}); +ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { + return Doc.SetInPlace(doc, field, value, false); +}); +ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { + return Doc.AreProtosEqual(doc1, doc2); +}); +ScriptingGlobals.add(function undo() { + SelectionManager.DeselectAll(); + return UndoManager.Undo(); +}); +ScriptingGlobals.add(function redo() { + SelectionManager.DeselectAll(); + return UndoManager.Redo(); +}); +ScriptingGlobals.add(function DOC(id: string) { + console.log("Can't parse a document id in a script"); + return 'invalid'; +}); +ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { + return Doc.assignDocToField(doc, field, id); +}); +ScriptingGlobals.add(function docCast(doc: FieldResult): any { + return DocCastAsync(doc); +}); ScriptingGlobals.add(function activePresentationItem() { const curPres = CurrentUserUtils.ActivePresentation; return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; }); ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { - const docs = SelectionManager.Views().map(dv => dv.props.Document). - filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && - (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); + const docs = SelectionManager.Views() + .map(dv => dv.props.Document) + .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); return docs.length ? new List(docs) : prevValue; }); -ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: "match" | "check" | "x" | "remove") { Doc.setDocFilter(container, key, value, modifiers); }); -ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { Doc.setDocRangeFilter(container, key, range); }); +ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') { + Doc.setDocFilter(container, key, value, modifiers); +}); +ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { + Doc.setDocRangeFilter(container, key, range); +}); diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index a19be5df9..bf055cd8b 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -1,48 +1,46 @@ -import { AssertionError } from "assert"; -import { docs_v1 } from "googleapis"; -import { Fragment, Mark, Node } from "prosemirror-model"; -import { sinkListItem } from "prosemirror-schema-list"; -import { Utils, DashColor } from "../Utils"; -import { Docs, DocUtils } from "../client/documents/Documents"; -import { schema } from "../client/views/nodes/formattedText/schema_rts"; -import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; -import { DocServer } from "../client/DocServer"; -import { Networking } from "../client/Network"; -import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox"; -import { Doc, Opt } from "./Doc"; -import { Id } from "./FieldSymbols"; -import { RichTextField } from "./RichTextField"; -import { Cast, StrCast } from "./Types"; +import { AssertionError } from 'assert'; +import { docs_v1 } from 'googleapis'; +import { Fragment, Mark, Node } from 'prosemirror-model'; +import { sinkListItem } from 'prosemirror-schema-list'; +import { Utils, DashColor } from '../Utils'; +import { Docs, DocUtils } from '../client/documents/Documents'; +import { schema } from '../client/views/nodes/formattedText/schema_rts'; +import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils'; +import { DocServer } from '../client/DocServer'; +import { Networking } from '../client/Network'; +import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox'; +import { Doc, Opt } from './Doc'; +import { Id } from './FieldSymbols'; +import { RichTextField } from './RichTextField'; +import { Cast, StrCast } from './Types'; import Color = require('color'); -import { EditorState, TextSelection, Transaction } from "prosemirror-state"; -import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; +import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; +import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils'; export namespace RichTextUtils { - - const delimiter = "\n"; - const joiner = ""; - + const delimiter = '\n'; + const joiner = ''; export const Initialize = (initial?: string) => { const content: any[] = []; const state = { doc: { - type: "doc", + type: 'doc', content, }, selection: { - type: "text", + type: 'text', anchor: 0, - head: 0 - } + head: 0, + }, }; if (initial && initial.length) { content.push({ - type: "paragraph", + type: 'paragraph', content: { - type: "text", - text: initial - } + type: 'text', + text: initial, + }, }); state.selection.anchor = state.selection.head = initial.length + 1; } @@ -56,8 +54,8 @@ export namespace RichTextUtils { export const ToPlainText = (state: EditorState) => { // Because we're working with plain text, just concatenate all paragraphs const content = state.doc.content; - const paragraphs: Node[] = []; - content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node)); + const paragraphs: Node[] = []; + content.forEach(node => node.type.name === 'paragraph' && paragraphs.push(node)); // Functions to flatten ProseMirror paragraph objects (and their components) to plain text // Concatentate paragraphs and string the result together @@ -80,22 +78,21 @@ export namespace RichTextUtils { // Preserve the current state, but re-write the content to be the blocks const parsed = JSON.parse(oldState ? oldState.Data : Initialize()); parsed.doc.content = elements.map(text => { - const paragraph: any = { type: "paragraph" }; - text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break + const paragraph: any = { type: 'paragraph' }; + text.length && (paragraph.content = [{ type: 'text', marks: [], text }]); // An empty paragraph gets treated as a line break return paragraph; }); // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it - parsed.selection = { type: "text", anchor: 1, head: 1 }; + parsed.selection = { type: 'text', anchor: 1, head: 1 }; // Export the ProseMirror-compatible state object we've just built return JSON.stringify(parsed); }; export namespace GoogleDocs { - export const Export = async (state: EditorState): Promise => { - const nodes: (Node | null)[] = []; + const nodes: (Node | null)[] = []; const text = ToPlainText(state); state.doc.content.forEach(node => { if (!node.childCount) { @@ -126,10 +123,10 @@ export namespace RichTextUtils { return { baseUrl: embeddedObject.imageProperties!.contentUri! }; }); - const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems }); + const uploads = await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems }); if (uploads.length !== mediaItems.length) { - throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" }); + throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: 'Error with internally uploading inlineObjects!' }); } for (let i = 0; i < objects.length; i++) { @@ -144,14 +141,14 @@ export namespace RichTextUtils { title: embeddedObject.title || `Imported Image from ${document.title}`, width, url: Utils.prepend(_m.client), - agnostic: Utils.prepend(agnostic.client) + agnostic: Utils.prepend(agnostic.client), }); } } return inlineObjectMap; }; - type BulletPosition = { value: number, sinks: number }; + type BulletPosition = { value: number; sinks: number }; interface MediaItem { baseUrl: string; @@ -172,7 +169,7 @@ export namespace RichTextUtils { const lists: ListGroup[] = []; const indentMap = new Map(); let globalOffset = 0; - const nodes: Node[] = []; + const nodes: Node[] = []; for (const element of structured) { if (Array.isArray(element)) { lists.push(element); @@ -182,7 +179,7 @@ export namespace RichTextUtils { const sinks = paragraph.bullet!; positions.push({ value: position + globalOffset, - sinks + sinks, }); position += item.nodeSize; globalOffset += 2 * sinks; @@ -191,13 +188,13 @@ export namespace RichTextUtils { indentMap.set(element, positions); nodes.push(list(state.schema, items)); } else { - if (element.contents.some(child => "inlineObjectId" in child)) { + if (element.contents.some(child => 'inlineObjectId' in child)) { const group = element.contents; group.forEach((child, i) => { - let node: Opt>; - if ("inlineObjectId" in child) { + let node: Opt; + if ('inlineObjectId' in child) { node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); - } else if ("content" in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { + } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { node = paragraphNode(state.schema, [child]); } if (node) { @@ -215,7 +212,7 @@ export namespace RichTextUtils { state = state.apply(state.tr.replaceWith(0, 2, nodes)); const sink = sinkListItem(state.schema.nodes.list_item); - const dispatcher = (tr: Transaction) => state = state.apply(tr); + const dispatcher = (tr: Transaction) => (state = state.apply(tr)); for (const list of lists) { for (const pos of indentMap.get(list)!) { const resolved = state.doc.resolve(pos.value); @@ -252,17 +249,17 @@ export namespace RichTextUtils { }; const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { - return schema.node("list_item", null, paragraphNode(schema, runs)); + return schema.node('list_item', null, paragraphNode(schema, runs)); }; const list = (schema: any, items: Node[]): Node => { - return schema.node("ordered_list", { mapStyle: "bullet" }, items); + return schema.node('ordered_list', { mapStyle: 'bullet' }, items); }; const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined); const fragment = children.length ? Fragment.from(children) : undefined; - return schema.node("paragraph", null, fragment); + return schema.node('paragraph', null, fragment); }; const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => { @@ -278,7 +275,7 @@ export namespace RichTextUtils { } else { docid = backingDocId; } - return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" }); + return schema.node('image', { src, agnostic, width, docid, float: null, location: 'add:right' }); }; const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { @@ -287,10 +284,10 @@ export namespace RichTextUtils { }; const StyleToMark = new Map([ - ["bold", "strong"], - ["italic", "em"], - ["foregroundColor", "pFontColor"], - ["fontSize", "pFontSize"] + ['bold', 'strong'], + ['italic', 'em'], + ['foregroundColor', 'pFontColor'], + ['fontSize', 'pFontSize'], ]); const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { @@ -301,21 +298,21 @@ export namespace RichTextUtils { Object.keys(textStyle).forEach(key => { let value: any; const targeted = key as keyof docs_v1.Schema$TextStyle; - if (value = textStyle[targeted]) { + if ((value = textStyle[targeted])) { const attributes: any = {}; let converted = StyleToMark.get(targeted) || targeted; value.url && (attributes.href = value.url); if (value.color) { const object = value.color.rgbColor; - attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex(); + attributes.color = Color.rgb(['red', 'green', 'blue'].map(color => object[color] * 255 || 0)).hex(); } if (value.magnitude) { attributes.fontSize = value.magnitude; } - if (converted === "weightedFontFamily") { - converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman"; + if (converted === 'weightedFontFamily') { + converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman'; } const mapped = schema.marks[converted]; @@ -332,38 +329,38 @@ export namespace RichTextUtils { }; const MarkToStyle = new Map([ - ["strong", "bold"], - ["em", "italic"], - ["pFontColor", "foregroundColor"], - ["pFontSize", "fontSize"], - ["timesNewRoman", "weightedFontFamily"], - ["georgia", "weightedFontFamily"], - ["comicSans", "weightedFontFamily"], - ["tahoma", "weightedFontFamily"], - ["impact", "weightedFontFamily"] + ['strong', 'bold'], + ['em', 'italic'], + ['pFontColor', 'foregroundColor'], + ['pFontSize', 'fontSize'], + ['timesNewRoman', 'weightedFontFamily'], + ['georgia', 'weightedFontFamily'], + ['comicSans', 'weightedFontFamily'], + ['tahoma', 'weightedFontFamily'], + ['impact', 'weightedFontFamily'], ]); const ExportFontFamilyMapping = new Map([ - ["timesNewRoman", "Times New Roman"], - ["arial", "Arial"], - ["georgia", "Georgia"], - ["comicSans", "Comic Sans MS"], - ["tahoma", "Tahoma"], - ["impact", "Impact"] + ['timesNewRoman', 'Times New Roman'], + ['arial', 'Arial'], + ['georgia', 'Georgia'], + ['comicSans', 'Comic Sans MS'], + ['tahoma', 'Tahoma'], + ['impact', 'Impact'], ]); const ImportFontFamilyMapping = new Map([ - ["Times New Roman", "timesNewRoman"], - ["Arial", "arial"], - ["Georgia", "georgia"], - ["Comic Sans MS", "comicSans"], - ["Tahoma", "tahoma"], - ["Impact", "impact"] + ['Times New Roman', 'timesNewRoman'], + ['Arial', 'arial'], + ['Georgia', 'georgia'], + ['Comic Sans MS', 'comicSans'], + ['Tahoma', 'tahoma'], + ['Impact', 'impact'], ]); - const ignored = ["user_mark"]; + const ignored = ['user_mark']; - const marksToStyle = async (nodes: (Node | null)[]): Promise => { + const marksToStyle = async (nodes: (Node | null)[]): Promise => { const requests: docs_v1.Schema$Request[] = []; let position = 1; for (const node of nodes) { @@ -376,25 +373,25 @@ export namespace RichTextUtils { const information: LinkInformation = { startIndex: position, endIndex: position + nodeSize, - textStyle + textStyle, }; - let mark: Mark; + let mark: Mark; const markMap = BuildMarkMap(marks); for (const markName of Object.keys(schema.marks)) { if (ignored.includes(markName) || !(mark = markMap[markName])) { continue; } - let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle; + let converted = MarkToStyle.get(markName) || (markName as keyof docs_v1.Schema$TextStyle); let value: any = true; if (!converted) { continue; } const { attrs } = mark; switch (converted) { - case "link": - let url = attrs.allLinks.length ? attrs.allLinks[0].href : ""; - const delimiter = "/doc/"; - const alreadyShared = "?sharing=true"; + case 'link': + let url = attrs.allLinks.length ? attrs.allLinks[0].href : ''; + const delimiter = '/doc/'; + const alreadyShared = '?sharing=true'; if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); if (linkDoc instanceof Doc) { @@ -411,41 +408,43 @@ export namespace RichTextUtils { textStyle.foregroundColor = fromRgb.blue; textStyle.bold = true; break; - case "fontSize": - value = { magnitude: attrs.fontSize, unit: "PT" }; + case 'fontSize': + value = { magnitude: attrs.fontSize, unit: 'PT' }; break; - case "foregroundColor": + case 'foregroundColor': value = fromHex(attrs.color); break; - case "weightedFontFamily": + case 'weightedFontFamily': value = { fontFamily: ExportFontFamilyMapping.get(markName) }; } let matches: RegExpExecArray | null; if ((matches = /p(\d+)/g.exec(markName)) !== null) { - converted = "fontSize"; - value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" }; + converted = 'fontSize'; + value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' }; } textStyle[converted] = value; } if (Object.keys(textStyle).length) { requests.push(EncodeStyleUpdate(information)); } - if (node.type.name === "image") { + if (node.type.name === 'image') { const width = attrs.width; - requests.push(await EncodeImage({ - startIndex: position + nodeSize - 1, - uri: attrs.agnostic, - width: Number(typeof width === "string" ? width.replace("px", "") : width) - })); + requests.push( + await EncodeImage({ + startIndex: position + nodeSize - 1, + uri: attrs.agnostic, + width: Number(typeof width === 'string' ? width.replace('px', '') : width), + }) + ); } position += nodeSize; } return requests; }; - const BuildMarkMap = (marks: Mark[]) => { - const markMap: { [type: string]: Mark } = {}; - marks.forEach(mark => markMap[mark.type.name] = mark); + const BuildMarkMap = (marks: readonly Mark[]) => { + const markMap: { [type: string]: Mark } = {}; + marks.forEach(mark => (markMap[mark.type.name] = mark)); return markMap; }; @@ -462,23 +461,21 @@ export namespace RichTextUtils { } namespace fromRgb { - export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { return { color: { rgbColor: { red: red / 255, green: green / 255, - blue: blue / 255 - } - } + blue: blue / 255, + }, + }, }; }; export const red = convert(255, 0, 0); export const green = convert(0, 255, 0); export const blue = convert(0, 0, 255); - } const fromHex = (color: string): docs_v1.Schema$OptionalColor => { @@ -490,10 +487,10 @@ export namespace RichTextUtils { const { startIndex, endIndex, textStyle } = information; return { updateTextStyle: { - fields: "*", + fields: '*', range: { startIndex, endIndex }, - textStyle - } as docs_v1.Schema$UpdateTextStyleRequest + textStyle, + } as docs_v1.Schema$UpdateTextStyleRequest, }; }; @@ -507,13 +504,12 @@ export namespace RichTextUtils { return { insertInlineImage: { uri: baseUrls[0], - objectSize: { width: { magnitude: width, unit: "PT" } }, - location: { index: startIndex } - } + objectSize: { width: { magnitude: width, unit: 'PT' } }, + location: { index: startIndex }, + }, }; } return {}; }; } - -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From a257a76a1c54ffb64f95eea3894ee2b6ae35cbf7 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Sat, 2 Jul 2022 15:49:42 -0700 Subject: Update DocumentDecorations.tsx --- src/client/views/DocumentDecorations.tsx | 43 ++++++++++++++++---------------- 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 59675c986..8b83eaeb2 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -57,7 +57,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @observable public pushIcon: IconProp = 'arrow-alt-circle-up'; @observable public pullIcon: IconProp = 'arrow-alt-circle-down'; @observable public pullColor: string = 'white'; - @observable private _showRotationPath: boolean = false; + @observable private _isRotating: boolean = false; + @observable private _isRounding: boolean = false; + @observable private _isResizing: boolean = false; constructor(props: any) { super(props); @@ -271,6 +273,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P */ @action onRadiusDown = (e: React.PointerEvent): void => { + this._isRounding = true; this._resizeUndo = UndoManager.StartBatch('DocDecs set radius'); // Call util move event function setupMoveUpEvents( @@ -293,17 +296,20 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); return false; }, // moveEvent - e => this._resizeUndo?.end(), // upEvent + action(e => { + this._isRounding = false; + this._resizeUndo?.end() + }), // upEvent e => {} // clickEvent ); }; @action onRotateDown = (e: React.PointerEvent): void => { - this._showRotationPath = true; + this._isRotating = true; const rotateUndo = UndoManager.StartBatch('rotatedown'); const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke); - const centerPoint = !selectedInk.length ? { X: this.Bounds.x, Y: this.Bounds.y } : { X: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, Y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 }; + const centerPoint = { X: (this.Bounds.x + this.Bounds.r) / 2, Y: (this.Bounds.y + this.Bounds.b) / 2 }; setupMoveUpEvents( this, e, @@ -318,12 +324,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } return false; }, // moveEvent - () => { + action(() => { console.log('up') - action(() => this._showRotationPath = false); + this._isRotating = false; rotateUndo?.end(); UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); - }, // upEvent + }), // upEvent emptyFunction ); }; @@ -645,9 +651,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const docMax = Math.min(NumCast(seldoc.rootDoc.width)/2, NumCast(seldoc.rootDoc.height)/2); const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); const radiusHandle = (borderRadius / docMax) * maxDist; - const radiusHandleLocation = Math.min(radiusHandle, maxDist) + const radiusHandleLocation = Math.min(radiusHandle, maxDist); const reachedMax:boolean = numbersAlmostEqual(radiusHandleLocation, maxDist); - console.log(reachedMax); + console.log("reachedMax: ", reachedMax); return (
{bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? null : ( - <> +
@@ -683,7 +689,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P {titleArea} {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')} {hideResizers ? null : ( - <> +
e.preventDefault()} />
e.preventDefault()} />
e.preventDefault()} /> @@ -700,17 +706,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P {'⟲'}
)} - {this._showRotationPath == true && ( -
- -
- )}
e.preventDefault()} /> - +
)} {hideDocumentButtonBar ? null : ( @@ -724,7 +725,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
)}
- +
)}
); -- cgit v1.2.3-70-g09d2 From b7e04efc2a17d443155c93ad3185d464272ede99 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Sat, 2 Jul 2022 22:43:42 -0700 Subject: made document decorations hidden when making document edits --- src/client/views/DocumentDecorations.tsx | 41 ++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8b83eaeb2..4f30bf846 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -579,19 +579,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P return null; } // hide the decorations if the parent chooses to hide it or if the document itself hides it - const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup; - const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle; - const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar; + const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup || this._isRounding || this._isRotating; + const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; + const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar || this._isRounding || + this._isRotating; // if multiple documents have been opened at the same time, then don't show open button const hideOpenButton = - seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton); + seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || this._isRounding || this._isRotating; const hideDeleteButton = + this._isRounding || + this._isRotating || seldoc.props.hideDeleteButton || seldoc.rootDoc.hideDeleteButton || SelectionManager.Views().some(docView => { const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; return docView.rootDoc.stayInCollection || (collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.rootDoc) !== AclAdmin); }); + const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => ( {title}
} placement="top">
{hideDeleteButton ?
: topBtn('close', this.hasIcons ? 'times' : 'window-maximize', undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), 'Close')} - {titleArea} + {hideTitle ? null : titleArea} {hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')} {hideResizers ? null : ( -
+ <>
e.preventDefault()} />
e.preventDefault()} />
e.preventDefault()} /> @@ -700,18 +706,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
e.preventDefault()} />
e.preventDefault()} /> - {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} - {useRotation && ( -
e.preventDefault()}> - {'⟲'} -
- )} -
+ )} + + {useRotation && ( +
e.preventDefault()}> + {'⟲'} +
+ )} + + {useRounding && ( +
e.preventDefault()} /> -
+ }} className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()} + /> )} {hideDocumentButtonBar ? null : ( -- cgit v1.2.3-70-g09d2 From b8c89d70a5ae5373e84d95647e302017011dfa53 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Mon, 4 Jul 2022 12:59:59 -0700 Subject: updated start location of border radius --- src/client/views/DocumentDecorations.scss | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index af4ceb0b5..b490278c3 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -107,6 +107,8 @@ $resizeHandler: 8px; .documentDecorations-borderRadius { position: absolute; border-radius: 100%; + left: 3px; + top: 23px; background: $medium-gray; height: 10; width: 10; -- cgit v1.2.3-70-g09d2 From 9f89d4d2bd166a189da0c6cce66ac4ebb5682ab5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 5 Jul 2022 11:22:02 -0400 Subject: fixed color picker so that dismissing it doesn't lose focus on active text box - allows font color to be changed --- src/client/views/nodes/button/FontIconBox.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 85efc67a5..798759c01 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -344,12 +344,13 @@ export class FontIconBox extends DocComponent() {
{this.label}
; - - const dropdownCaret =
- -
; + + // dropdown caret seems superfluous since clicking the color button does the same thing + // const dropdownCaret =
+ // + //
; setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return (
() { onClick={e => e.stopPropagation()}> {this.colorPicker(curColor)}
-
{ +
{ + e.preventDefault(); e.stopPropagation(); this.colorPickerClosed = true; })} />
} -- cgit v1.2.3-70-g09d2 From 0906eab4135db844dc45a20d33f84e7439461c9b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 5 Jul 2022 12:33:28 -0400 Subject: fixed sidebar annos to be editable (with i-beam cursor) when selected and to be active when parent document is active. fixed previewing a sidebar annotation to not open up sidebar --- src/client/views/DocumentDecorations.tsx | 1 + src/client/views/SidebarAnnos.tsx | 1 + src/client/views/collections/CollectionStackingView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 5 +++-- 5 files changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 17e135689..9dc02e3f4 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -341,6 +341,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P onPointerMove = (e: PointerEvent, down: number[], move: number[]): boolean => { const first = SelectionManager.Views()[0]; + if (!first) return false; let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY }; var fixedAspect = Doc.NativeAspect(first.layoutDoc); InkStrokeProperties.Instance._lock && diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 9e939aa19..8f73ac2c3 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -137,6 +137,7 @@ export class SidebarAnnos extends React.Component { select={emptyFunction} scaling={returnOne} childShowTitle={this.showTitle} + childDocumentsActive={this.props.isContentActive} whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} childHideDecorationTitle={returnTrue} removeDocument={this.removeDocument} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4e8c14039..44abbdb1c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -229,7 +229,7 @@ export class CollectionStackingView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)); + isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined; getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2ea976813..360a9b242 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -469,7 +469,7 @@ export class DocumentViewInternal extends DocComponent this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here + const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 8e1698eba..f83fdffc9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1607,6 +1607,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp dataDoc={this.dataDoc} // usePanelWidth={true} nativeWidth={NumCast(this.layoutDoc._nativeWidth)} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} showSidebar={this.SidebarShown} PanelWidth={this.sidebarWidth} setHeight={this.setSidebarHeight} @@ -1645,8 +1646,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } render() { TraceMobx(); - const selected = this.props.isSelected() || this.Document.forceActive; - const active = this.props.isContentActive(); + const active = this.props.isContentActive() || this.props.isSelected(); + const selected = active; const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); -- cgit v1.2.3-70-g09d2 From 85e290ee7a666412570f2bae43a9b62d35b425f2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 6 Jul 2022 17:26:26 -0400 Subject: fixed pushpin behavior to work with notes in annotation sidebar. fixed right-clicking on text anchors to bring up anchor menu --- src/client/util/DocumentManager.ts | 2 +- src/client/views/LightboxView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/PDFBox.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 90 +++++++++++++--------- 6 files changed, 66 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0435cd535..8473ce703 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -255,7 +255,7 @@ export class DocumentManager { retryDocView.focus(targetDoc, { willZoom, afterFocus: (didFocus: boolean) => new Promise(res => { - !noSelect && focusAndFinish(didFocus); + !noSelect && focusAndFinish(true); res(ViewAdjustment.doNothing); }) }); // focus on the target in the context diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index fcc4aea13..a1e71b5f4 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -233,7 +233,7 @@ export class LightboxView extends React.Component { const doc = LightboxView._doc; const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); - //else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button + //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button })); })} Document={LightboxView.LightboxDoc} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 44abbdb1c..37589974b 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -191,7 +191,7 @@ export class CollectionStackingView extends CollectionSubView options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing; + const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; this.props.focus(this.rootDoc, { willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 360a9b242..7569b209d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -470,10 +470,13 @@ export class DocumentViewInternal extends DocComponent this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here - const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; + const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => (options?.afterFocus?.(true) ?? ViewAdjustment.doNothing); this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0)) + new Promise(res => setTimeout(async () => res(endFocus ? + await endFocus(didFocus||focusSpeed !== undefined) : + ViewAdjustment.doNothing), focusSpeed ?? 0) + ) }); } @@ -696,6 +699,7 @@ export class DocumentViewInternal extends DocComponent { + if (e?.nativeEvent.cancelBubble) return; if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 4eb07fd8d..1fb3cef2a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -179,12 +179,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent { + let didToggle = false; if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) { this.toggleSidebar(!smooth); + didToggle = true; } if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; this._initialScrollTarget = doc; - return this._pdfViewer?.scrollFocus(doc, smooth); + return this._pdfViewer?.scrollFocus(doc, smooth) ?? (didToggle ? 1 : undefined); } getAnchor = () => { const anchor = diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f83fdffc9..27227c152 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -18,7 +18,7 @@ import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { ComputedField } from '../../../../fields/ScriptField'; -import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; @@ -617,9 +617,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return false; } + + @undoBatch + deleteAnnotation = (anchor:Doc) => { + LinkManager.Instance.deleteLink(DocListCast(anchor.links)[0]); + // const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]); + // this.props.dataDoc[this.props.fieldKey] = new List(docAnnotations.filter(a => a !== this.annoTextRegion)); + // AnchorMenu.Instance.fadeOut(true); + this.props.select(false); + } + + @undoBatch + pinToPres = (anchor:Doc) => this.props.pinToPres(anchor) + + @undoBatch + makePushpin = (anchor:Doc) => anchor.isPushpin = !anchor.isPushpin + + isPushpin = (anchor:Doc) => BoolCast(anchor.isPushpin); + specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; + const editor = this._editorView!; + const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); + let target = (e.target as any).parentElement; // hrefs are stored on the database of the node that wraps the hyerlink + while (target && !target.dataset?.targethrefs) target = target.parentElement; + if (target) { + const hrefs = (target.dataset?.targethrefs as string)?.trim().split(" ").filter(h => h); + const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), "").split("?")[0]; + e.persist(); + anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => { + AnchorMenu.Instance.Status = "annotation"; + AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc); + AnchorMenu.Instance.Pinned = false; + AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc); + AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc); + AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc); + AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); + })); + e.stopPropagation(); + return; + } + const changeItems: ContextMenuProps[] = []; changeItems.push({ description: "plain", event: undoBatch(() => { @@ -797,8 +836,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } scrollFocus = (textAnchor: Doc, smooth: boolean) => { + let didToggle = false; if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) { this.toggleSidebar(!smooth); + didToggle = true; } const textAnchorId = textAnchor[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { @@ -851,7 +892,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed + return this._didScroll ? this._focusSpeed : didToggle ? 1: undefined; // if we actually scrolled, then return some focusSpeed } getScrollHeight = () => this.scrollHeight; @@ -1251,6 +1292,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onPointerDown = (e: React.PointerEvent): void => { + if (((e.nativeEvent as any).handledByInnerReactInstance)) { + return e.stopPropagation(); + } else (e.nativeEvent as any).handledByInnerReactInstance = true; + if (this.Document.forceActive) e.stopPropagation(); this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view. if ((e.target as any).tagName === "AUDIOTAG") { @@ -1304,10 +1349,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; - if ((e.nativeEvent as any).formattedHandled) { - console.log("handled"); - } - if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) { + if (this.props.isContentActive(true)) { const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); @@ -1315,9 +1357,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); } - (e.nativeEvent as any).formattedHandled = true; - if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { + if (e.button === 0 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } } @@ -1334,8 +1375,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } FormattedTextBoxComment.Hide(); - (e.nativeEvent as any).formattedHandled = true; - if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } @@ -1354,6 +1393,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @observable public static Focused: FormattedTextBox | undefined; onClick = (e: React.MouseEvent): void => { + if ((e.nativeEvent as any).handledByInnerReactInstance) { + e.stopPropagation(); + return; + } if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { this._forceDownNode = undefined; return; @@ -1378,12 +1421,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos))); } } - if ((e.nativeEvent as any).formattedHandled) { - e.stopPropagation(); - return; - } if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events - (e.nativeEvent as any).formattedHandled = true; + (e.nativeEvent as any).handledByInnerReactInstance = true; if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it. // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment) this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey); @@ -1424,26 +1463,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - onMouseUp = (e: React.MouseEvent): void => { - e.stopPropagation(); - - const view = this._editorView as any; - // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there - // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. - if (view.mouseDown) { - const originalUpHandler = view.mouseDown.up; - view.root.removeEventListener("mouseup", originalUpHandler); - view.mouseDown.up = (e: MouseEvent) => { - if (!(e as any).formattedHandled) { - originalUpHandler(e); - (e as any).formattedHandled = true; - } else { - console.log("prehandled"); - } - }; - view.root.addEventListener("mouseup", view.mouseDown.up); - } - } startUndoTypingBatch() { !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); } @@ -1688,7 +1707,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} - onMouseUp={this.onMouseUp} onDoubleClick={this.onDoubleClick} >
Date: Wed, 6 Jul 2022 15:57:21 -0700 Subject: edting -> editing --- src/client/views/DocumentDecorations.tsx | 37 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 336ec9c83..9544c588b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -50,7 +50,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @observable private _accumulatedTitle = ''; @observable private _titleControlString: string = '#title'; - @observable private _edtingTitle = false; + @observable private _editingTitle = false; @observable private _hidden = false; @observable public AddToSelection = false; // if Shift is pressed, then this should be set so that clicking on the selection background is ignored so overlapped documents can be added to the selection set. @observable public Interacting = false; @@ -66,7 +66,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P DocumentDecorations.Instance = this; reaction( () => SelectionManager.Views().slice(), - action(docs => (this._edtingTitle = false)) + action(docs => (this._editingTitle = false)) ); } @@ -93,7 +93,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P @action titleBlur = () => { - this._edtingTitle = false; + this._editingTitle = false; if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) { this._titleControlString = this._accumulatedTitle; } else if (this._titleControlString.startsWith('#')) { @@ -147,8 +147,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P e => this.onBackgroundMove(true, e), e => {}, action(e => { - !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString); - this._edtingTitle = true; + !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('#') ? this.selectionTitle : this._titleControlString); + this._editingTitle = true; this._keyinput.current && setTimeout(this._keyinput.current.focus); }) ); @@ -320,12 +320,29 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (selectedInk.length) { angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint); } else { - SelectionManager.Views().forEach(dv => (dv.rootDoc._jitterRotation = NumCast(dv.rootDoc._jitterRotation) - (angle * 180) / Math.PI)); + SelectionManager.Views().forEach(dv => { + const oldRotation = NumCast(dv.rootDoc._jitterRotation); + // Rotation between -360 and 360 + let newRotation = (oldRotation - (angle * 180) / Math.PI) % 360; + + const diff = Math.round(newRotation / 45) - newRotation / 45 + if (diff < .05) { + console.log('show lines'); + } + dv.rootDoc._jitterRotation = newRotation; + }); } return false; }, // moveEvent action(() => { - console.log('up') + SelectionManager.Views().forEach(dv => { + const oldRotation = NumCast(dv.rootDoc._jitterRotation); + const diff = Math.round(oldRotation / 45) - oldRotation / 45 + if (diff < .05) { + let newRotation = Math.round(oldRotation / 45) * 45; + dv.rootDoc._jitterRotation = newRotation; + } + }) this._isRotating = false; rotateUndo?.end(); UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); @@ -619,7 +636,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); - const titleArea = hideTitle ? null : this._edtingTitle ? ( + const titleArea = hideTitle ? null : this._editingTitle ? (
e.preventDefault()} -- cgit v1.2.3-70-g09d2 From dd16695b0c5fe8c54bc276a230381ae36e19e5ac Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 7 Jul 2022 13:02:33 -0400 Subject: trying to fix errors in compiles --- package-lock.json | 2282 +++++++++++++++++-- package.json | 40 +- src/client/util/CaptureManager.tsx | 157 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/GroupManager.tsx | 253 ++- src/client/util/ScrollBox.tsx | 23 +- src/client/util/SharingManager.tsx | 566 +++-- src/client/views/Main.tsx | 44 +- src/client/views/MainView.tsx | 1086 ++++++---- src/client/views/Touchable.tsx | 68 +- src/client/views/animationtimeline/Timeline.tsx | 286 ++- .../views/collections/CollectionStackingView.tsx | 657 +++--- .../views/collections/CollectionTreeView.tsx | 321 +-- src/client/views/collections/CollectionView.tsx | 266 ++- .../CollectionFreeFormLinksView.tsx | 34 +- .../views/collections/collectionGrid/Grid.tsx | 17 +- .../collectionLinear/CollectionLinearView.tsx | 291 +-- src/client/views/linking/LinkEditor.tsx | 346 +-- src/client/views/linking/LinkMenuItem.tsx | 226 +- src/client/views/nodes/DocumentContentsView.tsx | 259 ++- src/client/views/nodes/LinkDocPreview.tsx | 192 +- src/client/views/nodes/MapBox/MapBox.tsx | 505 ++--- src/client/views/nodes/PDFBox.tsx | 451 ++-- src/client/views/nodes/formattedText/schema_rts.ts | 13 +- src/client/views/nodes/trails/PresBox.tsx | 2285 +++++++++++++------- src/client/views/search/SearchBox.tsx | 213 +- src/mobile/MobileInterface.tsx | 602 ++++-- src/server/authentication/AuthenticationManager.ts | 306 +-- 28 files changed, 7658 insertions(+), 4133 deletions(-) (limited to 'src') diff --git a/package-lock.json b/package-lock.json index e895161b5..7e413d5c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -462,6 +462,76 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, "@ffmpeg/core": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.10.0.tgz", @@ -547,9 +617,9 @@ } }, "@fortawesome/react-fontawesome": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz", - "integrity": "sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", + "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", "requires": { "prop-types": "^15.8.1" } @@ -580,6 +650,19 @@ "emotion": "^10.0.0", "prop-types": "^15.7.1", "react-transition-group": "^2.3.1" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } } }, "@hig/theme-context": { @@ -608,6 +691,40 @@ "lodash.memoize": "^4.1.2" } }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@hypnosphi/create-react-context": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", @@ -1184,9 +1301,9 @@ } }, "@types/express-session": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", - "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.5.tgz", + "integrity": "sha512-l0DhkvNVfyUPEEis8fcwbd46VptfA/jmMwHfob2TfDMf3HyPLiB9mKD71LXhz5TMUobODXPD27zXSwtFQLHm+w==", "dev": true, "requires": { "@types/express": "*" @@ -1332,6 +1449,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/keygrip": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", @@ -1461,9 +1584,9 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" }, "@types/passport": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.8.tgz", - "integrity": "sha512-Gdcvis7+7G/Mobm+25AeFi+oe5teBhHzpbCOFWeN10Bj8tnoEE1L5lkraQjzmDEKkJQuM7xSJUGIFGl/giyRfQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.9.tgz", + "integrity": "sha512-9+ilzUhmZQR4JP49GdC2O4UdDE3POPLwpmaTC/iLkW7l0TZCXOo1zsTnnlXPq6rP1UsUZPfbAV4IUdiwiXyC7g==", "dev": true, "requires": { "@types/express": "*" @@ -1663,10 +1786,9 @@ } }, "@types/react": { - "version": "16.14.26", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.26.tgz", - "integrity": "sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ==", - "dev": true, + "version": "18.0.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz", + "integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1676,8 +1798,7 @@ "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", - "dev": true + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" } } }, @@ -1720,18 +1841,18 @@ } }, "@types/react-dom": { - "version": "16.9.16", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.16.tgz", - "integrity": "sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "dev": true, "requires": { - "@types/react": "^16" + "@types/react": "*" } }, "@types/react-grid-layout": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-0.17.2.tgz", - "integrity": "sha512-iLKQ5rI9KyPGs/BlWmeBfoikRnoOmTuddK8V5mOCgJ46e2JaecZuPhuIX1kV0mkQSk6X3XoEZALVW0XQbz9g5Q==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz", + "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==", "dev": true, "requires": { "@types/react": "*" @@ -1741,6 +1862,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz", "integrity": "sha512-Vefs6LkLqF61vfV7AiAqls+vpR94q67gunhMueDznG+msAkrYgRxl7gYjNem/kZ+as2l2mNChmF1jRZzzQQtMg==", + "dev": true, "requires": { "react-icons": "*" } @@ -1758,6 +1880,7 @@ "version": "0.26.7", "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "dev": true, "requires": { "@types/react": "*" }, @@ -1766,6 +1889,7 @@ "version": "18.0.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", + "dev": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1775,7 +1899,8 @@ "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true } } }, @@ -1844,28 +1969,11 @@ } }, "@types/react-transition-group": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", - "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", "requires": { "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "18.0.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", - "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "csstype": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" - } } }, "@types/reactcss": { @@ -2359,22 +2467,22 @@ } }, "@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==" }, "@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==" }, "@webscopeio/react-textarea-autocomplete": { "version": "4.9.1", @@ -2487,7 +2595,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" }, "agent-base": { "version": "6.0.2", @@ -2617,6 +2725,23 @@ } } }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, "ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -2735,6 +2860,16 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -2852,6 +2987,19 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -2872,6 +3020,30 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, "array.prototype.reduce": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", @@ -2922,6 +3094,18 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -3006,6 +3190,12 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axe-core": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.2.tgz", + "integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==", + "dev": true + }, "axios": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", @@ -3014,6 +3204,12 @@ "follow-redirects": "1.5.10" } }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, "babel": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel/-/babel-6.23.0.tgz", @@ -3078,6 +3274,28 @@ } } }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "babel-plugin-emotion": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", @@ -3169,7 +3387,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" }, "balanced-match": { "version": "1.0.2", @@ -3234,7 +3452,7 @@ "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" }, "base64-js": { "version": "1.5.1", @@ -3752,9 +3970,9 @@ "integrity": "sha512-u+Ll+RDaiQEproTQjZLjZwyfNgNezA1fERMT7/54npcz+PkbVJUAHXMUz4bkXQYRPWrcFNO0Fbi1mwjfXg6N5g==" }, "canvas": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.1.tgz", - "integrity": "sha512-vSQti1uG/2gjv3x6QLOZw7TctfufaerTWbVe+NSduHxxLGB+qf3kFgQ6n66DSnuoINtVUjrLLIK2R+lxrBG07A==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.3.tgz", + "integrity": "sha512-WOUM7ghii5TV2rbhaZkh1youv/vW1/Canev6Yx6BG2W+1S07w8jKZqKkPnbiPpQEDsnJdN8ouDd7OvQEGXDcUw==", "requires": { "@mapbox/node-pre-gyp": "^1.0.0", "nan": "^2.15.0", @@ -3822,6 +4040,12 @@ "is-regex": "^1.0.3" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -3932,6 +4156,21 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, "clipboard": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", @@ -4140,7 +4379,7 @@ "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" }, "component-emitter": { "version": "1.3.0", @@ -4150,7 +4389,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" }, "compress-brotli": { "version": "1.3.8", @@ -4295,6 +4534,12 @@ } } }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", @@ -4860,16 +5105,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -4995,6 +5230,12 @@ "d3-transition": "2" } }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5584,6 +5825,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", @@ -5809,9 +6059,9 @@ } }, "engine.io": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", - "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz", + "integrity": "sha512-Kc8fo5bbg8F4a2f3HPHTEpGyq/IRIQpyeHu3H1ThR14XDD7VrLcsGBo16HUpahgp8YkHJDaU5gNxJZbuGcuueg==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", @@ -5865,7 +6115,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "ws": { "version": "7.4.6", @@ -5985,6 +6235,15 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -5995,28 +6254,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.61", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", - "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6028,7 +6265,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -6069,36 +6305,1192 @@ } } }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "eslint": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", + "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, "requires": { - "estraverse": "^5.2.0" + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true - } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-config-esnext": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-esnext/-/eslint-config-esnext-4.1.0.tgz", + "integrity": "sha512-GhfVEXdqYKEIIj7j+Fw2SQdL9qyZMekgXfq6PyXM66cQw0B435ddjz3P3kxOBVihMRJ0xGYjosaveQz5Y6z0uA==", + "dev": true, + "requires": { + "babel-eslint": "^10.0.1", + "eslint": "^6.8.0", + "eslint-plugin-babel": "^5.2.1", + "eslint-plugin-import": "^2.14.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "eslint-config-node": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-node/-/eslint-config-node-4.1.0.tgz", + "integrity": "sha512-Wz17xV5O2WFG8fGdMYEBdbiL6TL7YNJSJvSX9V4sXQownewfYmoqlly7wxqLkOUv/57pq6LnnotMiQQrrPjCqQ==", + "dev": true, + "requires": { + "eslint": "^6.8.0", + "eslint-config-esnext": "^4.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + } + } + }, + "eslint-plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", + "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.0.tgz", + "integrity": "sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.2", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.1", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "dependencies": { + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true + }, + "eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, "estraverse": { @@ -6451,6 +7843,28 @@ } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -6554,6 +7968,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6614,6 +8034,24 @@ "when": ">= 0.0.1" } }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", @@ -6818,6 +8256,22 @@ "is-buffer": "~2.0.3" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, "flexlayout-react": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/flexlayout-react/-/flexlayout-react-0.3.11.tgz", @@ -7144,6 +8598,12 @@ "functions-have-names": "^1.2.2" } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -7645,14 +9105,14 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" }, "has-flag": { "version": "3.0.0", @@ -7963,11 +9423,6 @@ } } }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, "hyntax": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/hyntax/-/hyntax-1.1.9.tgz", @@ -8153,7 +9608,7 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" }, "inflight": { "version": "1.0.6", @@ -8198,6 +9653,116 @@ "css-in-js-utils": "^2.0.0" } }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "inspect-function": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz", @@ -9055,6 +10620,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -9195,6 +10766,30 @@ "promise": "^7.0.1" } }, + "jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, "jszip": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", @@ -9292,6 +10887,21 @@ "graceful-fs": "^4.1.9" } }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -9460,7 +11070,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "lodash.isplainobject": { "version": "4.0.6", @@ -9711,7 +11321,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", + "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", "requires": { "jquery": "^1.12.3" }, @@ -9719,7 +11329,7 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" + "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" } } }, @@ -9817,7 +11427,8 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "methods": { "version": "1.1.2", @@ -9871,7 +11482,8 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, "mimic-response": { "version": "2.1.0", @@ -10301,6 +11913,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "nan": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", @@ -10339,6 +11957,12 @@ "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz", "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck=" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14468,6 +16092,28 @@ "object-keys": "^1.0.11" } }, + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "object.getownpropertydescriptors": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", @@ -14479,6 +16125,16 @@ "es-abstract": "^1.20.1" } }, + "object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -14487,6 +16143,17 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -14518,6 +16185,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -14563,9 +16231,9 @@ } }, "orderedmap": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-1.1.8.tgz", - "integrity": "sha512-eWEYOAggZZpZbJ9CTsqAKOTxlbBHdHZ8pzcfEvNTxGrjQ/m+Q25nSWUiMlT9MTbgpB6FOiBDKqsgJ2FlLDVNaw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.0.0.tgz", + "integrity": "sha512-buf4PoAMlh45b8a8gsGy/X6w279TSqkyAS0C0wdTSJwFSU+ljQFJON5I8NfjLHoCXwpSROIo2wr0g33T+kQshQ==" }, "os-homedir": { "version": "1.0.2", @@ -15270,6 +16938,21 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -15402,11 +17085,11 @@ } }, "prosemirror-model": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.18.0.tgz", - "integrity": "sha512-EZLl0mvVIixEL1V538GsPP4Iw+D83gBRdLcjL7VouzoPPWlhhv/0Y0V9ttT5lUhF7jieIrNHOa3XNFs1FwgrOw==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.18.1.tgz", + "integrity": "sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==", "requires": { - "orderedmap": "^1.1.0" + "orderedmap": "^2.0.0" } }, "prosemirror-schema-list": { @@ -15420,9 +17103,9 @@ } }, "prosemirror-state": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.0.tgz", - "integrity": "sha512-mVDZdjNX/YT5FvypiwbphJe9psA5h+j9apsSszVRFc6oKFoIInvzdujh8QW9f9lwHtSYajLxNiM1hPhd0Sl1XA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.1.tgz", + "integrity": "sha512-U/LBDW2gNmVa07sz/D229XigSdDQ5CLFwVB1Vb32MJbAHHhWe/6pOc721faI17tqw4pZ49i1xfY/jEZ9tbIhPg==", "requires": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0" @@ -15437,9 +17120,9 @@ } }, "prosemirror-view": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.26.0.tgz", - "integrity": "sha512-tT8ICUAw6YfOTRIwVQJoCdF65BYxoWFjAqmv44Kn4phYjdLJ8Vc92dhL3LnmwAQuZ9yfPHzRR6Lfb3/5EVJHWg==", + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.26.5.tgz", + "integrity": "sha512-SO+AX6WwdbJZHVvuloXI0qfO+YJAnZAat8qrYwfiqTQwL/FewLUnr0m3EXZ6a60hQs8/Q/lzeJXiFR/dOPaaKQ==", "requires": { "prosemirror-model": "^1.16.0", "prosemirror-state": "^1.0.0", @@ -15981,15 +17664,26 @@ } }, "react-grid-layout": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz", - "integrity": "sha512-lHkrk941Tk5nTwZPa9uj6ttHBT0VehSHwEhWbINBJKvM1GRaFNOefvjcuxSyuCI5JWjVUP+Qm3ARt2470AlxMA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", + "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", "requires": { - "classnames": "2.x", + "clsx": "^1.1.1", "lodash.isequal": "^4.0.0", - "prop-types": "^15.0.0", + "prop-types": "^15.8.1", "react-draggable": "^4.0.0", - "react-resizable": "^1.9.0" + "react-resizable": "^3.0.4" + }, + "dependencies": { + "react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==", + "requires": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + } + } } }, "react-icons": { @@ -16189,9 +17883,9 @@ } }, "react-refresh-typescript": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.5.tgz", - "integrity": "sha512-TlcxF9VMBBS8dHvMD+3dklgArXeIhjbtT7R09ycsXvhPPYmv70J67kiGT8mxe6/tYzkql9aCHaZAY6QYdutLdw==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.7.tgz", + "integrity": "sha512-KbuW57FauO11e6a9gU836sCm3M3z0b2+J2qPhUudg0QplOfz0eAF3gMeshcUC/ChfNLJCK1SZxvYmUtRxiZE5A==" }, "react-resizable": { "version": "1.11.1", @@ -16298,14 +17992,30 @@ "integrity": "sha512-EblIqTAsIpkYeM8bZtC4lcpTE0A2zCEGipFB52RgcQq/q+0oryrk7Sxt+sqhIjUu6xMNEVywV8dr74lz5yWO6A==" }, "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", "requires": { - "dom-helpers": "^3.4.0", + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "prop-types": "^15.6.2" + }, + "dependencies": { + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + } } }, "react-use-measure": { @@ -16492,6 +18202,12 @@ "functions-have-names": "^1.2.2" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "registry-auth-token": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", @@ -16811,6 +18527,16 @@ } } }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -16856,6 +18582,12 @@ "sdp": "^2.6.0" } }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -16873,6 +18605,23 @@ } } }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -17446,6 +19195,17 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", @@ -17567,15 +19327,15 @@ } }, "socket.io": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz", - "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz", + "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==", "requires": { "debug": "~4.1.0", - "engine.io": "~3.5.0", + "engine.io": "~3.6.0", "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.4.0", + "socket.io-client": "2.5.0", "socket.io-parser": "~3.4.0" }, "dependencies": { @@ -17595,9 +19355,9 @@ "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" }, "socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz", + "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==", "requires": { "backo2": "1.0.2", "component-bind": "1.0.0", @@ -17623,12 +19383,12 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "socket.io-parser": { "version": "3.3.2", @@ -18052,6 +19812,22 @@ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" }, + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -18128,11 +19904,6 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", @@ -18240,6 +20011,46 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "table-layout": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", @@ -18373,6 +20184,12 @@ } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "textarea-caret": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", @@ -18451,10 +20268,19 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" }, "to-fast-properties": { "version": "2.0.0", @@ -18835,6 +20661,26 @@ } } }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -18946,12 +20792,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -18966,6 +20806,12 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -18986,9 +20832,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", - "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, "typescript-collections": { @@ -19415,6 +21261,12 @@ "resolved": "https://registry.npmjs.org/uuid-js/-/uuid-js-0.7.5.tgz", "integrity": "sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA=" }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", @@ -19637,17 +21489,17 @@ } }, "webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", "colorette": "^2.0.14", "commander": "^7.0.0", - "execa": "^5.0.0", + "cross-spawn": "^7.0.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", @@ -19670,40 +21522,11 @@ "which": "^2.0.1" } }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==" }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -20281,6 +22104,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", @@ -20445,7 +22277,7 @@ "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" }, "yn": { "version": "2.0.0", diff --git a/package.json b/package.json index e4ec2798d..93968dce4 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@types/exif": "^0.6.3", "@types/express": "^4.17.13", "@types/express-flash": "0.0.0", - "@types/express-session": "^1.17.4", + "@types/express-session": "^1.17.5", "@types/express-validator": "^3.0.0", "@types/file-saver": "^2.0.5", "@types/google-maps-react": "^2.0.5", @@ -49,7 +49,7 @@ "@types/mongoose": "^5.11.97", "@types/node": "^10.17.60", "@types/nodemailer": "^4.6.6", - "@types/passport": "^1.0.7", + "@types/passport": "^1.0.9", "@types/passport-google-oauth20": "^2.0.11", "@types/passport-local": "^1.0.34", "@types/pdfjs-dist": "^2.10.378", @@ -65,12 +65,15 @@ "@types/prosemirror-transform": "^1.1.5", "@types/prosemirror-view": "^1.23.1", "@types/rc-switch": "^1.9.2", - "@types/react": "^16.14.23", + "@types/react": "^18.0.15", + "@types/react-icons": "^3.0.0", + "@types/react-reconciler": "^0.26.4", + "@types/react-transition-group": "^4.4.5", "@types/react-autosuggest": "^9.3.14", "@types/react-color": "^2.17.6", "@types/react-datepicker": "^3.1.8", - "@types/react-dom": "^16.9.14", - "@types/react-grid-layout": "^0.17.2", + "@types/react-dom": "^18.0.6", + "@types/react-grid-layout": "^1.3.2", "@types/react-measure": "^2.0.8", "@types/react-select": "^3.1.2", "@types/react-table": "^6.8.9", @@ -117,7 +120,7 @@ "ts-node-dev": "^1.1.8", "tslint": "^5.20.1", "tslint-loader": "^3.6.0", - "typescript": "^4.6.2", + "typescript": "^4.7.4", "webpack": "^5.69.1", "webpack-dev-server": "^3.11.3", "webpack-hot-middleware": "^2.25.1" @@ -129,7 +132,7 @@ "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "@fortawesome/react-fontawesome": "^0.1.17", + "@fortawesome/react-fontawesome": "^0.1.19", "@hig/flyout": "^1.3.1", "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.23.1", @@ -145,14 +148,13 @@ "@types/dom-speech-recognition": "0.0.1", "@types/formidable": "1.0.31", "@types/google-maps": "^3.2.3", - "@types/react-icons": "^3.0.0", - "@types/react-reconciler": "^0.26.4", "@types/reveal": "^3.3.33", "@types/supercluster": "^7.1.0", "@types/three": "^0.126.2", "@types/web": "0.0.53", "@types/webscopeio__react-textarea-autocomplete": "^4.7.2", "@webscopeio/react-textarea-autocomplete": "^4.9.1", + "D": "^1.0.0", "adm-zip": "^0.4.16", "archiver": "^3.1.1", "array-batcher": "^1.2.3", @@ -167,7 +169,7 @@ "bootstrap": "^4.6.1", "browser-assert": "^1.2.1", "bson": "^4.6.1", - "canvas": "^2.9.0", + "canvas": "^2.9.3", "child_process": "^1.0.2", "chrome": "^0.1.0", "class-transformer": "^0.2.0", @@ -178,7 +180,6 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.0.0", "cors": "^2.8.5", - "D": "^1.0.0", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", "exif": "^0.6.0", @@ -250,11 +251,11 @@ "prosemirror-history": "^1.2.0", "prosemirror-inputrules": "^1.1.3", "prosemirror-keymap": "^1.1.5", - "prosemirror-model": "^1.16.1", + "prosemirror-model": "^1.18.1", "prosemirror-schema-list": "^1.1.6", - "prosemirror-state": "^1.3.4", + "prosemirror-state": "^1.4.1", "prosemirror-transform": "^1.3.4", - "prosemirror-view": "^1.23.7", + "prosemirror-view": "^1.26.5", "pug": "^2.0.4", "puppeteer": "^3.3.0", "query-string": "^6.14.1", @@ -268,18 +269,19 @@ "react-compound-slider": "^2.5.0", "react-datepicker": "^3.8.0", "react-dom": "^16.14.0", - "react-grid-layout": "^0.18.3", + "react-grid-layout": "^1.3.4", "react-icons": "^4.3.1", "react-image-lightbox-with-rotate": "^5.1.1", "react-jsx-parser": "^1.29.0", "react-loading": "^2.0.3", "react-measure": "^2.5.2", - "react-refresh-typescript": "^2.0.3", + "react-refresh-typescript": "^2.0.7", "react-resizable": "^1.11.1", "react-resizable-rotatable-draggable": "^0.2.0", "react-reveal": "^1.2.2", "react-select": "^3.2.0", "react-table": "^6.11.5", + "react-transition-group": "^4.4.2", "readline": "^1.3.0", "request": "^2.88.2", "request-promise": "^4.2.6", @@ -288,8 +290,8 @@ "serializr": "^1.5.4", "sharp": "^0.23.4", "shelljs": "^0.8.5", - "socket.io": "^2.4.1", - "socket.io-client": "^2.4.0", + "socket.io": "^2.5.0", + "socket.io-client": "^2.5.0", "solr-node": "^1.2.1", "standard-http-error": "^2.0.1", "stream-browserify": "^3.0.0", @@ -306,7 +308,7 @@ "uuid": "^3.4.0", "valid-url": "^1.0.9", "web-request": "^1.0.7", - "webpack-cli": "^4.9.2", + "webpack-cli": "^4.10.0", "webpack-dev-middleware": "^5.3.1", "webrtc-adapter": "^7.7.1", "wikijs": "^6.3.3", diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index c247afa26..0b5957fac 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -1,17 +1,17 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { convertToObject } from "typescript"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { BoolCast, StrCast, Cast } from "../../fields/Types"; -import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils"; -import { LightboxView } from "../views/LightboxView"; -import { MainViewModal } from "../views/MainViewModal"; -import "./CaptureManager.scss"; -import { SelectionManager } from "./SelectionManager"; -import { undoBatch } from "./UndoManager"; -const higflyout = require("@hig/flyout"); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { convertToObject } from 'typescript'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { BoolCast, StrCast, Cast } from '../../fields/Types'; +import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils'; +import { LightboxView } from '../views/LightboxView'; +import { MainViewModal } from '../views/MainViewModal'; +import './CaptureManager.scss'; +import { SelectionManager } from './SelectionManager'; +import { undoBatch } from './UndoManager'; +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -22,32 +22,31 @@ export class CaptureManager extends React.Component<{}> { @observable _document: any; @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not. - constructor(props: {}) { super(props); CaptureManager.Instance = this; } - public close = action(() => this.isOpen = false); + public close = action(() => (this.isOpen = false)); public open = action((doc: Doc) => { this.isOpen = true; this._document = doc; }); - @computed get visibilityContent() { - - return
-
Visibility
-
-
- Private -
-
- Public + return ( +
+
Visibility
+
+
+ Private +
+
+ Public +
-
; + ); } @computed get linksContent() { @@ -66,75 +65,79 @@ export class CaptureManager extends React.Component<{}> { order.push(
{i}
- {(l.anchor1 as Doc).title} + {StrCast((l.anchor1 as Doc).title)}
); } }); } - return
-
Links
-
- {order} + return ( +
+
Links
+
{order}
-
; + ); } @computed get closeButtons() { - return
-
-
{ - LightboxView.SetLightboxDoc(this._document); - this.close(); - }}> - Save -
-
{ - const selected = SelectionManager.Views().slice(); - SelectionManager.DeselectAll(); - selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); - this.close(); - }}> - Cancel + return ( +
+
+
{ + LightboxView.SetLightboxDoc(this._document); + this.close(); + }}> + Save +
+
{ + const selected = SelectionManager.Views().slice(); + SelectionManager.DeselectAll(); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + this.close(); + }}> + Cancel +
-
; + ); } - - private get captureInterface() { - return
-
-
-
+ return ( +
+
+
+
+ Conversation Capture
- Conversation Capture -
-
- -
- {this.visibilityContent} - {this.linksContent} -
- +
+ {this.visibilityContent} + {this.linksContent} +
+ +
+ {this.closeButtons}
- {this.closeButtons} -
; - + ); } render() { - return ; + return ( + + ); } -} \ No newline at end of file +} diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b2a5fddcd..84efcb966 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -957,7 +957,7 @@ export class CurrentUserUtils { } new LinkManager(); - DocServer.UPDATE_SERVER_CACHE(); + setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500); return doc; } static setupFieldInfos(doc:Doc, field="fieldInfos") { diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 62d656c5d..59334f6a2 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,19 +1,19 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import Select from 'react-select'; -import * as RequestPromise from "request-promise"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; -import { StrCast, Cast } from "../../fields/Types"; -import { Utils } from "../../Utils"; -import { MainViewModal } from "../views/MainViewModal"; -import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import "./GroupManager.scss"; -import { GroupMemberView } from "./GroupMemberView"; -import { SharingManager, User } from "./SharingManager"; -import { listSpec } from "../../fields/Schema"; -import { DateField } from "../../fields/DateField"; +import * as RequestPromise from 'request-promise'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; +import { StrCast, Cast } from '../../fields/Types'; +import { Utils } from '../../Utils'; +import { MainViewModal } from '../views/MainViewModal'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import './GroupManager.scss'; +import { GroupMemberView } from './GroupMemberView'; +import { SharingManager, User } from './SharingManager'; +import { listSpec } from '../../fields/Schema'; +import { DateField } from '../../fields/DateField'; /** * Interface for options for the react-select component @@ -25,7 +25,6 @@ export interface UserOptions { @observer export class GroupManager extends React.Component<{}> { - static Instance: GroupManager; @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not. @observable private users: string[] = []; // list of users populated from the database. @@ -34,24 +33,26 @@ export class GroupManager extends React.Component<{}> { @observable private createGroupModalOpen: boolean = false; private inputRef: React.RefObject = React.createRef(); // the ref for the input box. private createGroupButtonRef: React.RefObject = React.createRef(); // the ref for the group creation button - @observable private buttonColour: "#979797" | "black" = "#979797"; - @observable private groupSort: "ascending" | "descending" | "none" = "none"; + @observable private buttonColour: '#979797' | 'black' = '#979797'; + @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; constructor(props: Readonly<{}>) { super(props); GroupManager.Instance = this; } - componentDidMount() { this.populateUsers(); } + componentDidMount() { + this.populateUsers(); + } /** * Fetches the list of users stored on the database. */ populateUsers = async () => { - const userList = await RequestPromise.get(Utils.prepend("/getUsers")); + const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email))); - } + }; /** * @returns the options to be rendered in the dropdown menu to add users and create a group. @@ -68,11 +69,11 @@ export class GroupManager extends React.Component<{}> { // SelectionManager.DeselectAll(); this.isOpen = true; this.populateUsers(); - } + }; /** * Hides the GroupManager. - */ + */ @action close = () => { this.isOpen = false; @@ -81,26 +82,32 @@ export class GroupManager extends React.Component<{}> { // this.users = []; this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; - } + }; /** * @returns the database of groups. */ - @computed get GroupManagerDoc(): Doc | undefined { return Doc.UserDoc().globalGroupDatabase as Doc; } + @computed get GroupManagerDoc(): Doc | undefined { + return Doc.UserDoc().globalGroupDatabase as Doc; + } /** * @returns a list of all group documents. */ - @computed get allGroups(): Doc[] { return DocListCast(this.GroupManagerDoc?.data); } + @computed get allGroups(): Doc[] { + return DocListCast(this.GroupManagerDoc?.data); + } /** * @returns the members of the admin group. */ - @computed get adminGroupMembers(): string[] { return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; } + @computed get adminGroupMembers(): string[] { + return this.getGroup('Admin') ? JSON.parse(StrCast(this.getGroup('Admin')!.members)) : ''; + } /** * @returns a group document based on the group name. - * @param groupName + * @param groupName */ getGroup(groupName: string): Doc | undefined { return this.allGroups.find(group => group.title === groupName); @@ -114,10 +121,9 @@ export class GroupManager extends React.Component<{}> { return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[]; } - /** * @returns a boolean indicating whether the current user has access to edit group documents. - * @param groupDoc + * @param groupDoc */ hasEditAccess(groupDoc: Doc): boolean { if (!groupDoc) return false; @@ -127,12 +133,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that sets up the group document. - * @param groupName - * @param memberEmails + * @param groupName + * @param memberEmails */ createGroupDoc(groupName: string, memberEmails: string[] = []) { - const name = groupName.toLowerCase() === "admin" ? "Admin" : groupName; - const groupDoc = new Doc("GROUP:" + name, true); + const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName; + const groupDoc = new Doc('GROUP:' + name, true); groupDoc.title = name; groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]); groupDoc.members = JSON.stringify(memberEmails); @@ -141,12 +147,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that adds a group document to the database of group documents and @returns whether it was successfully added or not. - * @param groupDoc + * @param groupDoc */ addGroup(groupDoc: Doc): boolean { if (this.GroupManagerDoc) { - Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc); - this.GroupManagerDoc["data-lastModified"] = new DateField; + Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc); + this.GroupManagerDoc['data-lastModified'] = new DateField(); return true; } return false; @@ -154,20 +160,20 @@ export class GroupManager extends React.Component<{}> { /** * Deletes a group from the database of group documents and @returns whether the group was deleted or not. - * @param group + * @param group */ @action deleteGroup(group: Doc): boolean { if (group) { if (this.GroupManagerDoc && this.hasEditAccess(group)) { - Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); + Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group); SharingManager.Instance.removeGroup(group); const members = JSON.parse(StrCast(group.members)); if (members.includes(Doc.CurrentUserEmail)) { const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group); index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1); } - this.GroupManagerDoc["data-lastModified"] = new DateField; + this.GroupManagerDoc['data-lastModified'] = new DateField(); if (group === this.currentGroup) { this.currentGroup = undefined; } @@ -179,8 +185,8 @@ export class GroupManager extends React.Component<{}> { /** * Adds a member to a group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ addMemberToGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -188,14 +194,14 @@ export class GroupManager extends React.Component<{}> { !memberList.includes(email) && memberList.push(email); groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.shareWithAddedMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } /** * Removes a member from the group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ removeMemberFromGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -205,27 +211,27 @@ export class GroupManager extends React.Component<{}> { const user = memberList.splice(index, 1)[0]; groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.removeMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } } /** * Handles changes in the users selected in the "Select users" dropdown. - * @param selectedOptions + * @param selectedOptions */ @action handleChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; - } + }; /** * Creates the group when the enter key has been pressed (when in the input). - * @param e + * @param e */ handleKeyDown = (e: React.KeyboardEvent) => { - e.key === "Enter" && this.createGroup(); - } + e.key === 'Enter' && this.createGroup(); + }; /** * Handles the input of required fields in the setup of a group and resets the relevant variables. @@ -234,32 +240,37 @@ export class GroupManager extends React.Component<{}> { createGroup = () => { const { value } = this.inputRef.current!; if (!value) { - alert("Please enter a group name"); + alert('Please enter a group name'); return; } - if (["admin", "public", "override"].includes(value.toLowerCase())) { - if (value.toLowerCase() !== "admin" || (value.toLowerCase() === "admin" && this.getGroup("Admin"))) { + if (['admin', 'public', 'override'].includes(value.toLowerCase())) { + if (value.toLowerCase() !== 'admin' || (value.toLowerCase() === 'admin' && this.getGroup('Admin'))) { alert(`You cannot override the ${value.charAt(0).toUpperCase() + value.slice(1)} group`); return; } } if (this.getGroup(value)) { - alert("Please select a unique group name"); + alert('Please select a unique group name'); return; } - this.createGroupDoc(value, this.selectedUsers?.map(user => user.value)); + this.createGroupDoc( + value, + this.selectedUsers?.map(user => user.value) + ); this.selectedUsers = null; - this.inputRef.current!.value = ""; - this.buttonColour = "#979797"; + this.inputRef.current!.value = ''; + this.buttonColour = '#979797'; const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect(); TaskCompletionBox.popupX = left - 2 * width; TaskCompletionBox.popupY = top; - TaskCompletionBox.textDisplayed = "Group created!"; + TaskCompletionBox.textDisplayed = 'Group created!'; TaskCompletionBox.taskCompleted = true; - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); - - } + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); + }; /** * @returns the MainViewModal which allows the user to create groups. @@ -268,50 +279,43 @@ export class GroupManager extends React.Component<{}> { const contents = (
-

New Group

-
{ - this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; - })}> - +

+ New Group +

+
{ + this.createGroupModalOpen = false; + TaskCompletionBox.taskCompleted = false; + })}> +
- this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} /> + (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} /> this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value)} - > - {this.sharingOptions(uniform)} - - ) : ( -
- {permissions} -
- )} + const userListContents: (JSX.Element | null)[] = users + .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email)) + .map(({ user, linkDatabase, sharingDoc, userColor }) => { + const userKey = `acl-${normalizeEmail(user.email)}`; + const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey])); + const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-'; + + return !permissions ? null : ( +
+ {user.email} +
+ {admin || this.myDocAcls ? ( + + ) : ( +
{permissions}
+ )} +
-
- ); - }); + ); + }); // checks if every doc has the same author const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author); // the owner of the doc and the current user are placed at the top of the user list. userListContents.unshift( - sameAuthor ? - ( -
- {targetDoc?.author === Doc.CurrentUserEmail ? "Me" : targetDoc?.author} -
-
- Owner -
-
+ sameAuthor ? ( +
+ {targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : targetDoc?.author} +
+
Owner
- ) : null, - sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? - ( -
- Me -
-
- {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : "-multiple-"} -
-
+
+ ) : null, + sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? ( +
+ Me +
+
{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : '-multiple-'}
- ) : null +
+ ) : null ); - // the list of groups shared with - const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true); - groupListMap.unshift({ title: "Public" });//, { title: "ALL" }); + const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true)); + groupListMap.unshift({ title: 'Public' }); //, { title: "ALL" }); const groupListContents = groupListMap.map(group => { const groupKey = `acl-${StrCast(group.title)}`; - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]); - const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : "-multiple-"; - - return !permissions ? (null) : ( -
-
{group.title}
- {group instanceof Doc ? - (
GroupManager.Instance.currentGroup = group)}> - -
) - : (null)} + const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey])); + const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.title)}`]) : '-multiple-'; + + return !permissions ? null : ( +
+
{StrCast(group.title)}
+ {group instanceof Doc ? ( +
(GroupManager.Instance.currentGroup = group))}> + +
+ ) : null}
{admin || this.myDocAcls ? ( - this.setInternalGroupSharing(group, e.currentTarget.value)}> + {this.sharingOptions(uniform, group.title === 'Override')} ) : ( -
- {permissions} -
+
{permissions}
)}
@@ -632,102 +609,95 @@ export class SharingManager extends React.Component<{}> { }); return ( -
- {GroupManager.Instance?.currentGroup ? - GroupManager.Instance.currentGroup = undefined)} - /> : - null} +
+ {GroupManager.Instance?.currentGroup ? (GroupManager.Instance.currentGroup = undefined))} /> : null}
-

Share {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}

-
- +

+ Share + {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} +

+
+
{/* {this.linkVisible ?
{this.sharingUrl}
: (null)} */} - {
-
- - {this.sharingOptions(true)} - - -
-
- this.showUserOptions = !this.showUserOptions)} /> - this.showGroupOptions = !this.showGroupOptions)} /> -
+ { +
+
+ + {this.sharingOptions(true)} + + +
+
+ (this.showUserOptions = !this.showUserOptions))} /> + (this.showGroupOptions = !this.showGroupOptions))} /> +
-
- {Doc.noviceMode ? (null) : -
- this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> -
} +
+ {Doc.noviceMode ? null : ( +
+ (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> +
+ )} +
-
}
-
-
this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}> - Individuals {this.individualSort === "ascending" ? - : this.individualSort === "descending" ? - : } -
-
- {userListContents} +
+
(this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}> + Individuals{' '} + {this.individualSort === 'ascending' ? ( + + ) : this.individualSort === 'descending' ? ( + + ) : ( + + )}
+
{userListContents}
-
-
this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Groups {this.groupSort === "ascending" ? - : this.groupSort === "descending" ? - : } - -
-
- {groupListContents} +
+
(this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}> + Groups{' '} + {this.groupSort === 'ascending' ? ( + + ) : this.groupSort === 'descending' ? ( + + ) : ( + + )}
+
{groupListContents}
-
); } render() { - return ; + return ; } -} \ No newline at end of file +} diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 31fa5b157..542f85228 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -4,42 +4,46 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { AssignAllExtensions } from "../../extensions/General/Extensions"; -import { Docs } from "../documents/Documents"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { LinkManager } from "../util/LinkManager"; +import { AssignAllExtensions } from '../../extensions/General/Extensions'; +import { Docs } from '../documents/Documents'; +import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { LinkManager } from '../util/LinkManager'; import { ReplayMovements } from '../util/ReplayMovements'; -import { TrackMovements } from "../util/TrackMovements"; -import { CollectionView } from "./collections/CollectionView"; -import { MainView } from "./MainView"; +import { TrackMovements } from '../util/TrackMovements'; +import { CollectionView } from './collections/CollectionView'; +import { MainView } from './MainView'; AssignAllExtensions(); (async () => { - MainView.Live = window.location.search.includes("live"); - window.location.search.includes("safe") && CollectionView.SetSafeMode(true); + MainView.Live = window.location.search.includes('live'); + window.location.search.includes('safe') && CollectionView.SetSafeMode(true); const info = await CurrentUserUtils.loadCurrentUser(); - if (info.id !== "__guest__") { + if (info.id !== '__guest__') { // a guest will not have an id registered await CurrentUserUtils.loadUserDocument(info.id); } else { await Docs.Prototypes.initialize(); new LinkManager(); } - document.getElementById('root')!.addEventListener('wheel', event => { - if (event.ctrlKey) { - event.preventDefault(); - } - }, true); + document.getElementById('root')!.addEventListener( + 'wheel', + event => { + if (event.ctrlKey) { + event.preventDefault(); + } + }, + true + ); const startload = (document as any).startLoad; - const loading = Date.now() - (startload ? Number(startload) : (Date.now() - 3000)); - console.log("Load Time = " + loading); + const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000); + console.log('Loading Time = ' + loading); const d = new Date(); - d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000)); - const expires = "expires=" + d.toUTCString(); + d.setTime(d.getTime() + 100 * 24 * 60 * 60 * 1000); + const expires = 'expires=' + d.toUTCString(); document.cookie = `loadtime=${loading};${expires};path=/`; new LinkManager(); new TrackMovements(); new ReplayMovements(); ReactDOM.render(, document.getElementById('root')); -})(); \ No newline at end of file +})(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4940c5f9d..6c0a67de2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -32,7 +32,7 @@ import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOpti import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; import { CollectionViewType } from './collections/CollectionView'; -import "./collections/TreeView.scss"; +import './collections/TreeView.scss'; import { ComponentDecorations } from './ComponentDecorations'; import { ContextMenu } from './ContextMenu'; import { DashboardView } from './DashboardView'; @@ -45,7 +45,7 @@ import { KeyManager } from './GlobalKeyHandler'; import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; -import "./MainView.scss"; +import './MainView.scss'; import { AudioBox } from './nodes/AudioBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; @@ -63,7 +63,7 @@ import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; import { TopBar } from './topbar/TopBar'; -const _global = (window /* browser */ || global /* node */) as any; +const _global = (window /* browser */ || global) /* node */ as any; @observer export class MainView extends React.Component { @@ -73,30 +73,54 @@ export class MainView extends React.Component { @observable public LastButton: Opt; @observable private _windowWidth: number = 0; @observable private _windowHeight: number = 0; - @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) - @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons - @observable private _panelContent: string = "none"; + @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) + @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons + @observable private _panelContent: string = 'none'; @observable private _sidebarContent: any = CurrentUserUtils.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; - @computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js - @computed private get topOfDashUI() { return Number(DASHBOARD_SELECTOR_HEIGHT.replace("px", "")); } - @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; } - @computed private get topOfSidebarDoc() { return this.topOfDashUI + this.topMenuHeight(); } - @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight(); } - @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; } - @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; } - @computed private get userDoc() { return Doc.UserDoc(); } - @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); } - @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; } - @computed private get headerBarDoc() { return CurrentUserUtils.MyHeaderBar; } - @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } + @computed private get dashboardTabHeight() { + return 27; + } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js + @computed private get topOfDashUI() { + return Number(DASHBOARD_SELECTOR_HEIGHT.replace('px', '')); + } + @computed private get topOfHeaderBarDoc() { + return this.topOfDashUI; + } + @computed private get topOfSidebarDoc() { + return this.topOfDashUI + this.topMenuHeight(); + } + @computed private get topOfMainDoc() { + return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight(); + } + @computed private get topOfMainDocContent() { + return this.topOfMainDoc + this.dashboardTabHeight; + } + @computed private get leftScreenOffsetOfMainDocView() { + return this.leftMenuWidth() - 2; + } + @computed private get userDoc() { + return Doc.UserDoc(); + } + @computed private get colorScheme() { + return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); + } + @computed private get mainContainer() { + return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; + } + @computed private get headerBarDoc() { + return CurrentUserUtils.MyHeaderBar; + } + @computed public get mainFreeform(): Opt { + return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data)); + } headerBarDocWidth = () => this.mainDocViewWidth(); headerBarDocHeight = () => CurrentUserUtils.headerBarHeight ?? 0; topMenuHeight = () => 35; topMenuWidth = returnZero; // value is ignored ... - leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace("px", "")); + leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace('px', '')); leftMenuHeight = () => this._dashUIHeight; leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth; leftMenuFlyoutHeight = () => this._dashUIHeight; @@ -106,89 +130,325 @@ export class MainView extends React.Component { mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); componentDidMount() { - document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!)); - const ele = document.getElementById("loader"); - const prog = document.getElementById("dash-progress"); + document.getElementById('root')?.addEventListener('scroll', e => (ele => (ele.scrollLeft = ele.scrollTop = 0))(document.getElementById('root')!)); + const ele = document.getElementById('loader'); + const prog = document.getElementById('dash-progress'); if (ele && prog) { // remove from DOM setTimeout(() => { clearTimeout(); - prog.style.transition = "1s"; - prog.style.width = "100%"; + prog.style.transition = '1s'; + prog.style.width = '100%'; }, 0); - setTimeout(() => ele.outerHTML = '', 1000); + setTimeout(() => (ele.outerHTML = ''), 1000); } this._sidebarContent.proto = undefined; if (!MainView.Live) { - DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "showSidebar", "sidebarWidthPercent", "viewTransition", - "panX", "panY", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap", - "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's + DocServer.setPlaygroundFields([ + 'dataTransition', + 'treeViewOpen', + 'showSidebar', + 'sidebarWidthPercent', + 'viewTransition', + 'panX', + 'panY', + 'nativeWidth', + 'nativeHeight', + 'text-scrollHeight', + 'text-height', + 'hideMinimap', + 'viewScale', + 'scrollTop', + 'hidden', + 'curPage', + 'viewType', + 'chromeHidden', + 'nativeWidth', + ]); // can play with these fields on someone else's } - DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); + DocServer.GetRefField('rtfProto').then( + proto => + proto instanceof Doc && + reaction( + () => StrCast(proto.BROADCAST_MESSAGE), + msg => msg && alert(msg) + ) + ); const tag = document.createElement('script'); - tag.src = "https://www.youtube.com/iframe_api"; + tag.src = 'https://www.youtube.com/iframe_api'; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); - window.removeEventListener("keydown", KeyManager.Instance.handle); - window.addEventListener("keydown", KeyManager.Instance.handle); - window.removeEventListener("keyup", KeyManager.Instance.unhandle); - window.addEventListener("keyup", KeyManager.Instance.unhandle); - window.addEventListener("paste", KeyManager.Instance.paste as any); - document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on + window.removeEventListener('keydown', KeyManager.Instance.handle); + window.addEventListener('keydown', KeyManager.Instance.handle); + window.removeEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('paste', KeyManager.Instance.paste as any); + document.addEventListener('dash', (e: any) => { + // event used by chrome plugin to tell Dash which document to focus on const id = FormattedTextBox.GetDocFromUrl(e.detail); - DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : (null)); + DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : null)); }); - document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener); + document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener); this.initEventListeners(); } componentWillUnMount() { - window.removeEventListener("keyup", KeyManager.Instance.unhandle); - window.removeEventListener("keydown", KeyManager.Instance.handle); - window.removeEventListener("pointerdown", this.globalPointerDown); - window.removeEventListener("paste", KeyManager.Instance.paste as any); - document.removeEventListener("linkAnnotationToDash", Hypothesis.linkListener); + window.removeEventListener('keyup', KeyManager.Instance.unhandle); + window.removeEventListener('keydown', KeyManager.Instance.handle); + window.removeEventListener('pointerdown', this.globalPointerDown); + window.removeEventListener('paste', KeyManager.Instance.paste as any); + document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); } constructor(props: Readonly<{}>) { super(props); MainView.Instance = this; - CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || {} as any; + CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); // causes errors to be generated when modifying an observable outside of an action - configure({ enforceActions: "observed" }); + configure({ enforceActions: 'observed' }); - if (window.location.pathname !== "/home") { - const pathname = window.location.pathname.substr(1).split("/"); - if (pathname.length > 1 && pathname[0] === "doc") { + if (window.location.pathname !== '/home') { + const pathname = window.location.pathname.substr(1).split('/'); + if (pathname.length > 1 && pathname[0] === 'doc') { CurrentUserUtils.MainDocId = pathname[1]; !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field))); } } - library.add(...[fa.faEdit, fa.faTrash, fa.faTrashAlt, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faCalendar, - fa.faSquare, far.faSquare as any, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, - fa.faLock, fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointLeft, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone, fa.faKeyboard, - fa.faQuestion, fa.faTasks, fa.faPalette, fa.faAngleLeft, fa.faAngleRight, fa.faBell, fa.faCamera, fa.faExpand, fa.faCaretDown, fa.faCaretLeft, fa.faCaretRight, - fa.faCaretSquareDown, fa.faCaretSquareRight, fa.faArrowsAltH, fa.faPlus, fa.faMinus, fa.faTerminal, fa.faToggleOn, fa.faFile, fa.faLocationArrow, - fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft, - fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp, - fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt, - fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, - fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV, - fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideoSlash, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, fa.faFlag, - fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, - fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, - fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any, - fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle as any, - fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, fa.faAngleDoubleDown, fa.faAngleDoubleLeft, fa.faAngleDoubleUp, faBuffer as any, fa.faExpand, fa.faUndo, - fa.faSlidersH, fa.faAngleUp, fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl, - fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo, - fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, - fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, - fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines, - fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeDown, fa.faSquareRootAlt, fa.faVolumeMute]); + library.add( + ...[ + fa.faEdit, + fa.faTrash, + fa.faTrashAlt, + fa.faShare, + fa.faDownload, + fa.faExpandArrowsAlt, + fa.faLayerGroup, + fa.faExternalLinkAlt, + fa.faCalendar, + fa.faSquare, + far.faSquare as any, + fa.faConciergeBell, + fa.faWindowRestore, + fa.faFolder, + fa.faMapPin, + fa.faMapMarker, + fa.faFingerprint, + fa.faCrosshairs, + fa.faDesktop, + fa.faUnlock, + fa.faLock, + fa.faLaptopCode, + fa.faMale, + fa.faCopy, + fa.faHandPointLeft, + fa.faHandPointRight, + fa.faCompass, + fa.faSnowflake, + fa.faMicrophone, + fa.faKeyboard, + fa.faQuestion, + fa.faTasks, + fa.faPalette, + fa.faAngleLeft, + fa.faAngleRight, + fa.faBell, + fa.faCamera, + fa.faExpand, + fa.faCaretDown, + fa.faCaretLeft, + fa.faCaretRight, + fa.faCaretSquareDown, + fa.faCaretSquareRight, + fa.faArrowsAltH, + fa.faPlus, + fa.faMinus, + fa.faTerminal, + fa.faToggleOn, + fa.faFile, + fa.faLocationArrow, + fa.faSearch, + fa.faFileDownload, + fa.faFileUpload, + fa.faStop, + fa.faCalculator, + fa.faWindowMaximize, + fa.faAddressCard, + fa.faQuestionCircle, + fa.faArrowLeft, + fa.faArrowRight, + fa.faArrowDown, + fa.faArrowUp, + fa.faBolt, + fa.faBullseye, + fa.faCaretUp, + fa.faCat, + fa.faCheck, + fa.faChevronRight, + fa.faChevronLeft, + fa.faChevronDown, + fa.faChevronUp, + fa.faClone, + fa.faCloudUploadAlt, + fa.faCommentAlt, + fa.faCompressArrowsAlt, + fa.faCut, + fa.faEllipsisV, + fa.faEraser, + fa.faExclamation, + fa.faFileAlt, + fa.faFileAudio, + fa.faFileVideo, + fa.faFilePdf, + fa.faFilm, + fa.faFilter, + fa.faFont, + fa.faGlobeAmericas, + fa.faGlobeAsia, + fa.faHighlighter, + fa.faLongArrowAltRight, + fa.faMousePointer, + fa.faMusic, + fa.faObjectGroup, + fa.faPause, + fa.faPen, + fa.faPenNib, + fa.faPhone, + fa.faPlay, + fa.faPortrait, + fa.faRedoAlt, + fa.faStamp, + fa.faStickyNote, + fa.faArrowsAltV, + fa.faTimesCircle, + fa.faThumbtack, + fa.faTree, + fa.faTv, + fa.faUndoAlt, + fa.faVideoSlash, + fa.faVideo, + fa.faAsterisk, + fa.faBrain, + fa.faImage, + fa.faPaintBrush, + fa.faTimes, + fa.faFlag, + fa.faEye, + fa.faArrowsAlt, + fa.faQuoteLeft, + fa.faSortAmountDown, + fa.faAlignLeft, + fa.faAlignCenter, + fa.faAlignRight, + fa.faHeading, + fa.faRulerCombined, + fa.faFillDrip, + fa.faLink, + fa.faUnlink, + fa.faBold, + fa.faItalic, + fa.faClipboard, + fa.faUnderline, + fa.faStrikethrough, + fa.faSuperscript, + fa.faSubscript, + fa.faIndent, + fa.faEyeDropper, + fa.faPaintRoller, + fa.faBars, + fa.faBrush, + fa.faShapes, + fa.faEllipsisH, + fa.faHandPaper, + fa.faMap, + fa.faUser, + faHireAHelper as any, + fa.faTrashRestore, + fa.faUsers, + fa.faWrench, + fa.faCog, + fa.faMap, + fa.faBellSlash, + fa.faExpandAlt, + fa.faArchive, + fa.faBezierCurve, + fa.faCircle, + far.faCircle as any, + fa.faLongArrowAltRight, + fa.faPenFancy, + fa.faAngleDoubleRight, + fa.faAngleDoubleDown, + fa.faAngleDoubleLeft, + fa.faAngleDoubleUp, + faBuffer as any, + fa.faExpand, + fa.faUndo, + fa.faSlidersH, + fa.faAngleUp, + fa.faAngleDown, + fa.faPlayCircle, + fa.faClock, + fa.faRocket, + fa.faExchangeAlt, + fa.faHashtag, + fa.faAlignJustify, + fa.faCheckSquare, + fa.faListUl, + fa.faWindowMinimize, + fa.faWindowRestore, + fa.faTextWidth, + fa.faTextHeight, + fa.faClosedCaptioning, + fa.faInfoCircle, + fa.faTag, + fa.faSyncAlt, + fa.faPhotoVideo, + fa.faArrowAltCircleDown, + fa.faArrowAltCircleUp, + fa.faArrowAltCircleLeft, + fa.faArrowAltCircleRight, + fa.faStopCircle, + fa.faCheckCircle, + fa.faGripVertical, + fa.faSortUp, + fa.faSortDown, + fa.faTable, + fa.faTh, + fa.faThList, + fa.faProjectDiagram, + fa.faSignature, + fa.faColumns, + fa.faChevronCircleUp, + fa.faUpload, + fa.faBorderAll, + fa.faBraille, + fa.faChalkboard, + fa.faPencilAlt, + fa.faEyeSlash, + fa.faSmile, + fa.faIndent, + fa.faOutdent, + fa.faChartBar, + fa.faBan, + fa.faPhoneSlash, + fa.faGripLines, + fa.faSave, + fa.faBookmark, + fa.faList, + fa.faListOl, + fa.faFolderPlus, + fa.faLightbulb, + fa.faBookOpen, + fa.faMapMarkerAlt, + fa.faSearchPlus, + fa.faVolumeUp, + fa.faVolumeDown, + fa.faSquareRootAlt, + fa.faVolumeMute, + ] + ); this.initAuthenticationRouters(); } @@ -197,175 +457,211 @@ export class MainView extends React.Component { const targets = document.elementsFromPoint(e.x, e.y); if (targets.length) { const targClass = targets[0].className.toString(); - !targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu(); - !["timeline-menu-desc", "timeline-menu-item", "timeline-menu-input"].includes(targClass) && TimelineMenu.Instance.closeMenu(); + !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu(); + !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu(); } }); initEventListeners = () => { - window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page - window.addEventListener("dragover", e => e.preventDefault(), false); + window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page + window.addEventListener('dragover', e => e.preventDefault(), false); // document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined)); - document.addEventListener("pointerdown", this.globalPointerDown); - document.addEventListener("click", (e: MouseEvent) => { - if (!e.cancelBubble) { - const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join(); - if (pathstr?.includes("libraryFlyout")) { - SelectionManager.DeselectAll(); + document.addEventListener('pointerdown', this.globalPointerDown); + document.addEventListener( + 'click', + (e: MouseEvent) => { + if (!e.cancelBubble) { + const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join(); + if (pathstr?.includes('libraryFlyout')) { + SelectionManager.DeselectAll(); + } } - } - }, false); + }, + false + ); document.oncontextmenu = () => false; - } + }; initAuthenticationRouters = async () => { const received = CurrentUserUtils.MainDocId; if (received && !this.userDoc) { - reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(), { fireImmediately: true }); - } + reaction( + () => CurrentUserUtils.GuestTarget, + target => target && CurrentUserUtils.createNewDashboard(), + { fireImmediately: true } + ); + } // else { // PromiseValue(this.userDoc.activeDashboard).then(dash => { // if (dash instanceof Doc) CurrentUserUtils.openDashboard(dash); // else CurrentUserUtils.createNewDashboard(); // }); // } - } + }; @action createNewPresentation = async () => { - const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); - CollectionDockingView.AddSplit(pres, "left"); + const pres = Docs.Create.PresDocument({ title: 'Untitled Trail', _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: 'alias', _chromeHidden: true, boxShadow: '0 0' }); + CollectionDockingView.AddSplit(pres, 'left'); CurrentUserUtils.ActivePresentation = pres; - Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", pres); - } + Doc.AddDocToList(CurrentUserUtils.MyTrails, 'data', pres); + }; @action createNewFolder = async () => { - const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); - Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); - } + const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true }); + Doc.AddDocToList(CurrentUserUtils.MyFilesystem, 'data', folder); + }; @observable _exploreMode = false; @computed get exploreMode() { - return () => this._exploreMode ? ScriptField.MakeScript("CollectionBrowseClick(documentView, clientX, clientY)", - { documentView: "any", clientX: "number", clientY: "number" })! : undefined; + return () => (this._exploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined); } headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1); @computed get headerBarDocView() { - return
-
; + return ( +
+ +
+ ); } @computed get mainDocView() { - return <> - {this.headerBarDocView} - ; + return ( + <> + {this.headerBarDocView} + + + ); } @computed get dockingContent() { - return
{ e.stopPropagation(); e.preventDefault(); }} - style={{ - minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, - transform: LightboxView.LightboxDoc ? "scale(0.0001)" : undefined, - }}> - {!this.mainContainer ? (null) : this.mainDocView} -
; + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + }} + style={{ + minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, + transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined, + }}> + {!this.mainContainer ? null : this.mainDocView} +
+ ); } @action onPropertiesPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - action(e => (CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false), + setupMoveUpEvents( + this, + e, + action(e => ((CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)), action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)), - action(() => CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0), false); - } + action(() => (CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)), + false + ); + }; @action onFlyoutPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - action(e => (this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false), + setupMoveUpEvents( + this, + e, + action(e => ((this._leftMenuFlyoutWidth = Math.max(e.clientX - 58, 0)) ? false : false)), () => this._leftMenuFlyoutWidth < 5 && this.closeFlyout(), - this.closeFlyout); - } + this.closeFlyout + ); + }; sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1); mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); addDocTabFunc = (doc: Doc, location: string): boolean => { - const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); - const locationParams = locationFields.length > 1 ? locationFields[1] : ""; + const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':'); + const locationParams = locationFields.length > 1 ? locationFields[1] : ''; if (doc.dockingConfig) return CurrentUserUtils.openDashboard(doc); switch (locationFields[0]) { - case "dashboard": return CurrentUserUtils.openDashboard(doc); - case "close": return CollectionDockingView.CloseSplit(doc, locationParams); - case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); - case "lightbox": return LightboxView.AddDocTab(doc, location); - case "toggle": return CollectionDockingView.ToggleSplit(doc, locationParams); - case "inPlace": - case "add": + case 'dashboard': + return CurrentUserUtils.openDashboard(doc); + case 'close': + return CollectionDockingView.CloseSplit(doc, locationParams); + case 'fullScreen': + return CollectionDockingView.OpenFullScreen(doc); + case 'lightbox': + return LightboxView.AddDocTab(doc, location); + case 'toggle': + return CollectionDockingView.ToggleSplit(doc, locationParams); + case 'inPlace': + case 'add': default: return CollectionDockingView.AddSplit(doc, locationParams); } - } - + }; @computed get flyout() { - return !this._leftMenuFlyoutWidth ?
- {this.docButtons} -
: -
-
+ return !this._leftMenuFlyoutWidth ? ( +
+ {this.docButtons} +
+ ) : ( +
+
{this.docButtons} -
; +
+ ); } @computed get leftMenuPanel() { - return
- -
; + return ( +
+ +
+ ); } @action @@ -432,69 +731,78 @@ export class MainView extends React.Component { const willOpen = !this._leftMenuFlyoutWidth || this._panelContent !== title; this.closeFlyout(); if (willOpen) { - switch (this._panelContent = title) { - case "Settings": + switch ((this._panelContent = title)) { + case 'Settings': SettingsManager.Instance.open(); break; - case "Help": + case 'Help': break; default: this.expandFlyout(button); } } return true; - } + }; @computed get mainInnerContent() { const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth(); const width = this.propertiesWidth() + leftMenuFlyoutWidth; - return <> - {this.leftMenuPanel} -
- {this.flyout} -
- -
-
- - {this.dockingContent} - -
- + return ( + <> + {this.leftMenuPanel} +
+ {this.flyout} +
+
-
- {this.propertiesWidth() < 10 ? (null) : } +
+ {this.dockingContent} + +
+ +
+
+ {this.propertiesWidth() < 10 ? null : } +
-
- ; + + ); } - @computed get mainDashboardArea() { - return !this.userDoc ? (null) : -
{ - r && new _global.ResizeObserver(action(() => { - this._dashUIWidth = r.getBoundingClientRect().width; - this._dashUIHeight = r.getBoundingClientRect().height; - })).observe(r); - }} style={{ - color: this.colorScheme === ColorScheme.Dark ? "rgb(205,205,205)" : "black", - height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`, - width: "100%", - }} > + return !this.userDoc ? null : ( +
{ + r && + new _global.ResizeObserver( + action(() => { + this._dashUIWidth = r.getBoundingClientRect().width; + this._dashUIHeight = r.getBoundingClientRect().height; + }) + ).observe(r); + }} + style={{ + color: this.colorScheme === ColorScheme.Dark ? 'rgb(205,205,205)' : 'black', + height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`, + width: '100%', + }}> {this.mainInnerContent} -
; +
+ ); } - expandFlyout = action((button: Doc) => { - // bcz: What's going on here!? + // bcz: What's going on here!? // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be. // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);) - this._leftMenuFlyoutWidth = (this._leftMenuFlyoutWidth || 250); - setTimeout(action(() => this._leftMenuFlyoutWidth += 0.5), 0); + this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250; + setTimeout( + action(() => (this._leftMenuFlyoutWidth += 0.5)), + 0 + ); this._sidebarContent.proto = button.target as any; this.LastButton = button; @@ -502,29 +810,29 @@ export class MainView extends React.Component { closeFlyout = action(() => { this.LastButton = undefined; - this._panelContent = "none"; + this._panelContent = 'none'; this._sidebarContent.proto = undefined; this._leftMenuFlyoutWidth = 0; }); - remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(CurrentUserUtils.MyDockedBtns, "data", doc), true); + remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(CurrentUserUtils.MyDockedBtns, 'data', doc), true); moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc); - addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(CurrentUserUtils.MyDockedBtns, "data", doc), true); + addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(CurrentUserUtils.MyDockedBtns, 'data', doc), true); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); return new Transform(-translateX, -translateY, 1 / scale); - } + }; @computed get docButtons() { - return !CurrentUserUtils.MyDockedBtns ? (null) : -
+ return !CurrentUserUtils.MyDockedBtns ? null : ( +
- {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ?
{this.userDoc?.presentationMode}
: <>} -
; + ContainingCollectionDoc={undefined} + /> + {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ?
{StrCast(this.userDoc?.presentationMode)}
: <>} +
+ ); } @computed get snapLines() { - return !SnappingManager.GetShowSnapLines() ? (null) :
- - {SnappingManager.horizSnapLines().map(l => )} - {SnappingManager.vertSnapLines().map(l => )} - -
; + return !SnappingManager.GetShowSnapLines() ? null : ( +
+ + {SnappingManager.horizSnapLines().map(l => ( + + ))} + {SnappingManager.vertSnapLines().map(l => ( + + ))} + +
+ ); } @computed get inkResources() { - return - - - + - - - - - - - - - - - ; + 0 0 0 1 0"> + + + + + + + + + + + ); } - @computed get invisibleWebBox() { // see note under the makeLink method in HypothesisUtils.ts - return !DocumentLinksButton.invisibleWebDoc ? null : + @computed get invisibleWebBox() { + // see note under the makeLink method in HypothesisUtils.ts + return !DocumentLinksButton.invisibleWebDoc ? null : (
-
; +
+ ); } render() { - return (
((ele) => ele.scrollTop = ele.scrollLeft = 0)(document.getElementById("root")!)} - ref={r => { - r && new _global.ResizeObserver(action(() => { this._windowWidth = r.getBoundingClientRect().width; this._windowHeight = r.getBoundingClientRect().height; })).observe(r); - }}> - {this.inkResources} - - - - - - - - - - {LinkDescriptionPopup.descriptionPopup ? : null} - {DocumentLinksButton.LinkEditorDocView ? DocumentLinksButton.LinkEditorDocView = undefined)} docView={DocumentLinksButton.LinkEditorDocView} /> : (null)} - {LinkDocPreview.LinkInfo ? : (null)} - - {((page:string) => { - switch (page) { - case "dashboard": - default:return <> -
- -
- {this.mainDashboardArea} - ; - case "home": return ; - } })(CurrentUserUtils.ActivePage) - } - - - - - - - - - - - - - {this.snapLines} -
- -
); + return ( +
(ele => (ele.scrollTop = ele.scrollLeft = 0))(document.getElementById('root')!)} + ref={r => { + r && + new _global.ResizeObserver( + action(() => { + this._windowWidth = r.getBoundingClientRect().width; + this._windowHeight = r.getBoundingClientRect().height; + }) + ).observe(r); + }}> + {this.inkResources} + + + + + + + + + + {LinkDescriptionPopup.descriptionPopup ? : null} + {DocumentLinksButton.LinkEditorDocView ? (DocumentLinksButton.LinkEditorDocView = undefined))} docView={DocumentLinksButton.LinkEditorDocView} /> : null} + {LinkDocPreview.LinkInfo ? : null} + + {((page: string) => { + switch (page) { + case 'dashboard': + default: + return ( + <> +
+ +
+ {this.mainDashboardArea} + + ); + case 'home': + return ; + } + })(CurrentUserUtils.ActivePage)} + + + + + + + + + + + + + {this.snapLines} +
+ +
+ ); } makeWebRef = (ele: HTMLDivElement) => { - reaction(() => DocumentLinksButton.invisibleWebDoc, + reaction( + () => DocumentLinksButton.invisibleWebDoc, invisibleDoc => { ReactDOM.unmountComponentAtNode(ele); - invisibleDoc && ReactDOM.render( -
- 500} - PanelHeight={() => 800} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - /> -
; -
, ele); + invisibleDoc && + ReactDOM.render( + +
+ 500} + PanelHeight={() => 800} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + /> +
+ ; +
, + ele + ); let success = false; const onSuccess = () => { success = true; clearTimeout(interval); - document.removeEventListener("editSuccess", onSuccess); + document.removeEventListener('editSuccess', onSuccess); }; // For some reason, Hypothes.is annotations don't load until a click is registered on the page, // so we keep simulating clicks until annotations have loaded and editing is successful const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500); setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s - document.addEventListener("editSuccess", onSuccess); - }); - } + document.addEventListener('editSuccess', onSuccess); + } + ); + }; } -ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { MainView.Instance.selectMenu(doc); }); \ No newline at end of file +ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { + MainView.Instance.selectMenu(doc); +}); diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 789756a78..436cb688f 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -4,7 +4,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; const HOLD_DURATION = 1000; -export abstract class Touchable extends React.Component { +export abstract class Touchable extends React.Component> { //private holdTimer: NodeJS.Timeout | undefined; private moveDisposer?: InteractionUtils.MultiTouchEventDisposer; private endDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -25,7 +25,6 @@ export abstract class Touchable extends React.Component { */ @action protected onTouchStart = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { - const actualPts: React.Touch[] = []; const te = me.touchEvent; // loop through all touches on screen @@ -60,7 +59,7 @@ export abstract class Touchable extends React.Component { case 1: this.handle1PointerDown(te, me); te.persist(); - // -- code for radial menu -- + // -- code for radial menu -- // if (this.holdTimer) { // clearTimeout(this.holdTimer) // this.holdTimer = undefined; @@ -71,11 +70,11 @@ export abstract class Touchable extends React.Component { break; } } - } + }; /** - * Handle touch move event - */ + * Handle touch move event + */ @action protected onTouch = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { const te = me.touchEvent; @@ -85,8 +84,12 @@ export abstract class Touchable extends React.Component { if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return; this._touchDrag = true; switch (myTouches.length) { - case 1: this.handle1PointerMove(te, me); break; - case 2: this.handle2PointersMove(te, me); break; + case 1: + this.handle1PointerMove(te, me); + break; + case 2: + this.handle2PointersMove(te, me); + break; } for (const pt of me.touches) { @@ -94,7 +97,7 @@ export abstract class Touchable extends React.Component { this.prevPoints.set(pt.identifier, pt); } } - } + }; @action protected onTouchEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { @@ -110,7 +113,6 @@ export abstract class Touchable extends React.Component { this._touchDrag = false; te.stopPropagation(); - // if (e.targetTouches.length === 0) { // this.prevPoints.clear(); // } @@ -119,36 +121,36 @@ export abstract class Touchable extends React.Component { this.cleanUpInteractions(); } e.stopPropagation(); - } + }; cleanUpInteractions = (): void => { this.removeMoveListeners(); this.removeEndListeners(); - } + }; handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); e.preventDefault(); - } + }; handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); e.preventDefault(); - } + }; handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); - } + }; handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); - } + }; handle1PointerHoldStart = (e: Event, me: InteractionUtils.MultiTouchEvent): any => { e.stopPropagation(); @@ -159,30 +161,30 @@ export abstract class Touchable extends React.Component { this.removeHoldEndListeners(); this.addHoldMoveListeners(); this.addHoldEndListeners(); - } + }; addMoveListeners = () => { const handler = (e: Event) => this.onTouch(e, (e as CustomEvent>).detail); - document.addEventListener("dashOnTouchMove", handler); - this.moveDisposer = () => document.removeEventListener("dashOnTouchMove", handler); - } + document.addEventListener('dashOnTouchMove', handler); + this.moveDisposer = () => document.removeEventListener('dashOnTouchMove', handler); + }; addEndListeners = () => { const handler = (e: Event) => this.onTouchEnd(e, (e as CustomEvent>).detail); - document.addEventListener("dashOnTouchEnd", handler); - this.endDisposer = () => document.removeEventListener("dashOnTouchEnd", handler); - } + document.addEventListener('dashOnTouchEnd', handler); + this.endDisposer = () => document.removeEventListener('dashOnTouchEnd', handler); + }; addHoldMoveListeners = () => { const handler = (e: Event) => this.handle1PointerHoldMove(e, (e as CustomEvent>).detail); - document.addEventListener("dashOnTouchHoldMove", handler); - this.holdMoveDisposer = () => document.removeEventListener("dashOnTouchHoldMove", handler); - } + document.addEventListener('dashOnTouchHoldMove', handler); + this.holdMoveDisposer = () => document.removeEventListener('dashOnTouchHoldMove', handler); + }; addHoldEndListeners = () => { const handler = (e: Event) => this.handle1PointerHoldEnd(e, (e as CustomEvent>).detail); - document.addEventListener("dashOnTouchHoldEnd", handler); - this.holdEndDisposer = () => document.removeEventListener("dashOnTouchHoldEnd", handler); - } + document.addEventListener('dashOnTouchHoldEnd', handler); + this.holdEndDisposer = () => document.removeEventListener('dashOnTouchHoldEnd', handler); + }; removeMoveListeners = () => this.moveDisposer?.(); removeEndListeners = () => this.endDisposer?.(); @@ -192,7 +194,7 @@ export abstract class Touchable extends React.Component { handle1PointerHoldMove = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { // e.stopPropagation(); // me.touchEvent.stopPropagation(); - } + }; handle1PointerHoldEnd = (e: Event, me: InteractionUtils.MultiTouchEvent): void => { e.stopPropagation(); @@ -202,10 +204,10 @@ export abstract class Touchable extends React.Component { me.touchEvent.stopPropagation(); me.touchEvent.preventDefault(); - } + }; handleHandDown = (e: React.TouchEvent) => { // e.stopPropagation(); // e.preventDefault(); - } -} \ No newline at end of file + }; +} diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index e80ba6f36..7a393a4f7 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -1,19 +1,19 @@ -import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from "@fortawesome/free-solid-svg-icons"; +import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, trace } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc, DocListCast } from "../../../fields/Doc"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils"; -import { FieldViewProps } from "../nodes/FieldView"; -import { KeyframeFunc } from "./Keyframe"; -import "./Timeline.scss"; -import { TimelineOverview } from "./TimelineOverview"; -import { Track } from "./Track"; -import clamp from "../../util/clamp"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { IconLookup } from "@fortawesome/fontawesome-svg-core"; +import { action, computed, observable, runInAction, trace } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from '../../../Utils'; +import { FieldViewProps } from '../nodes/FieldView'; +import { KeyframeFunc } from './Keyframe'; +import './Timeline.scss'; +import { TimelineOverview } from './TimelineOverview'; +import { Track } from './Track'; +import clamp from '../../util/clamp'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { IconLookup } from '@fortawesome/fontawesome-svg-core'; /** * Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are @@ -35,10 +35,8 @@ import { IconLookup } from "@fortawesome/fontawesome-svg-core"; @author Andrew Kim */ - @observer export class Timeline extends React.Component { - //readonly constants private readonly DEFAULT_TICK_SPACING: number = 50; private readonly MAX_TITLE_HEIGHT = 75; @@ -57,8 +55,7 @@ export class Timeline extends React.Component { @observable private _roundToggleRef = React.createRef(); @observable private _roundToggleContainerRef = React.createRef(); - - //boolean vars and instance vars + //boolean vars and instance vars @observable private _currentBarX: number = 0; @observable private _windSpeed: number = 1; @observable private _isPlaying: boolean = false; //scrubber playing @@ -73,13 +70,13 @@ export class Timeline extends React.Component { @observable private _titleHeight = 0; /** - * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit. + * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit. */ @computed private get children(): Doc[] { const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this.props.Document.type) as any); if (annotatedDoc) { - return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"]); + return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + '-annotations']); } return DocListCast(this.props.Document[this.props.fieldKey]); } @@ -91,7 +88,8 @@ export class Timeline extends React.Component { this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset - if (!this.props.Document.AnimationLength) { //if animation length did not exist + if (!this.props.Document.AnimationLength) { + //if animation length did not exist this.props.Document.AnimationLength = this._time; //set it to default time } else { this._time = NumCast(this.props.Document.AnimationLength); //else, set time to animationlength stored from before @@ -116,24 +114,29 @@ export class Timeline extends React.Component { drawTicks = () => { const ticks = []; for (let i = 0; i < this._time / this._tickIncrement; i++) { - ticks.push(

{this.toReadTime(i * this._tickIncrement)}

); + ticks.push( +
+ {' '} +

{this.toReadTime(i * this._tickIncrement)}

+
+ ); } return ticks; - } + }; /** * changes the scrubber to actual pixel position */ @action changeCurrentBarX = (pixel: number) => { - pixel <= 0 ? this._currentBarX = 0 : pixel >= this._totalLength ? this._currentBarX = this._totalLength : this._currentBarX = pixel; - } + pixel <= 0 ? (this._currentBarX = 0) : pixel >= this._totalLength ? (this._currentBarX = this._totalLength) : (this._currentBarX = pixel); + }; //for playing onPlay = (e: React.MouseEvent) => { e.stopPropagation(); this.play(); - } + }; /** * when playbutton is clicked @@ -149,8 +152,7 @@ export class Timeline extends React.Component { this._isPlaying = !this._isPlaying; this._playButton = this._isPlaying ? faPauseCircle : faPlayCircle; this._isPlaying && playTimeline(); - } - + }; /** * fast forward the timeline scrubbing @@ -159,30 +161,32 @@ export class Timeline extends React.Component { windForward = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - if (this._windSpeed < 64) { //max speed is 32 + if (this._windSpeed < 64) { + //max speed is 32 this._windSpeed = this._windSpeed * 2; } - } + }; /** - * rewind the timeline scrubbing + * rewind the timeline scrubbing */ @action windBackward = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - if (this._windSpeed > 1 / 16) { // min speed is 1/8 + if (this._windSpeed > 1 / 16) { + // min speed is 1/8 this._windSpeed = this._windSpeed / 2; } - } + }; /** - * scrubber down + * scrubber down */ @action onScrubberDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.onScrubberMove, emptyFunction, emptyFunction); - } + }; /** * when there is any scrubber movement @@ -194,16 +198,15 @@ export class Timeline extends React.Component { const offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale; this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position return false; - } + }; /** * when panning the timeline (in editing mode) */ @action onPanDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, (e) => - this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left)); - } + setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, e => this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left)); + }; /** * when moving the timeline (in editing mode) @@ -218,29 +221,34 @@ export class Timeline extends React.Component { if (this._visibleStart + this._visibleLength + 20 >= this._totalLength) { this._visibleStart -= e.movementX; this._totalLength -= e.movementX; - this._time -= KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this._tickSpacing, this._tickIncrement); + this._time -= KeyframeFunc.convertPixelTime(e.movementX, 'mili', 'time', this._tickSpacing, this._tickIncrement); this.props.Document.AnimationLength = this._time; } return false; - } - + }; @action movePanX = (pixel: number) => { this._infoContainer.current!.scrollLeft = pixel; this._visibleStart = this._infoContainer.current!.scrollLeft; - } + }; /** * resizing timeline (in editing mode) (the hamburger drag icon) */ onResizeDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, action((e) => { - const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom; - this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT); - return false; - }), emptyFunction, emptyFunction); - } + setupMoveUpEvents( + this, + e, + action(e => { + const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom; + this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT); + return false; + }), + emptyFunction, + emptyFunction + ); + }; /** * for displaying time to standard min:sec @@ -251,19 +259,18 @@ export class Timeline extends React.Component { const inSeconds = Math.round(time * 100) / 100; const min = Math.floor(inSeconds / 60); - const sec = (Math.round((inSeconds % 60) * 100) / 100); + const sec = Math.round((inSeconds % 60) * 100) / 100; let secString = sec.toFixed(2); if (Math.floor(sec / 10) === 0) { - secString = "0" + secString; + secString = '0' + secString; } return `${min}:${secString}`; - } - + }; /** - * timeline zoom function + * timeline zoom function * use mouse middle button to zoom in/out the timeline */ @action @@ -271,17 +278,16 @@ export class Timeline extends React.Component { e.preventDefault(); e.stopPropagation(); const offset = e.clientX - this._infoContainer.current!.getBoundingClientRect().left; - const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, "mili", "time", this._tickSpacing, this._tickIncrement); - const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement); + const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, 'mili', 'time', this._tickSpacing, this._tickIncrement); + const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, 'mili', 'time', this._tickSpacing, this._tickIncrement); this.zoom(e.deltaY < 0); - const currPixel = KeyframeFunc.convertPixelTime(prevTime, "mili", "pixel", this._tickSpacing, this._tickIncrement); - const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, "mili", "pixel", this._tickSpacing, this._tickIncrement); + const currPixel = KeyframeFunc.convertPixelTime(prevTime, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); + const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); this._infoContainer.current!.scrollLeft = currPixel - offset; this._visibleStart = currPixel - offset > 0 ? currPixel - offset : 0; this._visibleStart += this._visibleLength + this._visibleStart > this._totalLength ? this._totalLength - (this._visibleStart + this._visibleLength) : 0; this.changeCurrentBarX(currCurrent); - } - + }; resetView(doc: Doc) { doc._panX = doc._customOriginX ?? 0; @@ -324,7 +330,7 @@ export class Timeline extends React.Component { this._tickSpacing = spacingChange; this._tickIncrement = incrementChange; } - } + }; /** * tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode) @@ -333,60 +339,90 @@ export class Timeline extends React.Component { const size = 40 * scale; //50 is default const iconSize = 25; const width: number = this.props.PanelWidth(); - const modeType = this.props.Document.isATOn ? "Author" : "Play"; + const modeType = this.props.Document.isATOn ? 'Author' : 'Play'; //decides if information should be omitted because the timeline is very small // if its less than 950 pixels then it's going to be overlapping - let modeString = modeType, overviewString = "", lengthString = ""; + let modeString = modeType, + overviewString = '', + lengthString = ''; if (width < 850) { - modeString = "Mode: " + modeType; - overviewString = "Overview:"; - lengthString = "Length: "; + modeString = 'Mode: ' + modeType; + overviewString = 'Overview:'; + lengthString = 'Length: '; } return (
-
-
-
+
+ {' '} + {' '} +
+
+ {' '} + {' '} +
+
+ {' '} + {' '} +
-
{overviewString}
- +
+ {overviewString} +
+
-
{modeString}
+
+ {modeString} +
-
+
+ {' '} +
-
+
{this.timeIndicator(lengthString, totalTime)} -
this.resetView(this.props.Document)}>
-
this.setView(this.props.Document)}>
+
this.resetView(this.props.Document)}> + +
+
this.setView(this.props.Document)}> + +
); - } + }; timeIndicator(lengthString: string, totalTime: number) { if (this.props.Document.isATOn) { - return ( -
{`Total: ${this.toReadTime(totalTime)}`}
- ); - } - else { + return
{`Total: ${this.toReadTime(totalTime)}`}
; + } else { const ctime = `Current: ${this.getCurrentTime()}`; const ttime = `Total: ${this.toReadTime(this._time)}`; return ( -
-
+
+
{ctime}
-
+
{ttime}
@@ -402,7 +438,7 @@ export class Timeline extends React.Component { e.preventDefault(); e.stopPropagation(); this.toggleHandle(); - } + }; /** * turns on the toggle button (the purple slide button that changes from editing mode and play mode @@ -415,23 +451,22 @@ export class Timeline extends React.Component { this.props.Document.isATOn = !this.props.Document.isATOn; if (!BoolCast(this.props.Document.isATOn)) { //turning on playmode... - roundToggle.style.transform = "translate(0px, 0px)"; - roundToggle.style.animationName = "turnoff"; - roundToggleContainer.style.animationName = "turnoff"; - roundToggleContainer.style.backgroundColor = "white"; + roundToggle.style.transform = 'translate(0px, 0px)'; + roundToggle.style.animationName = 'turnoff'; + roundToggleContainer.style.animationName = 'turnoff'; + roundToggleContainer.style.backgroundColor = 'white'; timelineContainer.style.top = `${-this._containerHeight}px`; this.toPlay(); } else { //turning on authoring mode... - roundToggle.style.transform = "translate(20px, 0px)"; - roundToggle.style.animationName = "turnon"; - roundToggleContainer.style.animationName = "turnon"; - roundToggleContainer.style.backgroundColor = "#9acedf"; - timelineContainer.style.top = "0px"; + roundToggle.style.transform = 'translate(20px, 0px)'; + roundToggle.style.animationName = 'turnon'; + roundToggleContainer.style.animationName = 'turnon'; + roundToggleContainer.style.backgroundColor = '#9acedf'; + timelineContainer.style.top = '0px'; this.toAuthoring(); } - } - + }; @action.bound changeLengths() { @@ -443,9 +478,9 @@ export class Timeline extends React.Component { // @computed getCurrentTime = () => { - const current = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement); + const current = KeyframeFunc.convertPixelTime(this._currentBarX, 'mili', 'time', this._tickSpacing, this._tickIncrement); return this.toReadTime(current > this._time ? this._time : current); - } + }; @observable private mapOfTracks: (Track | null)[] = []; @@ -465,22 +500,22 @@ export class Timeline extends React.Component { } }); return longestTime; - } + }; @action toAuthoring = () => { this._time = Math.ceil((this.findLongestTime() ?? 1) / 100000) * 100000; - this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement); - } + this._totalLength = KeyframeFunc.convertPixelTime(this._time, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); + }; @action toPlay = () => { this._time = this.findLongestTime(); - this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement); - } + this._totalLength = KeyframeFunc.convertPixelTime(this._time, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); + }; /** - * if you have any question here, just shoot me an email or text. + * if you have any question here, just shoot me an email or text. * basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region) */ render() { @@ -488,23 +523,46 @@ export class Timeline extends React.Component { // change visible and total width return ( -
-
+
+
{this.drawTicks()}
-
+
- {this.children.map(doc => - this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} /> - )} + {this.children.map(doc => ( + this.mapOfTracks.push(ref)} + node={doc} + currentBarX={this._currentBarX} + changeCurrentBarX={this.changeCurrentBarX} + transform={this.props.ScreenToLocalTransform()} + time={this._time} + tickSpacing={this._tickSpacing} + tickIncrement={this._tickIncrement} + collection={this.props.Document} + timelineVisible={true} + /> + ))}
Current: {this.getCurrentTime()}
- {this.children.map(doc =>
{ Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}>

{doc.title}

)} + {this.children.map(doc => ( +
{ + Doc.BrushDoc(doc); + }} + onPointerOut={() => { + Doc.UnBrushDoc(doc); + }}> +

{StrCast(doc.title)}

+
+ ))}
@@ -515,4 +573,4 @@ export class Timeline extends React.Component {
); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 37589974b..7d40cab8c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,37 +1,36 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { CursorProperty } from "csstype"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; -import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils"; -import { Docs, DocUtils } from "../../documents/Documents"; -import { DragManager, dropActionType } from "../../util/DragManager"; -import { SnappingManager } from "../../util/SnappingManager"; -import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { EditableView } from "../EditableView"; -import { LightboxView } from "../LightboxView"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; -import { StyleProp } from "../StyleProvider"; -import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; -import "./CollectionStackingView.scss"; -import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; -import { CollectionSubView } from "./CollectionSubView"; -import { CollectionViewType } from "./CollectionView"; -import { FieldViewProps } from "../nodes/FieldView"; -import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; -const _global = (window /* browser */ || global /* node */) as any; - +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { CursorProperty } from 'csstype'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { Docs, DocUtils } from '../../documents/Documents'; +import { DragManager, dropActionType } from '../../util/DragManager'; +import { SnappingManager } from '../../util/SnappingManager'; +import { Transform } from '../../util/Transform'; +import { undoBatch } from '../../util/UndoManager'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { EditableView } from '../EditableView'; +import { LightboxView } from '../LightboxView'; +import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; +import { StyleProp } from '../StyleProvider'; +import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; +import './CollectionStackingView.scss'; +import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; +import { CollectionSubView } from './CollectionSubView'; +import { CollectionViewType } from './CollectionView'; +import { FieldViewProps } from '../nodes/FieldView'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { chromeHidden?: boolean; @@ -46,27 +45,50 @@ export class CollectionStackingView extends CollectionSubView(); _pivotFieldDisposer?: IReactionDisposer; _autoHeightDisposer?: IReactionDisposer; - _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = []; + _docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = []; _columnStart: number = 0; @observable _heightMap = new Map(); - @observable _cursor: CursorProperty = "grab"; + @observable _cursor: CursorProperty = 'grab'; @observable _scroll = 0; // used to force the document decoration to update when scrolling - @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } - @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } - @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } - @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } - @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } - @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } - @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } - @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } - @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; } - @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } - @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; } + @computed get chromeHidden() { + return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); + } + @computed get columnHeaders() { + return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); + } + @computed get pivotField() { + return StrCast(this.layoutDoc._pivotField); + } + @computed get filteredChildren() { + return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); + } + @computed get headerMargin() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); + } + @computed get xMargin() { + return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, 0.05 * this.props.PanelWidth())); + } + @computed get yMargin() { + return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); + } // 2 * this.gridGap)); } + @computed get gridGap() { + return NumCast(this.layoutDoc._gridGap, 10); + } + @computed get isStackingView() { + return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; + } + @computed get numGroupColumns() { + return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; + } + @computed get showAddAGroup() { + return this.pivotField && !this.chromeHidden; + } @computed get columnWidth() { - return Math.min(this.props.PanelWidth() - 2 * this.xMargin, - this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); + return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); + } + @computed get NodeWidth() { + return this.props.PanelWidth() - this.gridGap; } - @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } constructor(props: any) { super(props); @@ -84,21 +106,23 @@ export class CollectionStackingView extends CollectionSubView this.getDocWidth(d); const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; - return
- {this.getDisplayDoc(d, width)} -
; + return ( +
+ {this.getDisplayDoc(d, width)} +
+ ); }); - } + }; @action setDocHeight = (key: string, sectionHeight: number) => { this._heightMap.set(key, sectionHeight); - } + }; get Sections() { if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map(); if (this.columnHeaders === undefined) { - setTimeout(() => this.layoutDoc._columnHeaders = new List(), 0); + setTimeout(() => (this.layoutDoc._columnHeaders = new List()), 0); return new Map(); } const columnHeaders = Array.from(this.columnHeaders); @@ -114,8 +138,7 @@ export class CollectionStackingView extends CollectionSubView sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`)); if (existingHeader) { fields.get(existingHeader)!.push(d); - } - else { + } else { const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`); fields.set(newSchemaHeader, [d]); columnHeaders.push(newSchemaHeader); @@ -124,13 +147,19 @@ export class CollectionStackingView extends CollectionSubView !fields.get(key)!.length).map(header => { - fields.delete(header); - columnHeaders.splice(columnHeaders.indexOf(header), 1); - changed = true; - }); + Array.from(fields.keys()) + .filter(key => !fields.get(key)!.length) + .map(header => { + fields.delete(header); + columnHeaders.splice(columnHeaders.indexOf(header), 1); + changed = true; + }); } - changed && setTimeout(action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), 0); + changed && + setTimeout( + action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), + 0 + ); return fields; } @@ -140,13 +169,19 @@ export class CollectionStackingView extends CollectionSubView this.pivotField, - () => this.layoutDoc._columnHeaders = new List() + () => (this.layoutDoc._columnHeaders = new List()) + ); + this._autoHeightDisposer = reaction( + () => this.layoutDoc._autoHeight, + autoHeight => + autoHeight && + this.props.setHeight?.( + Math.min( + NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', '')))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0)) + ) + ) ); - this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, - autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), - this.headerMargin + (this.isStackingView ? - Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) : - this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))))); } componentWillUnmount() { @@ -158,45 +193,50 @@ export class CollectionStackingView extends CollectionSubView boolean): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; - } + }; createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; this.createDashEventsTarget(ele!); //so the whole grid is the drop target? - } + }; - @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); } - @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } + @computed get onChildClickHandler() { + return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); + } + @computed get onChildDoubleClickHandler() { + return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); + } addDocTab = (doc: Doc, where: string) => { - if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = new List([doc]); return true; } return this.props.addDocTab(doc, where); - } + }; scrollToBottom = () => { smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); - } + }; focusDocument = (doc: Doc, options?: DocFocusOptions) => { Doc.BrushDoc(doc); let focusSpeed = 0; - const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]); + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName('documentView-node')).find((node: any) => node.id === doc[Id]); if (found) { const top = found.getBoundingClientRect().top; const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); if (Math.floor(localTop[1]) !== 0) { - smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + smoothScroll((focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500), this._mainCont!, localTop[1] + this._mainCont!.scrollTop); } } const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; this.props.focus(this.rootDoc, { - willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) + willZoom: options?.willZoom, + scale: options?.scale, + afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)), }); - } + }; styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { if (property === StyleProp.Opacity && doc) { @@ -208,85 +248,88 @@ export class CollectionStackingView extends CollectionSubView { const docView = fieldProps.DocumentView?.(); - if (docView && ["Enter"].includes(e.key) && e.ctrlKey) { + if (docView && ['Enter'].includes(e.key) && e.ctrlKey) { e.stopPropagation?.(); - const below = !e.altKey && e.key !== "Tab"; + const below = !e.altKey && e.key !== 'Tab'; const layoutKey = StrCast(docView.LayoutFieldKey); const newDoc = Doc.MakeCopy(docView.rootDoc, true); const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; - if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) { newDoc[layoutKey] = docView.rootDoc[layoutKey]; } Doc.GetProto(newDoc).text = undefined; FormattedTextBox.SelectOnLoad = newDoc[Id]; return this.addDocument?.(newDoc); } - } + }; isContentActive = () => this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined; + isChildContentActive = () => (this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : undefined); getDisplayDoc(doc: Doc, width: () => number) { - const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; + const dataDoc = !doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); let dref: Opt; const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); - return dref = r || undefined} - Document={doc} - DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} - renderDepth={this.props.renderDepth + 1} - PanelWidth={width} - PanelHeight={height} - styleProvider={this.styleProvider} - docViewPath={this.props.docViewPath} - fitWidth={this.props.childFitWidth} - isContentActive={this.isChildContentActive} - onKey={this.onKeyDown} - isDocumentActive={this.isContentActive} - LayoutTemplate={this.props.childLayoutTemplate} - LayoutTemplateString={this.props.childLayoutString} - NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox - NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined} - dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined} - dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} - rootSelected={this.rootSelected} - showTitle={this.props.childShowTitle} - dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} - onClick={this.onChildClickHandler} - onDoubleClick={this.onChildDoubleClickHandler} - ScreenToLocalTransform={stackedDocTransform} - focus={this.focusDocument} - docFilters={this.childDocFilters} - hideDecorationTitle={this.props.childHideDecorationTitle?.()} - hideResizeHandles={this.props.childHideResizeHandles?.()} - hideTitle={this.props.childHideTitle?.()} - docRangeFilters={this.childDocRangeFilters} - searchFilterDocs={this.searchFilterDocs} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} - addDocument={this.props.addDocument} - moveDocument={this.props.moveDocument} - removeDocument={this.props.removeDocument} - contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.addDocTab} - bringToFront={returnFalse} - scriptContext={this.props.scriptContext} - pinToPres={this.props.pinToPres} - />; + return ( + (dref = r || undefined)} + Document={doc} + DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} + renderDepth={this.props.renderDepth + 1} + PanelWidth={width} + PanelHeight={height} + styleProvider={this.styleProvider} + docViewPath={this.props.docViewPath} + fitWidth={this.props.childFitWidth} + isContentActive={this.isChildContentActive} + onKey={this.onKeyDown} + isDocumentActive={this.isContentActive} + LayoutTemplate={this.props.childLayoutTemplate} + LayoutTemplateString={this.props.childLayoutString} + NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox + NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || (doc._fitWidth && !Doc.NativeHeight(doc)) ? height : undefined} + dontCenter={this.props.childIgnoreNativeSize ? 'xy' : undefined} + dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)} + rootSelected={this.rootSelected} + showTitle={this.props.childShowTitle} + dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} + onClick={this.onChildClickHandler} + onDoubleClick={this.onChildDoubleClickHandler} + ScreenToLocalTransform={stackedDocTransform} + focus={this.focusDocument} + docFilters={this.childDocFilters} + hideDecorationTitle={this.props.childHideDecorationTitle?.()} + hideResizeHandles={this.props.childHideResizeHandles?.()} + hideTitle={this.props.childHideTitle?.()} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + removeDocument={this.props.removeDocument} + contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.addDocTab} + bringToFront={returnFalse} + scriptContext={this.props.scriptContext} + pinToPres={this.props.pinToPres} + /> + ); } getDocTransform(doc: Doc, dref?: DocumentView) { const y = this._scroll; // required for document decorations to update when the text box container is scrolled const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); - // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off - return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); } getDocWidth(d?: Doc) { if (!d) return 0; @@ -300,37 +343,42 @@ export class CollectionStackingView extends CollectionSubView lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1)); + const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS ? undefined : this.props.DataDoc; + const maxHeight = (lim => (lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); if (nw && nh) { const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid); - return Math.min( - maxHeight, - docWid * nh / nw); + return Math.min(maxHeight, (docWid * nh) / nw); } const childHeight = NumCast(childLayoutDoc._height); - const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; + const panelHeight = childLayoutDoc._fitWidth || this.props.childFitWidth?.(d) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; return Math.min(childHeight, maxHeight, panelHeight); } columnDividerDown = (e: React.PointerEvent) => { - runInAction(() => this._cursor = "grabbing"); - setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction); - } + runInAction(() => (this._cursor = 'grabbing')); + setupMoveUpEvents( + this, + e, + this.onDividerMove, + action(() => (this._cursor = 'grab')), + emptyFunction + ); + }; @action onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => { this.layoutDoc._columnWidth = Math.max(10, this.columnWidth + delta[0]); return false; - } + }; @computed get columnDragger() { - return
- -
; + return ( +
+ +
+ ); } @undoBatch @@ -341,7 +389,10 @@ export class CollectionStackingView extends CollectionSubView { - const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + const pos = cd + .stackedDocTransform() + .inverse() + .transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && (i === this._docXfs.length - 1 || where[1] < pos1[1])) { dropInd = i; @@ -358,21 +409,19 @@ export class CollectionStackingView extends CollectionSubView this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); + const offset = newDocs.reduce((off, ndoc) => (this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off), 0); newDocs.filter(ndoc => docs.indexOf(ndoc) !== -1).forEach(ndoc => docs.splice(docs.indexOf(ndoc), 1)); docs.splice(insertInd - offset, 0, ...newDocs); } } - } - else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { - const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" }); + } else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { + const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, _fitWidth: true, title: 'dropped annotation' }); this.props.addDocument?.(source); - de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed + de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, 'doc annotation', ''); // TODODO this is where in text links get passed e.stopPropagation(); - } - else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + } else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); return false; - } + }; @undoBatch internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) { @@ -390,7 +439,10 @@ export class CollectionStackingView extends CollectionSubView { - const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + const pos = cd + .stackedDocTransform() + .inverse() + .transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) { targInd = i; @@ -406,98 +458,103 @@ export class CollectionStackingView extends CollectionSubView Array.from(this.Sections); refList: any[] = []; sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const key = this.pivotField; - let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined; if (this.pivotField) { const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { type = types[0]; } } - return this.refList.splice(this.refList.indexOf(ref), 1)} - observeHeight={ref => { - if (ref) { - this.refList.push(ref); - this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { - const height = this.headerMargin + - Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), - Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); - if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { - this.props.setHeight?.(height); - } - } - })); - this.observer.observe(ref); - } - }} - addDocument={this.addDocument} - chromeHidden={this.chromeHidden} - columnHeaders={this.columnHeaders} - Document={this.props.Document} - DataDoc={this.props.DataDoc} - renderChildren={this.children} - columnWidth={this.columnWidth} - numGroupColumns={this.numGroupColumns} - gridGap={this.gridGap} - pivotField={this.pivotField} - key={heading?.heading ?? ""} - headings={this.headings} - heading={heading?.heading ?? ""} - headingObject={heading} - docList={docList} - yMargin={this.yMargin} - type={type} - createDropTarget={this.createDashEventsTarget} - screenToLocalTransform={this.props.ScreenToLocalTransform} - />; - } + return ( + this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { + if (ref) { + this.refList.push(ref); + this.observer = new _global.ResizeObserver( + action((entries: any) => { + if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + const height = this.headerMargin + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace('px', ''))))); + if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { + this.props.setHeight?.(height); + } + } + }) + ); + this.observer.observe(ref); + } + }} + addDocument={this.addDocument} + chromeHidden={this.chromeHidden} + columnHeaders={this.columnHeaders} + Document={this.props.Document} + DataDoc={this.props.DataDoc} + renderChildren={this.children} + columnWidth={this.columnWidth} + numGroupColumns={this.numGroupColumns} + gridGap={this.gridGap} + pivotField={this.pivotField} + key={heading?.heading ?? ''} + headings={this.headings} + heading={heading?.heading ?? ''} + headingObject={heading} + docList={docList} + yMargin={this.yMargin} + type={type} + createDropTarget={this.createDashEventsTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + /> + ); + }; sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { const key = this.pivotField; - let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined = undefined; const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { type = types[0]; } - const rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, - Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); - return this.refList.splice(this.refList.indexOf(ref), 1)} - observeHeight={(ref) => { - if (ref) { - this.refList.push(ref); - this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { - const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); - this.props.setHeight?.(this.headerMargin + height); - } - })); - this.observer.observe(ref); - } - }} - key={heading ? heading.heading : ""} - rows={rows} - headings={this.headings} - heading={heading ? heading.heading : ""} - headingObject={heading} - docList={docList} - parent={this} - type={type} - createDropTarget={this.createDashEventsTarget} - screenToLocalTransform={this.props.ScreenToLocalTransform} - setDocHeight={this.setDocHeight} - />; - } + const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))))); + return ( + this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { + if (ref) { + this.refList.push(ref); + this.observer = new _global.ResizeObserver( + action((entries: any) => { + if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0); + this.props.setHeight?.(this.headerMargin + height); + } + }) + ); + this.observer.observe(ref); + } + }} + key={heading ? heading.heading : ''} + rows={rows} + headings={this.headings} + heading={heading ? heading.heading : ''} + headingObject={heading} + docList={docList} + parent={this} + type={type} + createDropTarget={this.createDashEventsTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + setDocHeight={this.setDocHeight} + /> + ); + }; @action addGroup = (value: string) => { @@ -507,25 +564,25 @@ export class CollectionStackingView extends CollectionSubView { - const descending = StrCast(this.layoutDoc._columnsSort) === "descending"; + const descending = StrCast(this.layoutDoc._columnsSort) === 'descending'; const firstEntry = descending ? b : a; const secondEntry = descending ? a : b; return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1; - } + }; onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!e.isPropagationStopped()) { const subItems: ContextMenuProps[] = []; - subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" }); - subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" }); + subItems.push({ description: `${this.layoutDoc._columnsFill ? 'Variable Size' : 'Autosize'} Column`, event: () => (this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill), icon: 'plus' }); + subItems.push({ description: `${this.layoutDoc._autoHeight ? 'Variable Height' : 'Auto Height'}`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); + subItems.push({ description: 'Clear All', event: () => (this.dataDoc.data = new List([])), icon: 'times' }); + ContextMenu.Instance.addItem({ description: 'Options...', subitems: subItems, icon: 'eye' }); } - } + }; @computed get renderedSections() { TraceMobx(); @@ -534,7 +591,7 @@ export class CollectionStackingView extends CollectionSubView this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0)); + return sections.map((section, i) => (this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0))); } @computed get buttonMenu() { @@ -544,85 +601,90 @@ export class CollectionStackingView extends CollectionSubView - 35} - PanelHeight={() => 35} - renderDepth={this.props.renderDepth} - focus={emptyFunction} - styleProvider={this.props.styleProvider} - docViewPath={returnEmptyDoclist} - whenChildContentsActiveChanged={emptyFunction} - bringToFront={emptyFunction} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} - searchFilterDocs={this.props.searchFilterDocs} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - /> -
+ return ( +
+ 35} + PanelHeight={() => 35} + renderDepth={this.props.renderDepth} + focus={emptyFunction} + styleProvider={this.props.styleProvider} + docViewPath={returnEmptyDoclist} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={this.props.docFilters} + docRangeFilters={this.props.docRangeFilters} + searchFilterDocs={this.props.searchFilterDocs} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + /> +
); } } + @computed get nativeWidth() { + return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); + } + @computed get nativeHeight() { + return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); + } - @computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); } - @computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); } - - @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; } + @computed get scaling() { + return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; + } - @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); } + @computed get backgroundEvents() { + return SnappingManager.GetIsDragging(); + } observer: any; render() { TraceMobx(); const editableViewProps = { - GetValue: () => "", + GetValue: () => '', SetValue: this.addGroup, - contents: "+ ADD A GROUP" + contents: '+ ADD A GROUP', }; const buttonMenu = this.rootDoc.buttonMenu; const noviceExplainer = this.rootDoc.explainer; return ( <> - {buttonMenu || noviceExplainer ?
- {buttonMenu ? this.buttonMenu : null} - {Doc.noviceMode && noviceExplainer ? -
- {noviceExplainer} -
- : null - } -
: null} -
-
+ {buttonMenu ? this.buttonMenu : null} + {Doc.noviceMode && noviceExplainer ?
{StrCast(noviceExplainer)}
: null} +
+ ) : null} +
+
this._scroll = e.currentTarget.scrollTop)} + onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} - onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > + onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}> {this.renderedSections} - {!this.showAddAGroup ? (null) : -
+ {!this.showAddAGroup ? null : ( +
-
} +
+ )} {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
- ); } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ba72fb7b9..f5b9162d3 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,5 +1,5 @@ -import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -11,27 +11,27 @@ import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnF import { DocUtils } from '../../documents/Documents'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from "../../util/DragManager"; +import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { EditableView } from "../EditableView"; +import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from './collectionFreeForm'; -import { CollectionSubView } from "./CollectionSubView"; -import "./CollectionTreeView.scss"; -import { TreeView } from "./TreeView"; -import React = require("react"); -import { FieldViewProps } from "../nodes/FieldView"; -const _global = (window /* browser */ || global /* node */) as any; +import { CollectionSubView } from './CollectionSubView'; +import './CollectionTreeView.scss'; +import { TreeView } from './TreeView'; +import React = require('react'); +import { FieldViewProps } from '../nodes/FieldView'; +const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { - treeViewExpandedView?: "fields" | "layout" | "links" | "data"; + treeViewExpandedView?: 'fields' | 'layout' | 'links' | 'data'; treeViewOpen?: boolean; treeViewHideTitle?: boolean; treeViewHideHeaderFields?: boolean; @@ -45,9 +45,9 @@ export type collectionTreeViewProps = { }; export enum TreeViewType { - outline = "outline", - fileSystem = "fileSystem", - default = "default" + outline = 'outline', + fileSystem = 'fileSystem', + default = 'default', } @observer @@ -61,13 +61,28 @@ export class CollectionTreeView extends CollectionSubView this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); - isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.ActiveTool !== InkTool.None || - (this.props.isContentActive?.() || this.props.Document.forceActive || - this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || - this.props.rootSelected(outsideReaction)) ? true : false) + whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); + isContentActive = (outsideReaction?: boolean) => + CurrentUserUtils.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) + ? true + : false; componentWillUnmount() { this._isDisposing = true; @@ -89,47 +104,51 @@ export class CollectionTreeView extends CollectionSubView this.rootDoc.autoHeight, + this._disposers.autoheight = reaction( + () => this.rootDoc.autoHeight, auto => auto && this.computeHeight(), - { fireImmediately: true }); + { fireImmediately: true } + ); } computeHeight = () => { if (!this._isDisposing) { - const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace("px", "")); - const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot()); + const titleHeight = !this._titleRef ? this.marginTop() : Number(getComputedStyle(this._titleRef).height.replace('px', '')); + const bodyHeight = Array.from(this.refList).reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), this.marginBot()); this.layoutDoc._autoHeightMargins = bodyHeight; this.props.setHeight?.(bodyHeight + titleHeight); } - } + }; unobserveHeight = (ref: any) => { this.refList.delete(ref); this.rootDoc.autoHeight && this.computeHeight(); - } + }; observeHeight = (ref: any) => { if (ref) { this.refList.add(ref); - this.observer = new _global.ResizeObserver(action((entries: any) => { - if (this.rootDoc.autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) { - this.computeHeight(); - } - })); + this.observer = new _global.ResizeObserver( + action((entries: any) => { + if (this.rootDoc.autoHeight && ref && this.refList.size && !SnappingManager.GetIsDragging()) { + this.computeHeight(); + } + }) + ); this.rootDoc.autoHeight && this.computeHeight(); this.observer.observe(ref); } - } + }; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - if (this._mainEle = ele) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); - } + if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); + }; protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData) { const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d)); - dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction; + dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? 'same' : dragData.dropAction; } - } + }; @action remove = (doc: Doc | Doc[]): boolean => { @@ -149,7 +168,7 @@ export class CollectionTreeView extends CollectionSubView, before?: boolean): boolean => { @@ -161,81 +180,85 @@ export class CollectionTreeView extends CollectionSubView { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!Doc.noviceMode) { const layoutItems: ContextMenuProps[] = []; - layoutItems.push({ description: "Make tree state " + (this.doc.treeViewOpenIsTransient ? "persistent" : "transient"), event: () => this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient, icon: "paint-brush" }); - layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" }); - layoutItems.push({ description: (this.doc.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle, icon: "paint-brush" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" }); - const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); - const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - onClicks.push({ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" }); - !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); + layoutItems.push({ description: 'Make tree state ' + (this.doc.treeViewOpenIsTransient ? 'persistent' : 'transient'), event: () => (this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient), icon: 'paint-brush' }); + layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields), icon: 'paint-brush' }); + layoutItems.push({ description: (this.doc.treeViewHideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle), icon: 'paint-brush' }); + ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); + const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...'); + const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; + onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' }); + !existingOnClick && ContextMenu.Instance.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } - } + }; onTreeDrop = (e: React.DragEvent, addDocs?: (docs: Doc[]) => void) => this.onExternalDrop(e, {}, addDocs); @undoBatch makeTextCollection = (childDocs: Doc[]) => { this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true); - } + }; get editableTitle() { - return StrCast(this.dataDoc.title)} - SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren); - this.dataDoc.title = value; - return true; - })} />; + return ( + StrCast(this.dataDoc.title)} + SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { + if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren); + this.dataDoc.title = value; + return true; + })} + /> + ); } - onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { - if (this.outlineMode && e.key === "Enter") { + if (this.outlineMode && e.key === 'Enter') { e.stopPropagation(); this.makeTextCollection(this.treeChildren); return true; } - } + }; get documentTitle() { - return ; + return ( + + ); } childContextMenuItems = () => { const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []); const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []); const icons = StrListCast(this.doc.childContextMenuIcons); return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); - } + }; @computed get treeViewElements() { TraceMobx(); const dropAction = StrCast(this.doc.childDropAction) as dropActionType; @@ -266,36 +289,34 @@ export class CollectionTreeView extends CollectionSubView this._titleRef = r}> + return this.dataDoc === null ? null : ( +
(this._titleRef = r)}> {this.outlineMode ? this.documentTitle : this.editableTitle} -
; +
+ ); } @computed get noviceExplainer() { - return !Doc.noviceMode || !this.rootDoc.explainer ? (null) : -
{this.rootDoc.explainer}
; + return !Doc.noviceMode || !this.rootDoc.explainer ? null :
{StrCast(this.rootDoc.explainer)}
; } return35 = () => 35; @computed get buttonMenu() { const menuDoc = Cast(this.rootDoc.buttonMenuDoc, Doc, null); // To create a multibutton menu add a CollectionLinearView - return !menuDoc ? null : - (
+ return !menuDoc ? null : ( +
-
); +
+ ); } - @computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); } - @computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); } + @computed get nativeWidth() { + return Doc.NativeWidth(this.Document, undefined, true); + } + @computed get nativeHeight() { + return Doc.NativeHeight(this.Document, undefined, true); + } @computed get contentScaling() { const nw = this.nativeWidth; @@ -347,68 +373,73 @@ export class CollectionTreeView extends CollectionSubView this.props.CollectionView?.addDocument(doc, `${this.props.fieldKey}-annotations`) || false; remAnnotationDocument = (doc: Doc | Doc[]) => this.props.CollectionView?.removeDocument(doc, `${this.props.fieldKey}-annotations`) || false; moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => - this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false + this.props.CollectionView?.moveDocument(doc, targetCollection, addDocument, `${this.props.fieldKey}-annotations`) || false; contentFunc = () => { const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); - const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined; - const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? (null) : this.titleBar; + const pointerEvents = () => (!this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'none' : undefined); + const titleBar = this.props.treeViewHideTitle || this.doc.treeViewHideTitle ? null : this.titleBar; return [ -
+
{titleBar} -
- {!this.buttonMenu && !this.noviceExplainer ? (null) : + {!this.buttonMenu && !this.noviceExplainer ? null : (
r && (this._explainerHeight = r.getBoundingClientRect().height))}> {this.buttonMenu} {this.noviceExplainer}
- } -
e.stopPropagation()} onDrop={this.onTreeDrop} ref={r => !this.doc.treeViewHasOverlay && r && this.createTreeDropTarget(r)}> -
    - {this.treeViewElements} -
-
+
    {this.treeViewElements}
+
-
+
, ]; - } + }; render() { TraceMobx(); - return !(this.doc instanceof Doc) || !this.treeChildren ? (null) : - this.doc.treeViewHasOverlay ? - - {this.contentFunc} - : - this.contentFunc(); + return !(this.doc instanceof Doc) || !this.treeChildren ? null : this.doc.treeViewHasOverlay ? ( + + {this.contentFunc} + + ) : ( + this.contentFunc() + ); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 2ae0c01ef..d788f9a77 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,5 +1,5 @@ import { computed, observable, runInAction } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import * as React from 'react'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app import { Doc, DocListCast } from '../../../fields/Doc'; @@ -14,66 +14,65 @@ import { BranchCreate, BranchTask } from '../../documents/Gitlike'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { InteractionUtils } from '../../util/InteractionUtils'; -import { ContextMenu } from "../ContextMenu"; +import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; -import { CollectionDockingView } from "./CollectionDockingView"; +import { CollectionDockingView } from './CollectionDockingView'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { CollectionGridView } from './collectionGrid/CollectionGridView'; import { CollectionLinearView } from './collectionLinear'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionPileView } from './CollectionPileView'; -import { CollectionSchemaView } from "./collectionSchema/CollectionSchemaView"; +import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; import { CollectionStackingView } from './CollectionStackingView'; import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; -import { CollectionTreeView } from "./CollectionTreeView"; +import { CollectionTreeView } from './CollectionTreeView'; import './CollectionView.scss'; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); export enum CollectionViewType { - Invalid = "invalid", - Freeform = "freeform", - Schema = "schema", - Docking = "docking", + Invalid = 'invalid', + Freeform = 'freeform', + Schema = 'schema', + Docking = 'docking', Tree = 'tree', - Stacking = "stacking", - Masonry = "masonry", - Multicolumn = "multicolumn", - Multirow = "multirow", - Time = "time", - Carousel = "carousel", - Carousel3D = "3D Carousel", - Linear = "linear", + Stacking = 'stacking', + Masonry = 'masonry', + Multicolumn = 'multicolumn', + Multirow = 'multirow', + Time = 'time', + Carousel = 'carousel', + Carousel3D = '3D Carousel', + Linear = 'linear', //Staff = "staff", - Map = "map", - Grid = "grid", - Pile = "pileup", - StackedTimeline = "stacked timeline" + Map = 'map', + Grid = 'grid', + Pile = 'pileup', + StackedTimeline = 'stacked timeline', } -export interface CollectionViewProps extends FieldViewProps { - isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) +interface CollectionViewProps_ extends FieldViewProps { + isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) layoutEngine?: () => string; setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void; // property overrides for child documents - children?: never | (() => JSX.Element[]) | React.ReactNode; childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox) - childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) + childDocumentsActive?: () => boolean; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode) childFitWidth?: (child: Doc) => boolean; childShowTitle?: () => string; childOpacity?: () => number; - childContextMenuItems?: () => { script: ScriptField, label: string }[]; + childContextMenuItems?: () => { script: ScriptField; label: string }[]; childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; - childLayoutTemplate?: () => (Doc | undefined);// specify a layout Doc template to use for children of the collection + childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection childLayoutString?: string; childIgnoreNativeSize?: boolean; childClickScript?: ScriptField; @@ -81,20 +80,25 @@ export interface CollectionViewProps extends FieldViewProps { //TODO: [AL] add these fields AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[]; - hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) + hierarchyIndex?: number[]; // hierarchical index of a document up to the rendering root (primarily used for tree views) } +export interface CollectionViewProps extends React.PropsWithChildren {} @observer export class CollectionView extends ViewBoxAnnotatableComponent() { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(CollectionView, fieldStr); + } @observable private static _safeMode = false; - public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } + public static SetSafeMode(safeMode: boolean) { + this._safeMode = safeMode; + } protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; constructor(props: any) { super(props); - runInAction(() => this._annotationKeySuffix = returnEmptyString); + runInAction(() => (this._annotationKeySuffix = returnEmptyString)); } get collectionViewType(): CollectionViewType | undefined { @@ -102,15 +106,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent { - return (null); + return null; // this section would display an icon in the bototm right of a collection to indicate that all // photos had been processed through Google's content analysis API and Google's tags had been // assigned to the documents googlePhotosTags field. @@ -119,136 +125,171 @@ export class CollectionView extends ViewBoxAnnotatableComponent 0 && imageProtos.every(image => image.googlePhotosTags); // return !allTagged ? (null) : ; //this.isContentActive(); - } + }; - screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()); + screenToLocalTransform = () => (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth())); private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => { TraceMobx(); if (type === undefined) return null; switch (type) { default: - case CollectionViewType.Freeform: return ; - case CollectionViewType.Schema: return ; - case CollectionViewType.Docking: return ; - case CollectionViewType.Tree: return ; - case CollectionViewType.Multicolumn: return ; - case CollectionViewType.Multirow: return ; - case CollectionViewType.Linear: return ; - case CollectionViewType.Pile: return ; - case CollectionViewType.Carousel: return ; - case CollectionViewType.Carousel3D: return ; - case CollectionViewType.Stacking: return ; - case CollectionViewType.Masonry: return ; - case CollectionViewType.Time: return ; - case CollectionViewType.Grid: return ; + case CollectionViewType.Freeform: + return ; + case CollectionViewType.Schema: + return ; + case CollectionViewType.Docking: + return ; + case CollectionViewType.Tree: + return ; + case CollectionViewType.Multicolumn: + return ; + case CollectionViewType.Multirow: + return ; + case CollectionViewType.Linear: + return ; + case CollectionViewType.Pile: + return ; + case CollectionViewType.Carousel: + return ; + case CollectionViewType.Carousel3D: + return ; + case CollectionViewType.Stacking: + return ; + case CollectionViewType.Masonry: + return ; + case CollectionViewType.Time: + return ; + case CollectionViewType.Grid: + return ; //case CollectionViewType.Staff: return ; } - } + }; setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) { const subItems: ContextMenuProps[] = []; - subItems.push({ description: "Freeform", event: () => func(CollectionViewType.Freeform), icon: "signature" }); + subItems.push({ description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' }); if (addExtras && CollectionView._safeMode) { - ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => func(CollectionViewType.Invalid), icon: "project-diagram" }); + ContextMenu.Instance.addItem({ description: 'Test Freeform', event: () => func(CollectionViewType.Invalid), icon: 'project-diagram' }); } - subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" }); - subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" }); - subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" }); - subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" }); - subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" }); - subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" }); - subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" }); - subItems.push({ description: "3D Carousel", event: () => func(CollectionViewType.Carousel3D), icon: "columns" }); - !Doc.noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); - !Doc.noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); - subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" }); + subItems.push({ description: 'Schema', event: () => func(CollectionViewType.Schema), icon: 'th-list' }); + subItems.push({ description: 'Tree', event: () => func(CollectionViewType.Tree), icon: 'tree' }); + subItems.push({ description: 'Stacking', event: () => (func(CollectionViewType.Stacking)._autoHeight = true), icon: 'ellipsis-v' }); + subItems.push({ description: 'Multicolumn', event: () => func(CollectionViewType.Multicolumn), icon: 'columns' }); + subItems.push({ description: 'Multirow', event: () => func(CollectionViewType.Multirow), icon: 'columns' }); + subItems.push({ description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' }); + subItems.push({ description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' }); + subItems.push({ description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' }); + !Doc.noviceMode && subItems.push({ description: 'Pivot/Time', event: () => func(CollectionViewType.Time), icon: 'columns' }); + !Doc.noviceMode && subItems.push({ description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' }); + subItems.push({ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' }); if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { const existingVm = ContextMenu.Instance.findByDescription(category); - const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : []; - catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" }); - !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: "eye" }); + const catItems = existingVm && 'subitems' in existingVm ? existingVm.subitems : []; + catItems.push({ description: 'Add a Perspective...', addDivider: true, noexpand: true, subitems: subItems, icon: 'eye' }); + !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: catItems, icon: 'eye' }); } } onContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; if (e.nativeEvent.cancelBubble) return; // nested calls to React to render can cause the same event to trigger in the outer view even if the inner view has handled it. This avoid CollectionDockingView menu options from being added when the event has been handled by a sub-document. - if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - this.setupViewTypes("UI Controls...", vtype => { - const newRendition = Doc.MakeAlias(this.rootDoc); - newRendition._viewType = vtype; - this.props.addDocTab(newRendition, "add:right"); - return newRendition; - }, false); + if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { + // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + this.setupViewTypes( + 'UI Controls...', + vtype => { + const newRendition = Doc.MakeAlias(this.rootDoc); + newRendition._viewType = vtype; + this.props.addDocTab(newRendition, 'add:right'); + return newRendition; + }, + false + ); - const options = cm.findByDescription("Options..."); - const optionItems = options && "subitems" in options ? options.subitems : []; - !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null; + const options = cm.findByDescription('Options...'); + const optionItems = options && 'subitems' in options ? options.subitems : []; + !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? 'Select' : 'Force'} Contents Active`, event: () => (this.rootDoc.forceActive = !this.rootDoc.forceActive), icon: 'project-diagram' }) : null; if (this.rootDoc.childLayout instanceof Doc) { - optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" }); + optionItems.push({ description: 'View Child Layout', event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, 'add:right'), icon: 'project-diagram' }); } if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" }); + optionItems.push({ description: 'View Child Detailed Layout', event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, 'add:right'), icon: 'project-diagram' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" }); + !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? 'Unset' : 'Set'} inPlace Container`, event: () => (this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer), icon: 'project-diagram' }); if (!Doc.noviceMode) { optionItems.push({ - description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram" + description: 'Create Branch', + event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), 'add:right'), + icon: 'project-diagram', }); optionItems.push({ - description: "Pull Master", event: () => BranchTask(this.rootDoc, "pull"), icon: "project-diagram" + description: 'Pull Master', + event: () => BranchTask(this.rootDoc, 'pull'), + icon: 'project-diagram', }); optionItems.push({ - description: "Merge Branches", event: () => BranchTask(this.rootDoc, "merge"), icon: "project-diagram" + description: 'Merge Branches', + event: () => BranchTask(this.rootDoc, 'merge'), + icon: 'project-diagram', }); } if (this.Document._viewType === CollectionViewType.Docking) { - optionItems.push({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(), icon: "project-diagram" }); + optionItems.push({ description: 'Create Dashboard', event: () => CurrentUserUtils.createNewDashboard(), icon: 'project-diagram' }); } - !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" }); + !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); if (!Doc.noviceMode && !this.rootDoc.annotationOn) { - const existingOnClick = cm.findByDescription("OnClick..."); - const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; - const funcs = [{ key: "onChildClick", name: "On Child Clicked" }, { key: "onChildDoubleClick", name: "On Child Double Clicked" }]; - funcs.map(func => onClicks.push({ - description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => { - const alias = Doc.MakeAlias(this.rootDoc); - DocUtils.makeCustomViewClicked(alias, undefined, func.key); - this.props.addDocTab(alias, "add:right"); - } - })); - DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick => + const existingOnClick = cm.findByDescription('OnClick...'); + const onClicks = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; + const funcs = [ + { key: 'onChildClick', name: 'On Child Clicked' }, + { key: 'onChildDoubleClick', name: 'On Child Double Clicked' }, + ]; + funcs.map(func => + onClicks.push({ + description: `Edit ${func.name} script`, + icon: 'edit', + event: (obj: any) => { + const alias = Doc.MakeAlias(this.rootDoc); + DocUtils.makeCustomViewClicked(alias, undefined, func.key); + this.props.addDocTab(alias, 'add:right'); + }, + }) + ); + DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null).data).forEach(childClick => onClicks.push({ description: `Set child ${childClick.title}`, - icon: "edit", - event: () => Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), - })); - !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); + icon: 'edit', + event: () => (Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data))), + }) + ); + !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } if (!Doc.noviceMode) { - const more = cm.findByDescription("More..."); - const moreItems = more && "subitems" in more ? more.subitems : []; - moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); - !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); + const more = cm.findByDescription('More...'); + const moreItems = more && 'subitems' in more ? more.subitems : []; + moreItems.push({ description: 'Export Image Hierarchy', icon: 'columns', event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); + !more && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'hand-point-right' }); } } - } + }; bodyPanelWidth = () => this.props.PanelWidth(); childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles); childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); - @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); } + @computed get childLayoutString() { + return StrCast(this.rootDoc.childLayoutString); + } isContentActive = (outsideReaction?: boolean) => { return this.props.isContentActive(); - } + }; render() { TraceMobx(); const props: SubCollectionViewProps = { @@ -268,10 +309,11 @@ export class CollectionView extends ViewBoxAnnotatableComponent - {this.showIsTagged()} - {this.renderSubView(this.collectionViewType, props)} -
); + return ( +
+ {this.showIsTagged()} + {this.renderSubView(this.collectionViewType, props)} +
+ ); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index dacbb3508..8720c9097 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,25 +1,23 @@ -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import { Id } from "../../../../fields/FieldSymbols"; -import { DocumentManager } from "../../../util/DocumentManager"; -import "./CollectionFreeFormLinksView.scss"; -import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; -import React = require("react"); +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { Id } from '../../../../fields/FieldSymbols'; +import { DocumentManager } from '../../../util/DocumentManager'; +import './CollectionFreeFormLinksView.scss'; +import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; +import React = require('react'); @observer -export class CollectionFreeFormLinksView extends React.Component { +export class CollectionFreeFormLinksView extends React.Component> { @computed get uniqueConnections() { - return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c => - - ); + return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c => ); } render() { - return
- - {this.uniqueConnections} - - {this.props.children} -
; + return ( +
+ {this.uniqueConnections} + {this.props.children} +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx index 3d2ed0cf9..3d1d87aa0 100644 --- a/src/client/views/collections/collectionGrid/Grid.tsx +++ b/src/client/views/collections/collectionGrid/Grid.tsx @@ -1,14 +1,13 @@ import * as React from 'react'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; -import "../../../../../node_modules/react-grid-layout/css/styles.css"; -import "../../../../../node_modules/react-resizable/css/styles.css"; +import '../../../../../node_modules/react-grid-layout/css/styles.css'; +import '../../../../../node_modules/react-resizable/css/styles.css'; import * as GridLayout from 'react-grid-layout'; import { Layout } from 'react-grid-layout'; export { Layout } from 'react-grid-layout'; - interface GridProps { width: number; nodeList: JSX.Element[] | null; @@ -29,9 +28,10 @@ interface GridProps { @observer export default class Grid extends React.Component { render() { - const compactType = this.props.compactType === "vertical" || this.props.compactType === "horizontal" ? this.props.compactType : null; + const compactType = this.props.compactType === 'vertical' || this.props.compactType === 'horizontal' ? this.props.compactType : null; return ( - { useCSSTransforms={true} onLayoutChange={this.props.setLayout} preventCollision={this.props.preventCollision} - transformScale={1 / this.props.transformScale} // still doesn't work :( - margin={[this.props.margin, this.props.margin]} - > + transformScale={1 / this.props.transformScale} // still doesn't work :( ?? + margin={[this.props.margin, this.props.margin]}> {this.props.nodeList} ); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index c31267e87..8adfdc70b 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -9,7 +9,7 @@ import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; import { DocUtils } from '../../../documents/Documents'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; -import { DocumentManager } from "../../../util/DocumentManager"; +import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { Colors, Shadows } from '../../global/globalEnums'; @@ -21,13 +21,12 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionStackedTimeline } from '../CollectionStackedTimeline'; import { CollectionSubView } from '../CollectionSubView'; import { CollectionViewType } from '../CollectionView'; -import "./CollectionLinearView.scss"; - +import './CollectionLinearView.scss'; /** - * CollectionLinearView is the class for rendering the horizontal collection + * CollectionLinearView is the class for rendering the horizontal collection * of documents, it useful for horizontal menus. It can either be expandable - * or not using the linearViewExpandable field. + * or not using the linearViewExpandable field. * It is used in the following locations: * - It is used in the popup menu on the bottom left (see docButtons() in MainView.tsx) * - It is used for the context sensitive toolbar at the top (see contMenuButtons() in CollectionMenu.tsx) @@ -48,45 +47,47 @@ export class CollectionLinearView extends CollectionSubView() { } componentDidMount() { - this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10), + this._widthDisposer = reaction( + () => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * this.rootDoc[HeightSym]() : 10), width => this.childDocs.length && (this.layoutDoc._width = width), { fireImmediately: true } ); this._selectedDisposer = reaction( () => NumCast(this.layoutDoc.selectedIndex), - (i) => runInAction(() => { - this._selectedIndex = i; - let selected: any = undefined; - this.childLayoutPairs.map(async (pair, ind) => { - const isSelected = this._selectedIndex === ind; - if (isSelected) { - selected = pair; - } - else { - ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); + i => + runInAction(() => { + this._selectedIndex = i; + let selected: any = undefined; + this.childLayoutPairs.map(async (pair, ind) => { + const isSelected = this._selectedIndex === ind; + if (isSelected) { + selected = pair; + } else { + ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log); + } + }); + if (selected && selected.layout) { + ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); } - }); - if (selected && selected.layout) { - ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log); - } - }), + }), { fireImmediately: true } ); } - protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } - } + }; dimension = () => NumCast(this.rootDoc._height); // 2 * the padding getTransform = (ele: Opt) => { if (!ele) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ele); return new Transform(-translateX, -translateY, 1); - } + }; @action exitLongLinks = () => { @@ -99,28 +100,27 @@ export class CollectionLinearView extends CollectionSubView() { } DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; - } + }; @action changeDescriptionSetting = () => { if (LinkDescriptionPopup.showDescriptions) { - if (LinkDescriptionPopup.showDescriptions === "ON") { - LinkDescriptionPopup.showDescriptions = "OFF"; + if (LinkDescriptionPopup.showDescriptions === 'ON') { + LinkDescriptionPopup.showDescriptions = 'OFF'; LinkDescriptionPopup.descriptionPopup = false; } else { - LinkDescriptionPopup.showDescriptions = "ON"; + LinkDescriptionPopup.showDescriptions = 'ON'; } } else { - LinkDescriptionPopup.showDescriptions = "OFF"; + LinkDescriptionPopup.showDescriptions = 'OFF'; LinkDescriptionPopup.descriptionPopup = false; } - } + }; myContextMenu = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); - } - + }; getDisplayDoc = (doc: Doc, preview: boolean = false) => { const nested = doc._viewType === CollectionViewType.Linear; @@ -129,44 +129,50 @@ export class CollectionLinearView extends CollectionSubView() { let dref: Opt; const docXf = () => this.getTransform(dref); // const scalable = pair.layout.onClick || pair.layout.onDragStart; - return hidden ? (null) :
dref = r || undefined} - style={{ - pointerEvents: "all", - width: nested ? undefined : NumCast(doc._width), - height: nested ? undefined : NumCast(doc._height), - marginLeft: !nested ? 2.5 : 0, - marginRight: !nested ? 2.5 : 0, - // width: NumCast(pair.layout._width), - // height: NumCast(pair.layout._height), - }} > - -
; - } + return hidden ? null : ( +
(dref = r || undefined)} + style={{ + pointerEvents: 'all', + width: nested ? undefined : NumCast(doc._width), + height: nested ? undefined : NumCast(doc._height), + marginLeft: !nested ? 2.5 : 0, + marginRight: !nested ? 2.5 : 0, + // width: NumCast(pair.layout._width), + // height: NumCast(pair.layout._height), + }}> + +
+ ); + }; render() { const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label @@ -178,66 +184,99 @@ export class CollectionLinearView extends CollectionSubView() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle - const menuOpener = ; - - return
-
- {!expandable ? (null) :
{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close" : "Open"}
} placement="top"> - {menuOpener} -
} - this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> - -
- {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} -
- {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : - e.stopPropagation()} > - - Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'} - + const menuOpener = ( + + ); -
{"Toggle description pop-up"}
} placement="top"> - - Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"} - + return ( +
+
+ {!expandable ? null : ( + +
{BoolCast(this.props.Document.linearViewIsExpanded) ? 'Close' : 'Open'}
+ + } + placement="top"> + {menuOpener}
+ )} + (this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked))} + /> -
Exit linking mode
} placement="top"> - - Stop +
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} +
+ {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : ( + e.stopPropagation()}> + + Creating link from:{' '} + + {(DocumentLinksButton.AnnotationId ? 'Annotation in ' : ' ') + + (StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...')} + -
- } - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? (null) : - - - Currently playing: - {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => - DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> - {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")} - )} - - } + +
{'Toggle description pop-up'}
+ + } + placement="top"> + + Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : 'ON'} + +
+ + +
Exit linking mode
+ + } + placement="top"> + + Stop + +
+ + )} + {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : ( + + + Currently playing: + {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( + DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> + {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? '' : ',')} + + ))} + + + )} +
-
; + ); } -} \ No newline at end of file +} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 1414bfdf7..ba301962b 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -1,15 +1,14 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, NumListCast, StrListCast, Field } from "../../../fields/Doc"; -import { DateCast, StrCast, Cast } from "../../../fields/Types"; -import { LinkManager } from "../../util/LinkManager"; -import { undoBatch } from "../../util/UndoManager"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, NumListCast, StrListCast, Field } from '../../../fields/Doc'; +import { DateCast, StrCast, Cast } from '../../../fields/Types'; +import { LinkManager } from '../../util/LinkManager'; +import { undoBatch } from '../../util/UndoManager'; import './LinkEditor.scss'; -import { LinkRelationshipSearch } from "./LinkRelationshipSearch"; -import React = require("react"); - +import { LinkRelationshipSearch } from './LinkRelationshipSearch'; +import React = require('react'); interface LinkEditorProps { sourceDoc: Doc; @@ -19,16 +18,20 @@ interface LinkEditorProps { } @observer export class LinkEditor extends React.Component { - @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); @observable zoomFollow = StrCast(this.props.sourceDoc.followLinkZoom); @observable openDropdown: boolean = false; @observable showInfo: boolean = false; - @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } - @observable private buttonColor: string = ""; - @observable private relationshipButtonColor: string = ""; - @observable private relationshipSearchVisibility: string = "none"; + @computed get infoIcon() { + if (this.showInfo) { + return 'chevron-up'; + } + return 'chevron-down'; + } + @observable private buttonColor: string = ''; + @observable private relationshipButtonColor: string = ''; + @observable private relationshipSearchVisibility: string = 'none'; @observable private searchIsActive: boolean = false; //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; @@ -37,7 +40,7 @@ export class LinkEditor extends React.Component { deleteLink = (): void => { LinkManager.Instance.deleteLink(this.props.linkDoc); this.props.showLinks(); - } + }; @undoBatch setRelationshipValue = action((value: string) => { @@ -49,11 +52,11 @@ export class LinkEditor extends React.Component { const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); - // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color + // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (!linkRelationshipList?.includes(value)) { linkRelationshipList.push(value); linkRelationshipSizes.push(1); - const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; + const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; linkColorList.push(randColor); // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes } else if (linkRelationshipList && value !== prevRelationship) { @@ -61,20 +64,22 @@ export class LinkEditor extends React.Component { //increment size of new relationship size if (index !== -1 && index < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[index]; - linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1); + linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1; } //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) if (linkRelationshipList.includes(prevRelationship)) { const pindex = linkRelationshipList.indexOf(prevRelationship); if (pindex !== -1 && pindex < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[pindex]; - linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1)); + linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1); } } - } - this.relationshipButtonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.relationshipButtonColor = ""), 750); + this.relationshipButtonColor = 'rgb(62, 133, 55)'; + setTimeout( + action(() => (this.relationshipButtonColor = '')), + 750 + ); return true; } }); @@ -89,144 +94,143 @@ export class LinkEditor extends React.Component { if (linkRelationshipList) { return linkRelationshipList.filter(rel => rel.includes(query)); } - } + }; /** * toggles visibility of the relationship search results when the input field is focused on */ @action toggleRelationshipResults = () => { - this.relationshipSearchVisibility = this.relationshipSearchVisibility === "none" ? "block" : "none"; - } + this.relationshipSearchVisibility = this.relationshipSearchVisibility === 'none' ? 'block' : 'none'; + }; @undoBatch setDescripValue = action((value: string) => { if (LinkManager.currentLink) { Doc.GetProto(LinkManager.currentLink).description = value; - this.buttonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.buttonColor = ""), 750); + this.buttonColor = 'rgb(62, 133, 55)'; + setTimeout( + action(() => (this.buttonColor = '')), + 750 + ); return true; } }); onDescriptionKey = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setDescripValue(this.description); document.getElementById('input')?.blur(); } e.stopPropagation(); - } + }; onRelationshipKey = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setRelationshipValue(this.relationship); document.getElementById('input')?.blur(); } e.stopPropagation(); - } + }; onDescriptionDown = () => this.setDescripValue(this.description); onRelationshipDown = () => this.setRelationshipValue(this.relationship); onBlur = () => { - //only hide the search results if the user clicks out of the input AND not on any of the search results + //only hide the search results if the user clicks out of the input AND not on any of the search results // i.e. if search is not active if (!this.searchIsActive) { this.toggleRelationshipResults(); } - } + }; onFocus = () => { this.toggleRelationshipResults(); - } + }; toggleSearchIsActive = () => { this.searchIsActive = !this.searchIsActive; - } + }; @action - handleDescriptionChange = (e: React.ChangeEvent) => { this.description = e.target.value; } + handleDescriptionChange = (e: React.ChangeEvent) => { + this.description = e.target.value; + }; @action - handleRelationshipChange = (e: React.ChangeEvent) => { this.relationship = e.target.value; } + handleRelationshipChange = (e: React.ChangeEvent) => { + this.relationship = e.target.value; + }; @action - handleZoomFollowChange = (e: React.ChangeEvent) => { this.props.sourceDoc.followLinkZoom = e.target.checked; } + handleZoomFollowChange = (e: React.ChangeEvent) => { + this.props.sourceDoc.followLinkZoom = e.target.checked; + }; @action handleRelationshipSearchChange = (result: string) => { this.setRelationshipValue(result); this.toggleRelationshipResults(); this.relationship = result; - } + }; @computed get editRelationship() { //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return
-
Link Relationship:
-
-
- - + return ( +
+
Link Relationship:
+
+
+ + +
+
+ Set +
-
Set
-
; + ); } @computed get editZoomFollow() { //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - return
-
Zoom To Link Target:
-
-
- + return ( +
+
Zoom To Link Target:
+
+
+ +
-
; + ); } @computed get editDescription() { - return
-
Link Description:
-
-
- + return ( +
+
Link Description:
+
+
+ +
+
+ Set +
-
Set
-
; + ); } @action - changeDropdown = () => { this.openDropdown = !this.openDropdown; } + changeDropdown = () => { + this.openDropdown = !this.openDropdown; + }; @undoBatch changeFollowBehavior = action((follow: string) => { @@ -236,94 +240,114 @@ export class LinkEditor extends React.Component { @computed get followingDropdown() { - return
-
Follow Behavior:
-
-
- {StrCast(this.props.linkDoc.followLinkLocation, "default")} - -
-
-
this.changeFollowBehavior("default")}> - Default + return ( +
+
Follow Behavior:
+
+
+ {StrCast(this.props.linkDoc.followLinkLocation, 'default')} +
-
this.changeFollowBehavior("add:left")}> - Always open in new left pane -
-
this.changeFollowBehavior("add:right")}> - Always open in new right pane -
-
this.changeFollowBehavior("replace:right")}> - Always replace right tab -
-
this.changeFollowBehavior("replace:left")}> - Always replace left tab -
-
this.changeFollowBehavior("fullScreen")}> - Always open full screen -
-
this.changeFollowBehavior("add")}> - Always open in a new tab -
-
this.changeFollowBehavior("replace")}> - Replace Tab -
- {this.props.linkDoc.linksToAnnotation ? -
this.changeFollowBehavior("openExternal")}> - Always open in external page +
+
this.changeFollowBehavior('default')}> + Default +
+
this.changeFollowBehavior('add:left')}> + Always open in new left pane +
+
this.changeFollowBehavior('add:right')}> + Always open in new right pane +
+
this.changeFollowBehavior('replace:right')}> + Always replace right tab +
+
this.changeFollowBehavior('replace:left')}> + Always replace left tab
- : null} +
this.changeFollowBehavior('fullScreen')}> + Always open full screen +
+
this.changeFollowBehavior('add')}> + Always open in a new tab +
+
this.changeFollowBehavior('replace')}> + Replace Tab +
+ {this.props.linkDoc.linksToAnnotation ? ( +
this.changeFollowBehavior('openExternal')}> + Always open in external page +
+ ) : null} +
-
; + ); } @action - changeInfo = () => { this.showInfo = !this.showInfo; } + changeInfo = () => { + this.showInfo = !this.showInfo; + }; render() { const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - return !destination ? (null) : ( + return !destination ? null : (
e.stopPropagation()}>
-
Return to link menu
} placement="top"> - + +
Return to link menu
+ + } + placement="top"> +
-

Editing Link to: { - destination.proto?.title ?? destination.title ?? "untitled"}

-
Show more link information
} placement="top"> -
+

+ Editing Link to: {StrCast(destination.proto?.title, StrCast(destination.title, 'untitled'))} +

+ +
Show more link information
+ + } + placement="top"> +
+ +
- {this.showInfo ?
-
{this.props.linkDoc.author ?
Author: {this.props.linkDoc.author}
: null}
-
{this.props.linkDoc.creationDate ?
Creation Date: - {DateCast(this.props.linkDoc.creationDate).toString()}
: null}
-
: null} + {this.showInfo ? ( +
+
+ {this.props.linkDoc.author ? ( +
+ {' '} + Author: {StrCast(this.props.linkDoc.author)} +
+ ) : null} +
+
+ {this.props.linkDoc.creationDate ? ( +
+ {' '} + Creation Date: + {DateCast(this.props.linkDoc.creationDate).toString()} +
+ ) : null} +
+
+ ) : null} {this.editDescription} {this.editRelationship} {this.editZoomFollow} {this.followingDropdown}
- ); } -} \ No newline at end of file +} diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index a75e7a0c4..1e7f4f10b 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, observable } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { Cast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; @@ -16,8 +16,7 @@ import { undoBatch } from '../../util/UndoManager'; import { DocumentView } from '../nodes/DocumentView'; import { LinkDocPreview } from '../nodes/LinkDocPreview'; import './LinkMenuItem.scss'; -import React = require("react"); - +import React = require('react'); interface LinkMenuItemProps { groupType: string; @@ -50,22 +49,21 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume }; const containingView = docView.props.ContainingCollectionView; const finishDrag = (e: DragManager.DragCompleteEvent) => - e.docDragData && (e.docDragData.droppedDocuments = - dragData.draggedDocuments.reduce((droppedDocs, d) => { - const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); - if (dvs.length) { - dvs.forEach(dv => droppedDocs.push(dv.props.Document)); - } else { - droppedDocs.push(Doc.MakeAlias(d)); - } - return droppedDocs; - }, [] as Doc[])); + e.docDragData && + (e.docDragData.droppedDocuments = dragData.draggedDocuments.reduce((droppedDocs, d) => { + const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); + if (dvs.length) { + dvs.forEach(dv => droppedDocs.push(dv.props.Document)); + } else { + droppedDocs.push(Doc.MakeAlias(d)); + } + return droppedDocs; + }, [] as Doc[])); DragManager.StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag); } } - @observer export class LinkMenuItem extends React.Component { private _drag = React.createRef(); @@ -74,20 +72,31 @@ export class LinkMenuItem extends React.Component { _buttonRef = React.createRef(); @observable private _showMore: boolean = false; - @action toggleShowMore(e: React.PointerEvent) { e.stopPropagation(); this._showMore = !this._showMore; } + @action toggleShowMore(e: React.PointerEvent) { + e.stopPropagation(); + this._showMore = !this._showMore; + } onEdit = (e: React.PointerEvent): void => { LinkManager.currentLink = this.props.linkDoc; - setupMoveUpEvents(this, e, e => { - const dragData = new DragManager.DocumentDragData([this.props.linkDoc], "alias"); - dragData.removeDropProperties = ["hidden"]; - DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y); - return true; - }, emptyFunction, () => this.props.showEditor(this.props.linkDoc)); - } + setupMoveUpEvents( + this, + e, + e => { + const dragData = new DragManager.DocumentDragData([this.props.linkDoc], 'alias'); + dragData.removeDropProperties = ['hidden']; + DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y); + return true; + }, + emptyFunction, + () => this.props.showEditor(this.props.linkDoc) + ); + }; onLinkButtonDown = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, + setupMoveUpEvents( + this, + e, e => { const eleClone: any = this._drag.current!.cloneNode(true); eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`; @@ -99,109 +108,170 @@ export class LinkMenuItem extends React.Component { () => { this.props.clearLinkEditor(); if (this.props.itemHandler) { - this.props.itemHandler?.(this.props.linkDoc); } else { - const focusDoc = Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor1, Doc, null) : - Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor12, Doc, null) : undefined; + const focusDoc = + Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc + ? Cast(this.props.linkDoc.anchor1, Doc, null) + : Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc + ? Cast(this.props.linkDoc.anchor12, Doc, null) + : undefined; if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, true); LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false); } - }); - } + } + ); + }; deleteLink = (e: React.PointerEvent): void => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => { - this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); - LinkManager.Instance.deleteLink(this.props.linkDoc); - }))); - } + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch( + action(() => { + this.props.linkDoc.linksToAnnotation && Hypothesis.deleteLink(this.props.linkDoc, this.props.sourceDoc, this.props.destinationDoc); + LinkManager.Instance.deleteLink(this.props.linkDoc); + }) + ) + ); + }; autoMove = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove))); - } + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkAutoMove = !this.props.linkDoc.linkAutoMove)))); + }; showLink = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay))); - } + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.linkDisplay = !this.props.linkDoc.linkDisplay)))); + }; showAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.props.linkDoc.hidden = !this.props.linkDoc.hidden))); - } + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => (this.props.linkDoc.hidden = !this.props.linkDoc.hidden)))); + }; render() { const destinationIcon = Doc.toIcon(this.props.destinationDoc) as any as IconProp; - const title = StrCast(this.props.destinationDoc.title).length > 18 ? - StrCast(this.props.destinationDoc.title).substr(0, 14) + "..." : this.props.destinationDoc.title; + const title = StrCast(this.props.destinationDoc.title).length > 18 ? StrCast(this.props.destinationDoc.title).substr(0, 14) + '...' : this.props.destinationDoc.title; // ... // from anika to bob: here's where the text that is specifically linked would show up (linkDoc.storedText) // ... - const source = this.props.sourceDoc.type === DocumentType.RTF ? this.props.linkDoc.storedText ? - StrCast(this.props.linkDoc.storedText).length > 17 ? - StrCast(this.props.linkDoc.storedText).substr(0, 18) - : this.props.linkDoc.storedText : undefined : undefined; + const source = + this.props.sourceDoc.type === DocumentType.RTF + ? this.props.linkDoc.storedText + ? StrCast(this.props.linkDoc.storedText).length > 17 + ? StrCast(this.props.linkDoc.storedText).substr(0, 18) + : this.props.linkDoc.storedText + : undefined + : undefined; return (
-
- -
+
this.props.linkDoc && LinkDocPreview.SetLinkInfo({ - docProps: this.props.docView.props, - linkSrc: this.props.sourceDoc, - linkDoc: this.props.linkDoc, - showHeader: false, - location: [e.clientX, e.clientY + 20] - })} + onPointerEnter={e => + this.props.linkDoc && + LinkDocPreview.SetLinkInfo({ + docProps: this.props.docView.props, + linkSrc: this.props.sourceDoc, + linkDoc: this.props.linkDoc, + showHeader: false, + location: [e.clientX, e.clientY + 20], + }) + } onPointerDown={this.onLinkButtonDown}> -
- {source ?

Source: {source}

: null} + {source ? ( +

+ {' '} + Source: {StrCast(source)} +

+ ) : null}
-
+

- {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? "Annotation in" : ""} {title} + {this.props.linkDoc.linksToAnnotation && Cast(this.props.destinationDoc.data, WebField)?.url.href === this.props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}

- {!this.props.linkDoc.description ? (null) :

{StrCast(this.props.linkDoc.description)}

} + {!this.props.linkDoc.description ? null :

{StrCast(this.props.linkDoc.description)}

}
-
- -
{this.props.linkDoc.hidden ? "Show Link Anchor" : "Hide Link Anchor"}
}> -
e.stopPropagation()}> -
+
+ +
{this.props.linkDoc.hidden ? 'Show Link Anchor' : 'Hide Link Anchor'}
+ + }> +
e.stopPropagation()}> + +
-
{this.props.linkDoc.linkDisplay ? "Hide Link Line" : "Show Link Line"}
}> -
e.stopPropagation()}> -
+ +
{this.props.linkDoc.linkDisplay ? 'Hide Link Line' : 'Show Link Line'}
+ + }> +
e.stopPropagation()}> + +
-
{this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}
}> -
e.stopPropagation()}> -
+ +
{this.props.linkDoc.linkAutoMove ? 'Click to freeze link anchor position' : 'Click to auto move link anchor'}
+ + }> +
e.stopPropagation()}> + +
-
Edit Link
}> + +
Edit Link
+ + }>
e.stopPropagation()}> -
+ +
-
Delete Link
}> + +
Delete Link
+ + }>
e.stopPropagation()}> -
+ +
- -
+
); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 96ac3e332..f1d8123da 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,48 +1,48 @@ -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import { AclPrivate, Doc, Opt } from "../../../fields/Doc"; -import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, StrCast } from "../../../fields/Types"; -import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; -import { emptyPath, OmitKeys, Without } from "../../../Utils"; -import { DirectoryImportBox } from "../../util/Import & Export/DirectoryImportBox"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { CollectionSchemaView } from "../collections/collectionSchema/CollectionSchemaView"; -import { CollectionView } from "../collections/CollectionView"; -import { InkingStroke } from "../InkingStroke"; -import { PresElementBox } from "../nodes/trails/PresElementBox"; -import { SearchBox } from "../search/SearchBox"; -import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; -import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; -import { AudioBox } from "./AudioBox"; -import { FontIconBox } from "./button/FontIconBox"; -import { ColorBox } from "./ColorBox"; -import { ComparisonBox } from "./ComparisonBox"; -import { DataVizBox } from "./DataVizBox/DataVizBox"; -import { DocumentViewProps } from "./DocumentView"; -import "./DocumentView.scss"; -import { EquationBox } from "./EquationBox"; -import { FieldView, FieldViewProps } from "./FieldView"; -import { FilterBox } from "./FilterBox"; -import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox"; -import { FunctionPlotBox } from "./FunctionPlotBox"; -import { ImageBox } from "./ImageBox"; -import { KeyValueBox } from "./KeyValueBox"; -import { LabelBox } from "./LabelBox"; -import { LinkAnchorBox } from "./LinkAnchorBox"; -import { LinkBox } from "./LinkBox"; -import { MapBox } from "./MapBox/MapBox"; -import { PDFBox } from "./PDFBox"; -import { RecordingBox } from "./RecordingBox"; -import { ScreenshotBox } from "./ScreenshotBox"; -import { ScriptingBox } from "./ScriptingBox"; -import { SliderBox } from "./SliderBox"; -import { PresBox } from "./trails/PresBox"; -import { VideoBox } from "./VideoBox"; -import { WebBox } from "./WebBox"; -import React = require("react"); -import XRegExp = require("xregexp"); +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { AclPrivate, Doc, Opt } from '../../../fields/Doc'; +import { ScriptField } from '../../../fields/ScriptField'; +import { Cast, StrCast } from '../../../fields/Types'; +import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; +import { emptyPath, OmitKeys, Without } from '../../../Utils'; +import { DirectoryImportBox } from '../../util/Import & Export/DirectoryImportBox'; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView'; +import { CollectionView } from '../collections/CollectionView'; +import { InkingStroke } from '../InkingStroke'; +import { PresElementBox } from '../nodes/trails/PresElementBox'; +import { SearchBox } from '../search/SearchBox'; +import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; +import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; +import { AudioBox } from './AudioBox'; +import { FontIconBox } from './button/FontIconBox'; +import { ColorBox } from './ColorBox'; +import { ComparisonBox } from './ComparisonBox'; +import { DataVizBox } from './DataVizBox/DataVizBox'; +import { DocumentViewProps } from './DocumentView'; +import './DocumentView.scss'; +import { EquationBox } from './EquationBox'; +import { FieldView, FieldViewProps } from './FieldView'; +import { FilterBox } from './FilterBox'; +import { FormattedTextBox, FormattedTextBoxProps } from './formattedText/FormattedTextBox'; +import { FunctionPlotBox } from './FunctionPlotBox'; +import { ImageBox } from './ImageBox'; +import { KeyValueBox } from './KeyValueBox'; +import { LabelBox } from './LabelBox'; +import { LinkAnchorBox } from './LinkAnchorBox'; +import { LinkBox } from './LinkBox'; +import { MapBox } from './MapBox/MapBox'; +import { PDFBox } from './PDFBox'; +import { RecordingBox } from './RecordingBox'; +import { ScreenshotBox } from './ScreenshotBox'; +import { ScriptingBox } from './ScriptingBox'; +import { SliderBox } from './SliderBox'; +import { PresBox } from './trails/PresBox'; +import { VideoBox } from './VideoBox'; +import { WebBox } from './WebBox'; +import React = require('react'); +import XRegExp = require('xregexp'); const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -60,7 +60,6 @@ class ObserverJsxParser1 extends JsxParser { const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; - interface HTMLtagProps { Document: Doc; RootDoc: Doc; @@ -68,16 +67,17 @@ interface HTMLtagProps { onClick?: ScriptField; onInput?: ScriptField; scaling: number; + children?: JSX.Element[]; } //" {this.title}" -//" // -// // {this.title} // @@ -87,45 +87,51 @@ export class HTMLtag extends React.Component { click = (e: React.MouseEvent) => { const clickScript = (this.props as any).onClick as Opt; clickScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, scale: this.props.scaling }); - } + }; onInput = (e: React.FormEvent) => { const onInputScript = (this.props as any).onInput as Opt; onInputScript?.script.run({ this: this.props.Document, self: this.props.RootDoc, value: (e.target as any).textContent }); - } + }; render() { const style: { [key: string]: any } = {}; - const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "scaling", "Document", "key", "onInput", "onClick", "__proto__"]).omit; - const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value - return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string || ""; + const divKeys = OmitKeys(this.props, ['children', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; + const replacer = (match: any, expr: string, offset: any, string: any) => { + // bcz: this executes a script to convert a propery expression string: { script } into a value + return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || ''; }; Object.keys(divKeys).map((prop: string) => { const p = (this.props as any)[prop] as string; style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer); }); const Tag = this.props.htmltag as keyof JSX.IntrinsicElements; - return - {this.props.children} - ; + return ( + + {this.props.children} + + ); } } @observer -export class DocumentContentsView extends React.Component boolean, - select: (ctrl: boolean) => void, - scaling?: () => number, - setHeight?: (height: number) => void, - layoutKey: string, -}> { +export class DocumentContentsView extends React.Component< + DocumentViewProps & + FormattedTextBoxProps & { + isSelected: (outsideReaction: boolean) => boolean; + select: (ctrl: boolean) => void; + scaling?: () => number; + setHeight?: (height: number) => void; + layoutKey: string; + } +> { @computed get layout(): string { TraceMobx(); if (this.props.LayoutTemplateString) return this.props.LayoutTemplateString; - if (!this.layoutDoc) return "

awaiting layout

"; - if (this.props.layoutKey === "layout_keyValue") return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString("data")); - const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, "layout")], "string"); - if (layout === undefined) return this.props.Document.data ? "" : KeyValueBox.LayoutString(this.layoutDoc.proto ? "proto" : ""); - if (typeof layout === "string") return layout; - return "

Loading layout

"; + if (!this.layoutDoc) return '

awaiting layout

'; + if (this.props.layoutKey === 'layout_keyValue') return StrCast(this.props.Document.layout_keyValue, KeyValueBox.LayoutString('data')); + const layout = Cast(this.layoutDoc[this.layoutDoc === this.props.Document && this.props.layoutKey ? this.props.layoutKey : StrCast(this.layoutDoc.layoutKey, 'layout')], 'string'); + if (layout === undefined) return this.props.Document.data ? "" : KeyValueBox.LayoutString(this.layoutDoc.proto ? 'proto' : ''); + if (typeof layout === 'string') return layout; + return '

Loading layout

'; } get dataDoc() { @@ -136,36 +142,39 @@ export class DocumentContentsView extends React.Component, onInput: Opt): JsxBindings { - const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews - "hideResizeHandles", - "hideTitle", - "treeViewDoc", - "contentPointerEvents", - "radialMenu", - "LayoutTemplateString", - "LayoutTemplate", - "dontCenter", - "ContentScaling", - "contextMenuItems", - "onClick", - "onDoubleClick", - "onPointerDown", - "onPointerUp", + const docOnlyProps = [ + // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews + 'hideResizeHandles', + 'hideTitle', + 'treeViewDoc', + 'contentPointerEvents', + 'radialMenu', + 'LayoutTemplateString', + 'LayoutTemplate', + 'dontCenter', + 'ContentScaling', + 'contextMenuItems', + 'onClick', + 'onDoubleClick', + 'onPointerDown', + 'onPointerUp', ]; const list = { - ...OmitKeys(this.props, [...docOnlyProps], "").omit, + ...OmitKeys(this.props, [...docOnlyProps], '').omit, RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc, Document: this.layoutDoc, DataDoc: this.dataDoc, onClick: onClick, - onInput: onInput + onInput: onInput, }; return { props: list }; } @@ -180,17 +189,17 @@ export class DocumentContentsView extends React.Component{content}< as in {this.title} const replacer = (match: any, prefix: string, expr: string, postfix: string, offset: any, string: any) => { - return prefix + (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string || "") + postfix; + return prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this.props.Document }).result as string) || '') + postfix; }; layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer); - // replace HTML with corresponding HTML tag as in: becomes + // replace HTML with corresponding HTML tag as in: becomes const replacer2 = (match: any, p1: string, offset: any, string: any) => { return ` with as in: becomes + // replace /HTML with
as in: becomes const replacer3 = (match: any, p1: string, offset: any, string: any) => { return ` { const splits = layoutFrame.split(`${func}=`); if (splits.length > 1) { - const code = XRegExp.matchRecursive(splits[1], "{", "}", "", { valueNames: ["between", "left", "match", "right", "between"] }); + const code = XRegExp.matchRecursive(splits[1], '{', '}', '', { valueNames: ['between', 'left', 'match', 'right', 'between'] }); layoutFrame = splits[0] + ` ${func}={props.${func}} ` + splits[1].substring(code[1].end + 1); - const script = code[1].value.replace(/^‘/, "").replace(/’$/, ""); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts - return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: "number", value: "string" }); + const script = code[1].value.replace(/^‘/, '').replace(/’$/, ''); // ‘’ are not valid quotes in javascript so get rid of them -- they may be present to make it easier to write complex scripts - see headerTemplate in currentUserUtils.ts + return ScriptField.MakeScript(script, { this: Doc.name, self: Doc.name, scale: 'number', value: 'string' }); } return undefined; // add input function to props }; - const onClick = makeFuncProp("onClick"); - const onInput = makeFuncProp("onInput"); + const onClick = makeFuncProp('onClick'); + const onInput = makeFuncProp('onInput'); const bindings = this.CreateBindings(onClick, onInput); return { bindings, layoutFrame }; } @@ -218,23 +227,55 @@ export class DocumentContentsView extends React.Component 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate) ? (null) : + return this.props.renderDepth > 12 || !layoutFrame || !this.layoutDoc || GetEffectiveAcl(this.layoutDoc) === AclPrivate ? null : ( { console.log("DocumentContentsView:" + test); }} - />; + onError={(test: any) => { + console.log('DocumentContentsView:' + test); + }} + /> + ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 6c7e174f7..47d5c662e 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -1,19 +1,19 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; -import { observer } from "mobx-react"; -import wiki from "wikijs"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from "../../../fields/Doc"; -import { NumCast, StrCast, Cast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from "../../../Utils"; +import { observer } from 'mobx-react'; +import wiki from 'wikijs'; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocCastAsync } from '../../../fields/Doc'; +import { NumCast, StrCast, Cast } from '../../../fields/Types'; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; -import { Docs, DocUtils } from "../../documents/Documents"; +import { Docs, DocUtils } from '../../documents/Documents'; import { LinkManager } from '../../util/LinkManager'; -import { Transform } from "../../util/Transform"; +import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; -import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; +import { DocumentView, DocumentViewSharedProps } from './DocumentView'; import './LinkDocPreview.scss'; -import React = require("react"); +import React = require('react'); import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; @@ -27,15 +27,19 @@ interface LinkDocPreviewProps { } @observer export class LinkDocPreview extends React.Component { - @action public static Clear() { LinkDocPreview.LinkInfo = undefined; } - @action public static SetLinkInfo(info?: LinkDocPreviewProps) { LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); } + @action public static Clear() { + LinkDocPreview.LinkInfo = undefined; + } + @action public static SetLinkInfo(info?: LinkDocPreviewProps) { + LinkDocPreview.LinkInfo !== info && (LinkDocPreview.LinkInfo = info); + } _infoRef = React.createRef(); @observable public static LinkInfo: Opt; @observable _targetDoc: Opt; @observable _linkDoc: Opt; @observable _linkSrc: Opt; - @observable _toolTipText = ""; + @observable _toolTipText = ''; @observable _hrefInd = 0; @action init() { @@ -47,113 +51,136 @@ export class LinkDocPreview extends React.Component { if (anchor1 && anchor2) { linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1; } - if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { // want to show annotation context document if annotation is not text - linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno)); + if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { + // want to show annotation context document if annotation is not text + linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._targetDoc = anno))); } else { this._targetDoc = linkTarget; } - this._toolTipText = ""; + this._toolTipText = ''; } componentDidUpdate(props: any) { if (props.linkSrc !== this.props.linkSrc || props.linkDoc !== this.props.linkDoc || props.hrefs !== this.props.hrefs) this.init(); } componentDidMount() { this.init(); - document.addEventListener("pointerdown", this.onPointerDown); + document.addEventListener('pointerdown', this.onPointerDown); } componentWillUnmount() { LinkDocPreview.SetLinkInfo(undefined); - document.removeEventListener("pointerdown", this.onPointerDown); + document.removeEventListener('pointerdown', this.onPointerDown); } onPointerDown = (e: PointerEvent) => { !this._infoRef.current?.contains(e.target as any) && LinkDocPreview.Clear(); // close preview when not clicking anywhere other than the info bar of the preview - } + }; @computed get href() { if (this.props.hrefs?.length) { const href = this.props.hrefs[this._hrefInd]; - if (href.indexOf(Doc.localServerPath()) !== 0) { // link to a web page URL -- try to show a preview - if (href.startsWith("https://en.wikipedia.org/wiki/")) { - wiki().page(href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500)))); + if (href.indexOf(Doc.localServerPath()) !== 0) { + // link to a web page URL -- try to show a preview + if (href.startsWith('https://en.wikipedia.org/wiki/')) { + wiki() + .page(href.replace('https://en.wikipedia.org/wiki/', '')) + .then(page => page.summary().then(action(summary => (this._toolTipText = summary.substring(0, 500))))); } else { - setTimeout(action(() => this._toolTipText = "url => " + href)); + setTimeout(action(() => (this._toolTipText = 'url => ' + href))); } - } else { // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated - const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0]; - anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => { - if (anchor instanceof Doc && DocListCast(anchor.links).length) { - this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0]; - const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords; - if (automaticLink) { // automatic links specify the target in the link info, not the source - const linkTarget = anchor; - this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); - this._targetDoc = linkTarget; - } else { - this._linkSrc = anchor; - const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; - } - this._toolTipText = ""; - } - })); + } else { + // hyperlink to a document .. decode doc id and retrieve from the server. this will trigger vals() being invalidated + const anchorDoc = href.replace(Doc.localServerPath(), '').split('?')[0]; + anchorDoc && + DocServer.GetRefField(anchorDoc).then( + action(anchor => { + if (anchor instanceof Doc && DocListCast(anchor.links).length) { + this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0]; + const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords; + if (automaticLink) { + // automatic links specify the target in the link info, not the source + const linkTarget = anchor; + this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); + this._targetDoc = linkTarget; + } else { + this._linkSrc = anchor; + const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; + } + this._toolTipText = ''; + } + }) + ); } return href; } return undefined; } deleteLink = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc))); - } + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)) + ); + }; nextHref = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => { - const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1); - if (nextHrefInd !== this._hrefInd) { - this._linkDoc = undefined; - this._hrefInd = nextHrefInd; - } - }), true); - } + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + action(() => { + const nextHrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1); + if (nextHrefInd !== this._hrefInd) { + this._linkDoc = undefined; + this._hrefInd = nextHrefInd; + } + }), + true + ); + }; followLink = (e: React.PointerEvent) => { if (this._linkDoc && this._linkSrc) { LinkDocPreview.Clear(); LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false); } else if (this.props.hrefs?.length) { - this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), "add:right"); + this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, useCors: true }), 'add:right'); } - } + }; width = () => { if (!this._targetDoc) return 225; if (this._targetDoc[WidthSym]() < this._targetDoc?.[HeightSym]()) { - return Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]() / this._targetDoc[HeightSym](); + return (Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]()) / this._targetDoc[HeightSym](); } return Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)); - } + }; height = () => { if (!this._targetDoc) return 225; if (this._targetDoc[WidthSym]() > this._targetDoc?.[HeightSym]()) { - return Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]() / this._targetDoc[WidthSym](); + return (Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]()) / this._targetDoc[WidthSym](); } return Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); - } + }; @computed get previewHeader() { - return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) : + return !this._linkDoc || !this._targetDoc || !this._linkSrc ? null : (
-
{ - DragManager.StartDocumentDrag([this._infoRef.current!], - new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); + DragManager.StartDocumentDrag([this._infoRef.current!], new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); e.stopPropagation(); LinkDocPreview.Clear(); }}> - {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title} + {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + '...' : StrCast(this._targetDoc.title)}

{StrCast(this._linkDoc.description)}

-
+
Next Link
} placement="top"> -
+
@@ -164,20 +191,24 @@ export class LinkDocPreview extends React.Component {
-
; +
+ ); } @computed get docPreview() { const href = this.href; // needs to be here to trigger lookup of web pages and docs on server - return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) : + return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : (
- {!this.props.showHeader ? (null) : this.previewHeader} -
- {this._toolTipText ? this._toolTipText : - { - const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); - targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor); - }} + {!this.props.showHeader ? null : this.previewHeader} +
+ {this._toolTipText ? ( + this._toolTipText + ) : ( + { + const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor); + }} Document={this._targetDoc!} moveDocument={returnFalse} rootSelected={returnFalse} @@ -206,16 +237,19 @@ export class LinkDocPreview extends React.Component { bringToFront={returnFalse} NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined} NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined} - />} + /> + )}
-
; +
+ ); } render() { const borders = 16; // 8px border on each side - return
- {this.docPreview} -
; + return ( +
+ {this.docPreview} +
+ ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 0b7264d79..18a6b5453 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -1,8 +1,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-maps/api'; import { action, computed, IReactionDisposer, observable, ObservableMap } from 'mobx'; -import { observer } from "mobx-react"; -import * as React from "react"; +import { observer } from 'mobx-react'; +import * as React from 'react'; import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; @@ -22,17 +22,17 @@ import { Annotation } from '../../pdf/Annotation'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; import { FieldView, FieldViewProps } from '../FieldView'; -import "./MapBox.scss"; +import './MapBox.scss'; import { MapBoxInfoWindow } from './MapBoxInfoWindow'; /** * MapBox architecture: * Main component: MapBox.tsx * Supporting Components: SidebarAnnos, CollectionStackingView - * - * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content. - * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view. - * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available, + * + * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content. + * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view. + * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available, * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map). * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts). * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps @@ -77,26 +77,30 @@ document.head.appendChild(script); // }, // }); - // options for searchbox in Google Maps Places Autocomplete API const options = { - fields: ["formatted_address", "geometry", "name"], // note: level of details is charged by item per retrieval, not recommended to return all fields + fields: ['formatted_address', 'geometry', 'name'], // note: level of details is charged by item per retrieval, not recommended to return all fields strictBounds: false, - types: ["establishment"], // type pf places, subject of change according to user need + types: ['establishment'], // type pf places, subject of change according to user need } as google.maps.places.AutocompleteOptions; @observer export class MapBox extends ViewBoxAnnotatableComponent>() { - private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _annotationLayer: React.RefObject = React.createRef(); @observable private _overlayAnnoInfo: Opt; - showInfo = action((anno: Opt) => this._overlayAnnoInfo = anno); - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBox, fieldKey); } - public get SidebarKey() { return this.fieldKey + "-sidebar"; } + showInfo = action((anno: Opt) => (this._overlayAnnoInfo = anno)); + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(MapBox, fieldKey); + } + public get SidebarKey() { + return this.fieldKey + '-sidebar'; + } private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); - @computed get inlineTextAnnotations() { return this.allMapMarkers.filter(a => a.textInlineAnnotations); } + @computed get inlineTextAnnotations() { + return this.allMapMarkers.filter(a => a.textInlineAnnotations); + } @observable private _map: google.maps.Map = null as unknown as google.maps.Map; @observable private selectedPlace: Doc | undefined; @@ -108,14 +112,19 @@ export class MapBox extends ViewBoxAnnotatableComponent(); - @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } - @computed get allMapMarkers() { return DocListCast(this.dataDoc[this.annotationKey]); } + @computed get allSidebarDocs() { + return DocListCast(this.dataDoc[this.SidebarKey]); + } + @computed get allMapMarkers() { + return DocListCast(this.dataDoc[this.annotationKey]); + } @observable private toggleAddMarker = false; private _mainCont: React.RefObject = React.createRef(); - @observable _showSidebar = false; - @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } + @computed get SidebarShown() { + return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + } static _canAnnotate = true; static _hadSelection: boolean = false; @@ -129,109 +138,106 @@ export class MapBox extends ViewBoxAnnotatableComponent { this.searchBox = searchBox; - } + }; // iterate allMarkers to size, center, and zoom map to contain all markers private fitBounds = (map: google.maps.Map) => { const curBounds = map.getBounds() ?? new window.google.maps.LatLngBounds(); - const isFitting = this.allMapMarkers.reduce((fits, place) => - fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean); - !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => - bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), - new window.google.maps.LatLngBounds())); - } + const isFitting = this.allMapMarkers.reduce((fits, place) => fits && curBounds?.contains({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), true as boolean); + !isFitting && map.fitBounds(this.allMapMarkers.reduce((bounds, place) => bounds.extend({ lat: NumCast(place.lat), lng: NumCast(place.lng) }), new window.google.maps.LatLngBounds())); + }; /** * Custom control for add marker button - * @param controlDiv - * @param map + * @param controlDiv + * @param map */ private CenterControl = () => { - const controlDiv = document.createElement("div"); - controlDiv.className = "mapBox-addMarker"; + const controlDiv = document.createElement('div'); + controlDiv.className = 'mapBox-addMarker'; // Set CSS for the control border. - const controlUI = document.createElement("div"); - controlUI.style.backgroundColor = "#fff"; - controlUI.style.borderRadius = "3px"; - controlUI.style.cursor = "pointer"; - controlUI.style.marginTop = "10px"; - controlUI.style.borderRadius = "4px"; - controlUI.style.marginBottom = "22px"; - controlUI.style.textAlign = "center"; - controlUI.style.position = "absolute"; - controlUI.style.width = "32px"; - controlUI.style.height = "32px"; - controlUI.title = "Click to toggle marker mode. In marker mode, click on map to place a marker."; - - const plIcon = document.createElement("img"); - plIcon.src = "https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png"; - plIcon.style.color = "rgb(25,25,25)"; - plIcon.style.fontFamily = "Roboto,Arial,sans-serif"; - plIcon.style.fontSize = "16px"; - plIcon.style.lineHeight = "32px"; - plIcon.style.left = "18"; - plIcon.style.top = "15"; - plIcon.style.position = "absolute"; + const controlUI = document.createElement('div'); + controlUI.style.backgroundColor = '#fff'; + controlUI.style.borderRadius = '3px'; + controlUI.style.cursor = 'pointer'; + controlUI.style.marginTop = '10px'; + controlUI.style.borderRadius = '4px'; + controlUI.style.marginBottom = '22px'; + controlUI.style.textAlign = 'center'; + controlUI.style.position = 'absolute'; + controlUI.style.width = '32px'; + controlUI.style.height = '32px'; + controlUI.title = 'Click to toggle marker mode. In marker mode, click on map to place a marker.'; + + const plIcon = document.createElement('img'); + plIcon.src = 'https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/add-256.png'; + plIcon.style.color = 'rgb(25,25,25)'; + plIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; + plIcon.style.fontSize = '16px'; + plIcon.style.lineHeight = '32px'; + plIcon.style.left = '18'; + plIcon.style.top = '15'; + plIcon.style.position = 'absolute'; plIcon.width = 14; plIcon.height = 14; - plIcon.innerHTML = "Add"; + plIcon.innerHTML = 'Add'; controlUI.appendChild(plIcon); // Set CSS for the control interior. - const markerIcon = document.createElement("img"); - markerIcon.src = "https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png"; - markerIcon.style.color = "rgb(25,25,25)"; - markerIcon.style.fontFamily = "Roboto,Arial,sans-serif"; - markerIcon.style.fontSize = "16px"; - markerIcon.style.lineHeight = "32px"; - markerIcon.style.left = "-2"; - markerIcon.style.top = "1"; + const markerIcon = document.createElement('img'); + markerIcon.src = 'https://cdn0.iconfinder.com/data/icons/small-n-flat/24/678111-map-marker-1024.png'; + markerIcon.style.color = 'rgb(25,25,25)'; + markerIcon.style.fontFamily = 'Roboto,Arial,sans-serif'; + markerIcon.style.fontSize = '16px'; + markerIcon.style.lineHeight = '32px'; + markerIcon.style.left = '-2'; + markerIcon.style.top = '1'; markerIcon.width = 30; markerIcon.height = 30; - markerIcon.style.position = "absolute"; - markerIcon.innerHTML = "Add"; + markerIcon.style.position = 'absolute'; + markerIcon.innerHTML = 'Add'; controlUI.appendChild(markerIcon); // Setup the click event listeners - controlUI.addEventListener("click", () => { + controlUI.addEventListener('click', () => { if (this.toggleAddMarker === true) { this.toggleAddMarker = false; - console.log("add marker button status:" + this.toggleAddMarker); - controlUI.style.backgroundColor = "#fff"; - markerIcon.style.color = "rgb(25,25,25)"; + console.log('add marker button status:' + this.toggleAddMarker); + controlUI.style.backgroundColor = '#fff'; + markerIcon.style.color = 'rgb(25,25,25)'; } else { this.toggleAddMarker = true; - console.log("add marker button status:" + this.toggleAddMarker); - controlUI.style.backgroundColor = "#4476f7"; - markerIcon.style.color = "rgb(255,255,255)"; + console.log('add marker button status:' + this.toggleAddMarker); + controlUI.style.backgroundColor = '#4476f7'; + markerIcon.style.color = 'rgb(255,255,255)'; } }); controlDiv.appendChild(controlUI); return controlDiv; - } + }; /** * Place the marker on google maps & store the empty marker as a MapMarker Document in allMarkers list * @param position - the LatLng position where the marker is placed - * @param map + * @param map */ @action private placeMarker = (position: google.maps.LatLng, map: google.maps.Map) => { const marker = new google.maps.Marker({ position: position, - map: map + map: map, }); map.panTo(position); const mapMarker = Docs.Create.MapMarkerDocument(NumCast(position.lat()), NumCast(position.lng()), false, [], {}); this.addDocument(mapMarker, this.annotationKey); - } + }; _loadPending = true; /** * store a reference to google map instance * setup the drawing manager on the top right corner of map - * fit map bounds to contain all markers - * @param map + * fit map bounds to contain all markers + * @param map */ @action private loadHandler = (map: google.maps.Map) => { @@ -256,7 +262,6 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (this._loadPending && this._map.getBounds()) { this._loadPending = false; this.layoutDoc.fitContentsToBox && this.fitBounds(this._map); @@ -268,7 +273,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { @@ -278,7 +283,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { @@ -287,64 +292,64 @@ export class MapBox extends ViewBoxAnnotatableComponent { - place[Id] ? this.markerMap[place[Id]] = marker : null; - } + place[Id] ? (this.markerMap[place[Id]] = marker) : null; + }; /** * on clicking the map marker, set the selected place to the marker document & set infowindowopen to be true - * @param e - * @param place + * @param e + * @param place */ @action private markerClickHandler = (e: google.maps.MapMouseEvent, place: Doc) => { // set which place was clicked this.selectedPlace = place; place.infoWindowOpen = true; - } + }; /** * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts - * @param doc - * @param sidebarKey - * @returns + * @param doc + * @param sidebarKey + * @returns */ sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { - console.log("print all sidebar Docs"); + console.log('print all sidebar Docs'); console.log(this.allSidebarDocs); if (!this.layoutDoc._showSidebar) this.toggleSidebar(); const docs = doc instanceof Doc ? [doc] : doc; docs.forEach(doc => { if (doc.lat !== undefined && doc.lng !== undefined) { const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng); - doc.onClickBehavior = "enterPortal"; + doc.onClickBehavior = 'enterPortal'; if (existingMarker) { - Doc.AddDocToList(existingMarker, "data", doc); + Doc.AddDocToList(existingMarker, 'data', doc); } else { const marker = Docs.Create.MapMarkerDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {}); this.addDocument(marker, this.annotationKey); } } }); //add to annotation list - console.log("sidebaraddDocument"); + console.log('sidebaraddDocument'); console.log(doc); return this.addDocument(doc, sidebarKey); // add to sidebar list - } + }; /** * Removing documents from the sidebar - * @param doc - * @param sidebarKey - * @returns + * @param doc + * @param sidebarKey + * @returns */ sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (this.layoutDoc._showSidebar) this.toggleSidebar(); @@ -354,31 +359,43 @@ export class MapBox extends ViewBoxAnnotatableComponent { - setupMoveUpEvents(this, e, (e, down, delta) => { - const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; - if (ratio >= 1) { - this.layoutDoc.nativeWidth = nativeWidth * ratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; - this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; - } - return false; - }, emptyFunction, this.toggleSidebar); - } - - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } - @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.scaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); + const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); + const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; + if (ratio >= 1) { + this.layoutDoc.nativeWidth = nativeWidth * ratio; + this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; + this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; + } + return false; + }, + emptyFunction, + this.toggleSidebar + ); + }; + sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); + @computed get sidebarWidthPercent() { + return StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); + } + @computed get sidebarColor() { + return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4')); + } /** * function that reads the place inputed from searchbox, then zoom in on the location that's been autocompleted; @@ -414,7 +431,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { + this.searchMarkers.forEach(marker => { marker.setMap(null); }); this.searchMarkers = []; @@ -426,7 +443,7 @@ export class MapBox extends ViewBoxAnnotatableComponent d?.author).length; const color = !annotated ? Colors.WHITE : Colors.BLACK; - const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props as any, StyleProp.WidgetColor + (annotated ? ":annotated" : "")); - return (!annotated) ? (null) : -
- -
; + opacity: annotated ? 1 : undefined, + }}> + +
+ ); } // TODO: Adding highlight box layer to Maps @action toggleSidebar = () => { const prevWidth = this.sidebarWidth(); - this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%"; + this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; this.layoutDoc._width = this.layoutDoc._showSidebar ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); - } + }; sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); - } + }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this._ref.current!.getBoundingClientRect(); - this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; - this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; + this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; + this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%'; e.preventDefault(); return false; - } + }; - setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func; + setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); @action onMarqueeDown = (e: React.PointerEvent) => { if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { - setupMoveUpEvents(this, e, action(e => { - MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; - return true; - }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); + setupMoveUpEvents( + this, + e, + action(e => { + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); + this._marqueeing = [e.clientX, e.clientY]; + return true; + }), + returnFalse, + () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), + false + ); } - } + }; @action finishMarquee = (x?: number, y?: number) => { this._marqueeing = undefined; this._isAnnotating = false; x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false); - } + }; addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => { return this.addDocument(doc, annotationKey); - } + }; pointerEvents = () => { - return this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? - "all" : - SnappingManager.GetIsDragging() ? - undefined : "none"; - } + return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'; + }; @computed get annotationLayer() { - return
- {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - )} -
; + return ( +
+ {this.inlineTextAnnotations + .sort((a, b) => NumCast(a.y) - NumCast(b.y)) + .map(anno => ( + + ))} +
+ ); } getAnchor = () => { - const anchor = - AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? - this.rootDoc; + const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? this.rootDoc; return anchor; - } + }; /** * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker - * @returns + * @returns */ private renderMarkers = () => { return this.allMapMarkers.map(place => ( - this.markerLoadHandler(marker, place)} - onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} - /> + this.markerLoadHandler(marker, place)} onClick={(e: google.maps.MapMouseEvent) => this.markerClickHandler(e, place)} /> )); - } + }; // TODO: auto center on select a document in the sidebar private handleMapCenter = (map: google.maps.Map) => { @@ -531,7 +552,7 @@ export class MapBox extends ViewBoxAnnotatableComponent this.props.PanelWidth() / (this.props.scaling?.() || 1) - this.sidebarWidth(); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); @@ -543,7 +564,7 @@ export class MapBox extends ViewBoxAnnotatableComponent this._sidebarRef.current?.anchorMenuClick; savedAnnotations = () => this._savedAnnotations; render() { - const renderAnnotations = (docFilters?: () => string[]) => (null); + const renderAnnotations = (docFilters?: () => string[]) => null; // bcz: commmented this out. Otherwise, any documents that are rendered with an InfoWindow of a marker // will also be rendered as freeform annotations on the map. However, it doesn't seem that rendering // freeform documents on the map does anything anyway, so getting rid of it for now. Also, since documents @@ -571,88 +592,88 @@ export class MapBox extends ViewBoxAnnotatableComponent; - return
- {/*console.log(apiKey)*/} - {/* + {/*console.log(apiKey)*/} + {/* */} -
e.stopPropagation()} - onPointerDown={e => (e.button === 0 && !e.ctrlKey) && e.stopPropagation()} - style={{ width: `calc(100% - ${this.sidebarWidthPercent})` }}> - -
- {renderAnnotations(this.transparentFilter)} +
e.stopPropagation()} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} style={{ width: `calc(100% - ${this.sidebarWidthPercent})` }}> +
{renderAnnotations(this.transparentFilter)}
+ {renderAnnotations(this.opaqueFilter)} + {SnappingManager.GetIsDragging() ? null : renderAnnotations()} + {this.annotationLayer} + + + + + + {this.renderMarkers()} + {this.allMapMarkers + .filter(marker => marker.infoWindowOpen) + .map(marker => ( + + ))} + {/* {this.handleMapCenter(this._map)} */} + + {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( + + )}
- {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()} - {this.annotationLayer} - - - - - - {this.renderMarkers()} - {this.allMapMarkers.filter(marker => marker.infoWindowOpen).map(marker => */} +
+ )} - {this.handleMapCenter(this._map)} - - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - } -
- {/* */} -
- -
-
- + PanelWidth={this.sidebarWidth} + sidebarAddDocument={this.sidebarAddDocument} + moveDocument={this.moveDocument} + removeDocument={this.sidebarRemoveDocument} + /> +
+
+ +
-
; + ); } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 1fb3cef2a..5b9d90780 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; -import { observer } from "mobx-react"; -import * as Pdfjs from "pdfjs-dist"; -import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { observer } from 'mobx-react'; +import * as Pdfjs from 'pdfjs-dist'; +import 'pdfjs-dist/web/pdf_viewer.css'; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; -import { ImageField, PdfField } from "../../../fields/URLField"; +import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -15,25 +15,27 @@ import { KeyCodes } from '../../util/KeyCodes'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { Colors } from '../global/globalEnums'; -import { CreateImage } from "../nodes/WebBoxRenderer"; +import { CreateImage } from '../nodes/WebBoxRenderer'; import { AnchorMenu } from '../pdf/AnchorMenu'; -import { PDFViewer } from "../pdf/PDFViewer"; +import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; -import "./PDFBox.scss"; +import './PDFBox.scss'; import { VideoBox } from './VideoBox'; -import React = require("react"); +import React = require('react'); import { CollectionFreeFormView } from '../collections/collectionFreeForm'; @observer export class PDFBox extends ViewBoxAnnotatableComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(PDFBox, fieldKey); + } public static openSidebarWidth = 250; public static sidebarResizerWidth = 5; - private _searchString: string = ""; + private _searchString: string = ''; private _initialScrollTarget: Opt; private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef(); @@ -44,8 +46,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent; @observable private _pageControls = false; - @computed get pdfUrl() { return Cast(this.dataDoc[this.props.fieldKey], PdfField); } - @computed get pdfThumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url; } + @computed get pdfUrl() { + return Cast(this.dataDoc[this.props.fieldKey], PdfField); + } + @computed get pdfThumb() { + return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url; + } constructor(props: any) { super(props); @@ -53,8 +59,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href)); - else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => this._pdf = pdf)); + if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href))); + else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf))); } } @@ -65,15 +71,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent { if (!region) return; const cropping = Doc.MakeCopy(region, true); Doc.GetProto(region).lockedPosition = true; - Doc.GetProto(region).title = "region:" + this.rootDoc.title; + Doc.GetProto(region).title = 'region:' + this.rootDoc.title; Doc.GetProto(region).isPushpin = true; this.addDocument(region); const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); - newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + newDiv.style.width = this.layoutDoc[WidthSym]().toString(); + newDiv.style.height = this.layoutDoc[HeightSym]().toString(); this.replaceCanvases(docViewContent, newDiv); const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); @@ -103,7 +108,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - VideoBox.convertDataUri(data_url, region[Id]).then( - returnedfilename => setTimeout(action(() => { - croppingProto.data = new ImageField(returnedfilename); - }), 500)); + ) + .then((data_url: any) => { + VideoBox.convertDataUri(data_url, region[Id]).then(returnedfilename => + setTimeout( + action(() => { + croppingProto.data = new ImageField(returnedfilename); + }), + 500 + ) + ); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); }); - return cropping; - } + }; updateIcon = () => { // currently we render pdf icons as text labels const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; - const filename = this.layoutDoc[Id] + "-icon" + (new Date()).getTime(); - this._pdfViewer?._mainCont.current && CollectionFreeFormView.UpdateIcon( - filename, docViewContent, - this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), - this.props.PanelWidth(), this.props.PanelHeight(), - NumCast(this.layoutDoc._scrollTop), - NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"], 1), - true, - this.layoutDoc[Id] + "-icon", - (iconFile:string, nativeWidth:number, nativeHeight:number) => { - setTimeout(() => { - this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }, 500); - }); - } + const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime(); + this._pdfViewer?._mainCont.current && + CollectionFreeFormView.UpdateIcon( + filename, + docViewContent, + this.layoutDoc[WidthSym](), + this.layoutDoc[HeightSym](), + this.props.PanelWidth(), + this.props.PanelHeight(), + NumCast(this.layoutDoc._scrollTop), + NumCast(this.rootDoc[this.fieldKey + '-nativeHeight'], 1), + true, + this.layoutDoc[Id] + '-icon', + (iconFile: string, nativeWidth: number, nativeHeight: number) => { + setTimeout(() => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc['icon-nativeWidth'] = nativeWidth; + this.dataDoc['icon-nativeHeight'] = nativeHeight; + }, 500); + } + ); + }; - componentWillUnmount() { this._selectReactionDisposer?.(); } + componentWillUnmount() { + this._selectReactionDisposer?.(); + } componentDidMount() { this.props.setContentView?.(this); - this._selectReactionDisposer = reaction(() => this.props.isSelected(), + this._selectReactionDisposer = reaction( + () => this.props.isSelected(), () => { - document.removeEventListener("keydown", this.onKeyDown); - this.props.isSelected(true) && document.addEventListener("keydown", this.onKeyDown); - }, { fireImmediately: true }); + document.removeEventListener('keydown', this.onKeyDown); + this.props.isSelected(true) && document.addEventListener('keydown', this.onKeyDown); + }, + { fireImmediately: true } + ); } scrollFocus = (doc: Doc, smooth: boolean) => { let didToggle = false; - if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) { + if (DocListCast(this.props.Document[this.fieldKey + '-sidebar']).includes(doc) && !this.SidebarShown) { this.toggleSidebar(!smooth); didToggle = true; } if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; this._initialScrollTarget = doc; return this._pdfViewer?.scrollFocus(doc, smooth) ?? (didToggle ? 1 : undefined); - } + }; getAnchor = () => { const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? Docs.Create.TextanchorDocument({ - title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), + title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), y: NumCast(this.layoutDoc._scrollTop), - unrendered: true + unrendered: true, }); this.addDocument(anchor); return anchor; - } + }; @action loaded = (nw: number, nh: number, np: number) => { - this.dataDoc[this.props.fieldKey + "-numPages"] = np; - Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), nw * 96 / 72)); - Doc.SetNativeHeight(this.dataDoc, nh * 96 / 72); + this.dataDoc[this.props.fieldKey + '-numPages'] = np; + Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72)); + Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72); this.layoutDoc._height = this.layoutDoc[WidthSym]() / (Doc.NativeAspect(this.dataDoc) || 1); !this.Document._fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); - } + }; public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { if (!this._searching && !clear) { @@ -225,19 +243,23 @@ export class PDFBox extends ViewBoxAnnotatableComponent { this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1); return true; - } + }; public forwardPage = () => { - this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1); + this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + '-numPages']), (NumCast(this.Document._curPage) || 1) + 1); return true; - } - public gotoPage = (p: number) => this.Document._curPage = p; + }; + public gotoPage = (p: number) => (this.Document._curPage = p); @undoBatch onKeyDown = action((e: KeyboardEvent) => { let processed = false; switch (e.key) { - case "PageDown": processed = this.forwardPage(); break; - case "PageUp": processed = this.backPage(); break; + case 'PageDown': + processed = this.forwardPage(); + break; + case 'PageUp': + processed = this.backPage(); + break; } if (processed) { e.stopImmediatePropagation(); @@ -251,131 +273,163 @@ export class PDFBox extends ViewBoxAnnotatableComponent) => this._searchString = e.currentTarget.value; + }; + searchStringChanged = (e: React.ChangeEvent) => (this._searchString = e.currentTarget.value); // adding external documents; to sidebar key // if (doc.Geolocation) this.addDocument(doc, this.fieldkey+"-annotation") sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); return this.addDocument(doc, sidebarKey); - } - sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf - const batch = UndoManager.StartBatch("sidebar"); - setupMoveUpEvents(this, e, (e, down, delta) => { - const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); - const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); - const ratio = (curNativeWidth + (onButton ? 1 : -1) * localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; - if (ratio >= 1) { - this.layoutDoc.nativeWidth = nativeWidth * ratio; - onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); - this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; - } - return false; - }, () => batch.end(), () => this.toggleSidebar()); - } + }; + sidebarBtnDown = (e: React.PointerEvent, onButton: boolean) => { + // onButton determines whether the width of the pdf box changes, or just the ratio of the sidebar to the pdf + const batch = UndoManager.StartBatch('sidebar'); + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.scaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); + const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); + const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.scaling?.() || 1)) / nativeWidth; + if (ratio >= 1) { + this.layoutDoc.nativeWidth = nativeWidth * ratio; + onButton && (this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]); + this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; + } + return false; + }, + () => batch.end(), + () => this.toggleSidebar() + ); + }; @observable _previewNativeWidth: Opt = undefined; @observable _previewWidth: Opt = undefined; toggleSidebar = action((preview: boolean = false) => { - const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '-nativeWidth']); const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth; const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { this._previewNativeWidth = nativeWidth * sideratio; - this._previewWidth = this.layoutDoc[WidthSym]() * nativeWidth * sideratio / curNativeWidth; + this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth; this._showSidebar = true; - } - else { + } else { this.layoutDoc.nativeWidth = nativeWidth * pdfratio; - this.layoutDoc._width = this.layoutDoc[WidthSym]() * nativeWidth * pdfratio / curNativeWidth; + this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth; this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } }); settingsPanel() { - const pageBtns = <> - - - ; - const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; + const pageBtns = ( + <> + + + + ); + const searchTitle = `${!this._searching ? 'Open' : 'Close'} Search Bar`; const curPage = NumCast(this.Document._curPage) || 1; - return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? (null) : -
[KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true} - onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}> -
e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> + return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? null : ( +
([KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true)} + onPointerDown={e => e.stopPropagation()} + style={{ display: this.props.isContentActive() ? 'flex' : 'none' }}> +
e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> - -
-
- 99 ? 4 : 3}ch`, pointerEvents: "all" }} - onChange={e => this.Document._curPage = Number(e.currentTarget.value)} + 99 ? 4 : 3}ch`, pointerEvents: 'all' }} + onChange={e => (this.Document._curPage = Number(e.currentTarget.value))} onKeyDown={e => e.stopPropagation()} - onClick={action(() => this._pageControls = !this._pageControls)} /> - {this._pageControls ? pageBtns : (null)} + onClick={action(() => (this._pageControls = !this._pageControls))} + /> + {this._pageControls ? pageBtns : null}
-
this.sidebarBtnDown(e, true)} > - + onPointerDown={e => this.sidebarBtnDown(e, true)}> +
-
; +
+ ); } - sidebarWidth = () => !this.SidebarShown ? 0 : - PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : - (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth)) + sidebarWidth = () => + !this.SidebarShown ? 0 : PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth)); specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" }); - funcs.push({ description: "update icon", event: () => this.pdfUrl && this.updateIcon(), icon: "expand-arrows-alt" }); + funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); + funcs.push({ description: 'update icon', event: () => this.pdfUrl && this.updateIcon(), icon: 'expand-arrows-alt' }); //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - } + ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); + }; @computed get renderTitleBox() { - const classname = "pdfBox" + (this.props.isContentActive() ? "-interactive" : ""); - return
-
- {this.props.Document.title} + const classname = 'pdfBox' + (this.props.isContentActive() ? '-interactive' : ''); + return ( +
+
+ {StrCast(this.props.Document.title)} +
-
; + ); } anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; @observable _showSidebar = false; - @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } + @computed get SidebarShown() { + return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + } contentScaling = () => 1; isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected(); @@ -383,54 +437,60 @@ export class PDFBox extends ViewBoxAnnotatableComponent 600) ? - NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined - }}> -
this.sidebarBtnDown(e, false)} /> -
- 600 ? (NumCast(this.Document._height) * this.props.PanelWidth()) / NumCast(this.Document._width) : undefined, + }}> +
this.sidebarBtnDown(e, false)} /> +
+ +
+ + {this.settingsPanel()}
- - {this.settingsPanel()} -
; + ); } static pdfcache = new Map(); @@ -446,13 +506,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._pdf = PDFBox.pdfcache.get(href))); + if (PDFBox.pdfcache.get(href)) setTimeout(action(() => (this._pdf = PDFBox.pdfcache.get(href)))); else { if (!PDFBox.pdfpromise.get(href)) PDFBox.pdfpromise.set(href, Pdfjs.getDocument(href).promise); - PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, this._pdf = pdf))); + PDFBox.pdfpromise.get(href)?.then(action((pdf: any) => PDFBox.pdfcache.set(href, (this._pdf = pdf)))); } } return this.renderTitleBox; } } - diff --git a/src/client/views/nodes/formattedText/schema_rts.ts b/src/client/views/nodes/formattedText/schema_rts.ts index 83561073c..d6e0e6002 100644 --- a/src/client/views/nodes/formattedText/schema_rts.ts +++ b/src/client/views/nodes/formattedText/schema_rts.ts @@ -1,8 +1,7 @@ -import { Schema, Slice } from "prosemirror-model"; - -import { nodes } from "./nodes_rts"; -import { marks } from "./marks_rts"; +import { Schema, Slice } from 'prosemirror-model'; +import { nodes } from './nodes_rts'; +import { marks } from './marks_rts'; // :: Schema // This schema rougly corresponds to the document schema used by @@ -20,7 +19,9 @@ const fromJson = schema.nodeFromJSON; schema.nodeFromJSON = (json: any) => { const node = fromJson(json); if (json.type === schema.nodes.summary.name) { - node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); + // bcz: this is a hacky way to convert the JSON that's serialized for a summary node into the Slice that the summary node wants at run-time. + // since attrs are readonly, assigning the text field like this violates the way prosemirror works, but I think we can get away with it. + (node.attrs.text as any) = Slice.fromJSON(schema, node.attrs.textslice); } return node; -}; \ No newline at end of file +}; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 7045d6d5d..91f8d1efc 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1,55 +1,57 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { ColorState, SketchPicker } from "react-color"; +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { ColorState, SketchPicker } from 'react-color'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; -import { Doc, DocListCast, DocListCastAsync, FieldResult } from "../../../../fields/Doc"; -import { InkTool } from "../../../../fields/InkField"; -import { List } from "../../../../fields/List"; -import { PrefetchProxy } from "../../../../fields/Proxy"; -import { listSpec } from "../../../../fields/Schema"; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from "../../../../fields/Types"; +import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc'; +import { InkTool } from '../../../../fields/InkField'; +import { List } from '../../../../fields/List'; +import { PrefetchProxy } from '../../../../fields/Proxy'; +import { listSpec } from '../../../../fields/Schema'; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; -import { Docs } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { CollectionDockingView } from "../../collections/CollectionDockingView"; -import { MarqueeViewBounds } from "../../collections/collectionFreeForm"; -import { CollectionView, CollectionViewType } from "../../collections/CollectionView"; -import { TabDocView } from "../../collections/TabDocView"; -import { ViewBoxBaseComponent } from "../../DocComponent"; -import { Colors } from "../../global/globalEnums"; -import { LightboxView } from "../../LightboxView"; -import { CollectionFreeFormDocumentView } from "../CollectionFreeFormDocumentView"; +import { Docs } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { CollectionDockingView } from '../../collections/CollectionDockingView'; +import { MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionView, CollectionViewType } from '../../collections/CollectionView'; +import { TabDocView } from '../../collections/TabDocView'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { Colors } from '../../global/globalEnums'; +import { LightboxView } from '../../LightboxView'; +import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; -import "./PresBox.scss"; -import { PresEffect, PresMovement, PresStatus } from "./PresEnums"; -import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; -import { PresElementBox } from "."; +import './PresBox.scss'; +import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; +import { PresElementBox } from '.'; export interface PinProps { audioRange?: boolean; setPosition?: boolean; hidePresBox?: boolean; pinWithView?: PinViewProps; - pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document - panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) - panelHeight?: number + pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document + panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) + panelHeight?: number; } export interface PinViewProps { - bounds: MarqueeViewBounds; - scale: number; + bounds: MarqueeViewBounds; + scale: number; } @observer export class PresBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(PresBox, fieldKey); + } /** * transitions & effects for documents @@ -67,22 +69,27 @@ export class PresBox extends ViewBoxBaseComponent() { // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, }; switch (presDoc.presEffect) { - case PresEffect.Zoom: return ({renderDoc}); - case PresEffect.Fade: return ({renderDoc}); - case PresEffect.Flip: return ({renderDoc}); - case PresEffect.Rotate: return ({renderDoc}); - case PresEffect.Bounce: return ({renderDoc}); - case PresEffect.Roll: return ({renderDoc}); - case PresEffect.Lightspeed: return ({renderDoc}); + case PresEffect.Zoom: + return {renderDoc}; + case PresEffect.Fade: + return {renderDoc}; + case PresEffect.Flip: + return {renderDoc}; + case PresEffect.Rotate: + return {renderDoc}; + case PresEffect.Bounce: + return {renderDoc}; + case PresEffect.Roll: + return {renderDoc}; + case PresEffect.Lightspeed: + return {renderDoc}; case PresEffect.None: - default: return renderDoc; + default: + return renderDoc; } } public static EffectsProvider(layoutDoc: Doc, renderDoc: any) { - return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? - PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) - : - renderDoc; + return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) : renderDoc; } @observable public static Instance: PresBox; @@ -107,10 +114,18 @@ export class PresBox extends ViewBoxBaseComponent() { @observable private openMovementDropdown: boolean = false; @observable private openEffectDropdown: boolean = false; @observable private presentTools: boolean = false; - @computed get isTreeOrStack() {return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any) } - @computed get isTree() { return this.layoutDoc._viewType === CollectionViewType.Tree;} - @computed get presFieldKey() { return StrCast(this.layoutDoc.presFieldKey, "data"); } - @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); } + @computed get isTreeOrStack() { + return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any); + } + @computed get isTree() { + return this.layoutDoc._viewType === CollectionViewType.Tree; + } + @computed get presFieldKey() { + return StrCast(this.layoutDoc.presFieldKey, 'data'); + } + @computed get childDocs() { + return DocListCast(this.rootDoc[this.presFieldKey]); + } @observable _treeViewMap: Map = new Map(); @computed get tagDocs() { @@ -121,9 +136,15 @@ export class PresBox extends ViewBoxBaseComponent() { } return tagDocs; } - @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } - @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); } - @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } + @computed get itemIndex() { + return NumCast(this.rootDoc._itemIndex); + } + @computed get activeItem() { + return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); + } + @computed get targetDoc() { + return Cast(this.activeItem?.presentationTargetDoc, Doc, null); + } @computed get scrollable(): boolean { if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; else return false; @@ -134,7 +155,7 @@ export class PresBox extends ViewBoxBaseComponent() { } constructor(props: any) { super(props); - if (CurrentUserUtils.ActivePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); + if ((CurrentUserUtils.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this)); this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox } @computed get selectedDocumentView() { @@ -142,21 +163,23 @@ export class PresBox extends ViewBoxBaseComponent() { if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); } @computed get isPres(): boolean { - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); if (this.selectedDoc?.type === DocumentType.PRES) { - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); - document.addEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); + document.addEventListener('keydown', PresBox.keyEventsWrapper, true); return true; } return false; } - @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } + @computed get selectedDoc() { + return this.selectedDocumentView?.rootDoc; + } _unmounting = false; @action componentWillUnmount() { this._unmounting = true; - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); this._presKeyEventsActive = false; this.resetPresentation(); // Turn of progressivize editors @@ -167,37 +190,38 @@ export class PresBox extends ViewBoxBaseComponent() { @action componentDidMount() { this._unmounting = false; - this.rootDoc._forceRenderEngine = "timeline"; + this.rootDoc._forceRenderEngine = 'timeline'; this.layoutDoc.presStatus = PresStatus.Edit; this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; this.turnOffEdit(true); - DocListCastAsync(CurrentUserUtils.MyTrails.data).then(pres => - !pres?.includes(this.rootDoc) && Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", this.rootDoc)); - this._disposers.selection = reaction(() => SelectionManager.Views(), - views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()); + DocListCastAsync(CurrentUserUtils.MyTrails.data).then(pres => !pres?.includes(this.rootDoc) && Doc.AddDocToList(CurrentUserUtils.MyTrails, 'data', this.rootDoc)); + this._disposers.selection = reaction( + () => SelectionManager.Views(), + views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation() + ); } @action updateCurrentPresentation = (pres?: Doc) => { if (pres) CurrentUserUtils.ActivePresentation = pres; else CurrentUserUtils.ActivePresentation = this.rootDoc; - document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); - document.addEventListener("keydown", PresBox.keyEventsWrapper, true); + document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); + document.addEventListener('keydown', PresBox.keyEventsWrapper, true); this._presKeyEventsActive = true; PresBox.Instance = this; - } + }; // There are still other internal frames and should go through all frames before going to next slide nextInternalFrame = (targetDoc: Doc, activeItem: Doc) => { - const currentFrame = Cast(targetDoc?._currentFrame, "number", null); + const currentFrame = Cast(targetDoc?._currentFrame, 'number', null); const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); - targetDoc._viewTransition = "all 1s"; - setTimeout(() => targetDoc._viewTransition = undefined, 1010); + targetDoc._viewTransition = 'all 1s'; + setTimeout(() => (targetDoc._viewTransition = undefined), 1010); this.nextKeyframe(targetDoc, activeItem); if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); else targetDoc.keyFrameEditing = true; - } + }; _mediaTimer!: [NodeJS.Timeout, Doc]; // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played @@ -207,7 +231,7 @@ export class PresBox extends ViewBoxBaseComponent() { const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.presStartTime), NumCast(activeItem.presStartTime) + duration); } - } + }; stopTempMedia = (targetDocField: FieldResult) => { const targetDoc = Cast(targetDocField, Doc, null); @@ -215,11 +239,11 @@ export class PresBox extends ViewBoxBaseComponent() { const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); targMedia?.ComponentView?.Pause?.(); } - } + }; - //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time + //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions - // No more frames in current doc and next slide is defined, therefore move to next slide + // No more frames in current doc and next slide is defined, therefore move to next slide nextSlide = (activeNext: Doc) => { const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); console.info('nextSlide', activeNext.title, targetNext?.title); @@ -229,11 +253,11 @@ export class PresBox extends ViewBoxBaseComponent() { if (!this.childDocs[nextSelected].groupWithUp) { break; } else { - console.log("Title: " + this.childDocs[nextSelected].title); + console.log('Title: ' + this.childDocs[nextSelected].title); this.gotoDocument(nextSelected, this.activeItem, true); } } - } + }; // Called when the user activates 'next' - to move to the next part of the pres. trail @action @@ -241,7 +265,7 @@ export class PresBox extends ViewBoxBaseComponent() { const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const lastFrame = Cast(targetDoc?.lastFrame, "number", null); + const lastFrame = Cast(targetDoc?.lastFrame, 'number', null); const curFrame = NumCast(targetDoc?._currentFrame); let internalFrames: boolean = false; if (activeItem.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; @@ -249,13 +273,13 @@ export class PresBox extends ViewBoxBaseComponent() { // Case 1: There are still other frames and should go through all frames before going to next slide this.nextInternalFrame(targetDoc, activeItem); } else if (this.childDocs[this.itemIndex + 1] !== undefined) { - // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide + // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide this.nextSlide(activeNext); } else if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) { // Case 3: Last slide and presLoop is toggled ON or it is in Edit mode this.gotoDocument(0, this.activeItem); } - } + }; // Called when the user activates 'back' - to move to the previous part of the pres. trail @action @@ -264,7 +288,7 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); - const lastFrame = Cast(targetDoc.lastFrame, "number", null); + const lastFrame = Cast(targetDoc.lastFrame, 'number', null); const curFrame = NumCast(targetDoc._currentFrame); let prevSelected = this.itemIndex; // Functionality for group with up @@ -284,7 +308,7 @@ export class PresBox extends ViewBoxBaseComponent() { // Case 3: Pres loop is on so it should go to the last slide this.gotoDocument(this.childDocs.length - 1, activeItem); } - } + }; //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. @@ -297,20 +321,21 @@ export class PresBox extends ViewBoxBaseComponent() { if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia); } - if (from?.mediaStop === "auto" && this.layoutDoc.presStatus !== PresStatus.Edit) { + if (from?.mediaStop === 'auto' && this.layoutDoc.presStatus !== PresStatus.Edit) { this.stopTempMedia(from.presentationTargetDoc); } // If next slide is audio / video 'Play automatically' then the next slide should be played - if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && (activeItem.mediaStart === "auto")) { + if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') { this.startTempMedia(targetDoc, activeItem); } if (targetDoc) { - Doc.linkFollowHighlight((targetDoc.annotationOn instanceof Doc) ? [targetDoc, targetDoc.annotationOn] : targetDoc); - targetDoc && runInAction(() => { - if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0; - else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; - }); - setTimeout(() => targetDoc.focusSpeed = 500, this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); + Doc.linkFollowHighlight(targetDoc.annotationOn instanceof Doc ? [targetDoc, targetDoc.annotationOn] : targetDoc); + targetDoc && + runInAction(() => { + if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0; + else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; + }); + setTimeout(() => (targetDoc.focusSpeed = 500), this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); } if (targetDoc?.lastFrame !== undefined) { targetDoc._currentFrame = 0; @@ -327,14 +352,14 @@ export class PresBox extends ViewBoxBaseComponent() { clearTimeout(this._navTimer); const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; if (bestTarget) console.log(bestTarget.title, bestTarget.type); - else console.log("no best target"); - if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); - } + else console.log('no best target'); + if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); + }; // navigates to the bestTarget document by making sure it is on screen, - // then it applies the view specs stored in activeItem to + // then it applies the view specs stored in activeItem to @action - static navigateToDoc(bestTarget:Doc, activeItem:Doc, jumpToDoc:boolean) { + static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; bestTarget._scrollTop = activeItem.presPinViewScroll; @@ -343,32 +368,31 @@ export class PresBox extends ViewBoxBaseComponent() { } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) { bestTarget._currentTimecode = activeItem.presStartTime; } else { - const contentBounds= Cast(activeItem.contentBounds, listSpec("number")); + const contentBounds = Cast(activeItem.contentBounds, listSpec('number')); bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; if (contentBounds) { - bestTarget._panX = (contentBounds[0] + contentBounds[2])/2; - bestTarget._panY = (contentBounds[1] + contentBounds[3])/2; + bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2; + bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2; const dv = DocumentManager.Instance.getDocumentView(bestTarget); if (dv) { - bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), - dv.props.PanelWidth() / (contentBounds[2]- contentBounds[0])); - }; + bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0])); + } } else { bestTarget._panX = activeItem.presPinViewX; bestTarget._panY = activeItem.presPinViewY; bestTarget._viewScale = activeItem.presPinViewScale; } } - return setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); + return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); } /** * This method makes sure that cursor navigates to the element that - * has the option open and last in the group. - * Design choice: If the next document is not in presCollection or + * has the option open and last in the group. + * Design choice: If the next document is not in presCollection or * presCollection itself then if there is a presCollection it will add * a new tab. If presCollection is undefined it will open the document - * on the right. + * on the right. */ navigateToElement = async (curDoc: Doc) => { const activeItem: Doc = this.activeItem; @@ -401,7 +425,7 @@ export class PresBox extends ViewBoxBaseComponent() { self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); const openInTab = (doc: Doc, finished?: () => void) => { - collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ""); + collectionDocView ? collectionDocView.props.addDocTab(doc, '') : this.props.addDocTab(doc, ''); this.layoutDoc.presCollection = targetDoc; // this still needs some fixing setTimeout(resetSelection, 500); @@ -417,21 +441,21 @@ export class PresBox extends ViewBoxBaseComponent() { // openInTab(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { LightboxView.SetLightboxDoc(undefined); - await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right + await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { LightboxView.SetLightboxDoc(undefined); - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext]:[], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right + //awaiting jump so that new scale can be found, since jumping is async + await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. if (activeItem.presPinView) { console.log(targetDoc.title); - console.log("presPinView in PresBox.tsx:420"); + console.log('presPinView in PresBox.tsx:420'); // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target this.navigateToView(targetDoc, activeItem); } - } + }; /** * Uses the viewfinder to progressivize through the different views of a single collection. @@ -441,10 +465,10 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; const srcContext = Cast(targetDoc?.context, Doc, null); const docView = DocumentManager.Instance.getDocumentView(targetDoc); - const vfLeft = this.checkList(targetDoc, activeItem["viewfinder-left-indexed"]); - const vfWidth = this.checkList(targetDoc, activeItem["viewfinder-width-indexed"]); - const vfTop = this.checkList(targetDoc, activeItem["viewfinder-top-indexed"]); - const vfHeight = this.checkList(targetDoc, activeItem["viewfinder-height-indexed"]); + const vfLeft = this.checkList(targetDoc, activeItem['viewfinder-left-indexed']); + const vfWidth = this.checkList(targetDoc, activeItem['viewfinder-width-indexed']); + const vfTop = this.checkList(targetDoc, activeItem['viewfinder-top-indexed']); + const vfHeight = this.checkList(targetDoc, activeItem['viewfinder-height-indexed']); // Case 1: document that is not a Golden Layout tab if (srcContext) { const srcDocView = DocumentManager.Instance.getDocumentView(srcContext); @@ -455,8 +479,8 @@ export class PresBox extends ViewBoxBaseComponent() { const newPanX = NumCast(targetDoc.x) + NumCast(layoutdoc._width) / 2; const newPanY = NumCast(targetDoc.y) + NumCast(layoutdoc._height) / 2; const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); - srcContext._panX = newPanX + (vfLeft + (vfWidth / 2)); - srcContext._panY = newPanY + (vfTop + (vfHeight / 2)); + srcContext._panX = newPanX + (vfLeft + vfWidth / 2); + srcContext._panY = newPanY + (vfTop + vfHeight / 2); srcContext._viewScale = newScale; } } @@ -465,8 +489,8 @@ export class PresBox extends ViewBoxBaseComponent() { const panelWidth: number = docView.props.PanelWidth(); const panelHeight: number = docView.props.PanelHeight(); const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); - targetDoc._panX = vfLeft + (vfWidth / 2); - targetDoc._panY = vfTop + (vfWidth / 2); + targetDoc._panX = vfLeft + vfWidth / 2; + targetDoc._panY = vfTop + vfWidth / 2; targetDoc._viewScale = newScale; } const resize = document.getElementById('resizable'); @@ -476,7 +500,7 @@ export class PresBox extends ViewBoxBaseComponent() { resize.style.top = vfTop + 'px'; resize.style.left = vfLeft + 'px'; } - } + }; /** * For 'Hide Before' and 'Hide After' buttons making sure that @@ -490,18 +514,19 @@ export class PresBox extends ViewBoxBaseComponent() { if (tagDoc) tagDoc.opacity = 1; const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc); const curInd: number = itemIndexes.indexOf(index); - if (tagDoc === this.layoutDoc.presCollection) { tagDoc.opacity = 1; } - else { - if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { } - else if (curDoc.presHideBefore) { + if (tagDoc === this.layoutDoc.presCollection) { + tagDoc.opacity = 1; + } else { + if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { + } else if (curDoc.presHideBefore) { if (index > this.itemIndex) { tagDoc.opacity = 0; } else if (!curDoc.presHideAfter) { tagDoc.opacity = 1; } } - if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== (itemIndexes.length - 1)) { } - else if (curDoc.presHideAfter) { + if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) { + } else if (curDoc.presHideAfter) { if (index < this.itemIndex) { tagDoc.opacity = 0; } else if (!curDoc.presHideBefore) { @@ -510,9 +535,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } }); - } - - + }; //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @action @@ -521,13 +544,16 @@ export class PresBox extends ViewBoxBaseComponent() { let activeItem: Doc = this.activeItem; let targetDoc: Doc = this.targetDoc; let duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); - const load = async () => { // Wrap the loop into an async function for this to work + const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); + const load = async () => { + // Wrap the loop into an async function for this to work for (var i = startSlide; i < this.childDocs.length; i++) { activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); - if (duration < 100) { duration = 2500; } + if (duration < 100) { + duration = 2500; + } if (NumCast(targetDoc.lastFrame) > 0) { for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { await timer(duration / NumCast(targetDoc.lastFrame)); @@ -535,7 +561,8 @@ export class PresBox extends ViewBoxBaseComponent() { } } - await timer(duration); this.next(); // then the created Promise can be awaited + await timer(duration); + this.next(); // then the created Promise can be awaited if (i === this.childDocs.length - 1) { setTimeout(() => { clearTimeout(this._presTimer); @@ -549,7 +576,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.startPresentation(startSlide); this.gotoDocument(startSlide, activeItem); load(); - } + }; // The function pauses the auto presentation @action @@ -560,20 +587,23 @@ export class PresBox extends ViewBoxBaseComponent() { this.layoutDoc.presLoop = false; this.childDocs.forEach(this.stopTempMedia); } - } + }; //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. resetPresentation = () => { this.rootDoc._itemIndex = 0; - this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)).filter(doc => doc instanceof Doc).forEach(doc => { - try { - doc.opacity = 1; - } catch (e) { - console.log("Reset presentation error: ", e); - } - }); - } + this.childDocs + .map(doc => Cast(doc.presentationTargetDoc, Doc, null)) + .filter(doc => doc instanceof Doc) + .forEach(doc => { + try { + doc.opacity = 1; + } catch (e) { + console.log('Reset presentation error: ', e); + } + }); + }; // The function allows for viewing the pres path on toggle @action togglePath = (srcContext: Doc, off?: boolean) => { @@ -581,19 +611,19 @@ export class PresBox extends ViewBoxBaseComponent() { this._pathBoolean = false; srcContext.presPathView = false; } else { - runInAction(() => this._pathBoolean = !this._pathBoolean); + runInAction(() => (this._pathBoolean = !this._pathBoolean)); srcContext.presPathView = this._pathBoolean; } - } + }; // The function allows for expanding the view of pres on toggle @action toggleExpandMode = () => { - runInAction(() => this._expandBoolean = !this._expandBoolean); + runInAction(() => (this._expandBoolean = !this._expandBoolean)); this.rootDoc.expandBoolean = this._expandBoolean; - this.childDocs.forEach((doc) => { + this.childDocs.forEach(doc => { doc.presExpandInlineButton = this._expandBoolean; }); - } + }; /** * The function that starts the presentation at the given index, also checking if actions should be applied @@ -611,7 +641,7 @@ export class PresBox extends ViewBoxBaseComponent() { tagDoc.opacity = 0; } }); - } + }; /** * The method called to open the presentation as a minimized view @@ -621,7 +651,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); - CollectionDockingView.AddSplit(this.rootDoc, "right"); + CollectionDockingView.AddSplit(this.rootDoc, 'right'); } else { this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); @@ -633,7 +663,7 @@ export class PresBox extends ViewBoxBaseComponent() { Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); this.props.removeDocument?.(this.layoutDoc); } - } + }; /** * Called when the user changes the view type @@ -643,7 +673,7 @@ export class PresBox extends ViewBoxBaseComponent() { viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore const viewType = e.target.selectedOptions[0].value as CollectionViewType; - this.layoutDoc.presFieldKey = this.fieldKey+(viewType === CollectionViewType.Tree ?"-linearized":""); + this.layoutDoc.presFieldKey = this.fieldKey + (viewType === CollectionViewType.Tree ? '-linearized' : ''); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here [CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined); this.rootDoc._viewType = viewType; @@ -677,20 +707,30 @@ export class PresBox extends ViewBoxBaseComponent() { } }); - setMovementName = action((movement: any, activeItem: Doc): string => { let output: string = 'none'; switch (movement) { - case PresMovement.Zoom: output = 'Pan & Zoom'; break; //Pan and zoom - case PresMovement.Pan: output = 'Pan'; break; //Pan - case PresMovement.Jump: output = 'Jump cut'; break; //Jump Cut - case PresMovement.None: output = 'None'; break; //None - default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom + case PresMovement.Zoom: + output = 'Pan & Zoom'; + break; //Pan and zoom + case PresMovement.Pan: + output = 'Pan'; + break; //Pan + case PresMovement.Jump: + output = 'Jump cut'; + break; //Jump Cut + case PresMovement.None: + output = 'None'; + break; //None + default: + output = 'Zoom'; + activeItem.presMovement = 'zoom'; + break; //default set as zoom } return output; }); - whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isChildActive = isActive)); + whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive))); // For dragging documents into the presentation trail addDocumentFilter = (docs: Doc[]) => { docs.forEach((doc, i) => { @@ -698,8 +738,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (doc.type === DocumentType.LABEL) { const audio = Cast(doc.annotationOn, Doc, null); if (audio) { - audio.mediaStart = "manual"; - audio.mediaStop = "manual"; + audio.mediaStart = 'manual'; + audio.mediaStop = 'manual'; audio.presStartTime = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */)); audio.presEndTime = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */)); audio.presDuration = audio.presStartTime - audio.presEndTime; @@ -714,7 +754,7 @@ export class PresBox extends ViewBoxBaseComponent() { setTimeout(() => this.removeDocument(doc), 0); return false; } else { - if (!doc.presentationTargetDoc) doc.title = doc.title + " - Slide"; + if (!doc.presentationTargetDoc) doc.title = doc.title + ' - Slide'; doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); doc.presMovement = PresMovement.Zoom; if (this._expandBoolean) doc.presExpandInlineButton = true; @@ -722,13 +762,13 @@ export class PresBox extends ViewBoxBaseComponent() { } }); return true; - } - childLayoutTemplate = () => !this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement); + }; + childLayoutTemplate = () => (!this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement)); removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); - getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight + getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition) && - (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + isContentActive = (outsideReaction?: boolean) => + CurrentUserUtils.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; /** * For sorting the array so that the order is maintained when it is dropped. @@ -736,7 +776,7 @@ export class PresBox extends ViewBoxBaseComponent() { @action sortArray = (): Doc[] => { return this.childDocs.filter(doc => this._selectedArray.has(doc)); - } + }; /** * Method to get the list of selected items in the order in which they have been selected @@ -745,9 +785,26 @@ export class PresBox extends ViewBoxBaseComponent() { const list = Array.from(this._selectedArray.keys()).map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); - if (curDoc && curDoc === this.activeItem) return
{index + 1}. {curDoc.title}
; - else if (tagDoc) return
{index + 1}. {curDoc.title}
; - else if (curDoc) return
{index + 1}. {curDoc.title}
; + if (curDoc && curDoc === this.activeItem) + return ( +
+ + {index + 1}. {curDoc.title} + +
+ ); + else if (tagDoc) + return ( +
+ {index + 1}. {curDoc.title} +
+ ); + else if (curDoc) + return ( +
+ {index + 1}. {curDoc.title} +
+ ); }); return list; } @@ -756,7 +813,7 @@ export class PresBox extends ViewBoxBaseComponent() { selectPres = () => { const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc); presDocView && SelectionManager.SelectView(presDocView, false); - } + }; //Regular click @action @@ -766,12 +823,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); else this.updateCurrentPresentation(context); - if (this.activeItem.setPosition && - this.activeItem.y !== undefined && - this.activeItem.x !== undefined && - this.targetDoc.x !== undefined && - this.targetDoc.y !== undefined) { - const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); + if (this.activeItem.setPosition && this.activeItem.y !== undefined && this.activeItem.x !== undefined && this.targetDoc.x !== undefined && this.targetDoc.y !== undefined) { + const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); const time = 10; const ydiff = NumCast(this.activeItem.y) - NumCast(this.targetDoc.y); const xdiff = NumCast(this.activeItem.x) - NumCast(this.targetDoc.x); @@ -782,7 +835,7 @@ export class PresBox extends ViewBoxBaseComponent() { await timer(0.1); } } - } + }; //Command click @action @@ -797,13 +850,13 @@ export class PresBox extends ViewBoxBaseComponent() { this.removeFromArray(this._dragArray, doc); } this.selectPres(); - } + }; removeFromArray = (arr: any[], val: any) => { const index: number = arr.indexOf(val); const ret: any[] = arr.splice(index, 1); arr = ret; - } + }; //Shift click @action @@ -818,7 +871,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } this.selectPres(); - } + }; //regular click @action @@ -829,17 +882,17 @@ export class PresBox extends ViewBoxBaseComponent() { this._dragArray.splice(0, this._dragArray.length, drag); focus && this.selectElement(doc); selectPres && this.selectPres(); - } + }; modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean) => { if (cmdClick) this.multiSelect(doc, ref, drag); else if (shiftClick) this.shiftSelect(doc, ref, drag); else this.regularSelect(doc, ref, drag, focus); - } + }; static keyEventsWrapper = (e: KeyboardEvent) => { PresBox.Instance.keyEvents(e); - } + }; // Key for when the presentaiton is active @action @@ -847,57 +900,75 @@ export class PresBox extends ViewBoxBaseComponent() { if (e.target instanceof HTMLInputElement) return; let handled = false; const anchorNode = document.activeElement as HTMLDivElement; - if (anchorNode && anchorNode.className?.includes("lm_title")) return; + if (anchorNode && anchorNode.className?.includes('lm_title')) return; switch (e.key) { - case "Backspace": - if (this.layoutDoc.presStatus === "edit") { - undoBatch(action(() => { - for (const doc of Array.from(this._selectedArray.keys())) { - this.removeDocument(doc); - } - this._selectedArray.clear(); - this._eleArray.length = 0; - this._dragArray.length = 0; - }))(); + case 'Backspace': + if (this.layoutDoc.presStatus === 'edit') { + undoBatch( + action(() => { + for (const doc of Array.from(this._selectedArray.keys())) { + this.removeDocument(doc); + } + this._selectedArray.clear(); + this._eleArray.length = 0; + this._dragArray.length = 0; + }) + )(); handled = true; } break; - case "Escape": - if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.updateMinimize(); } - else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; } - else this.layoutDoc.presStatus = "edit"; + case 'Escape': + if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { + this.updateMinimize(); + } else if (this.layoutDoc.presStatus === 'edit') { + this._selectedArray.clear(); + this._eleArray.length = this._dragArray.length = 0; + } else this.layoutDoc.presStatus = 'edit'; if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; - case "Down": case "ArrowDown": - case "Right": case "ArrowRight": - if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { // TODO: update to work properly + case 'Down': + case 'ArrowDown': + case 'Right': + case 'ArrowRight': + if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { + // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1; this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); } else { this.next(); - if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } } handled = true; break; - case "Up": case "ArrowUp": - case "Left": case "ArrowLeft": - if (e.shiftKey && this.itemIndex !== 0) { // TODO: update to work properly + case 'Up': + case 'ArrowUp': + case 'Left': + case 'ArrowLeft': + if (e.shiftKey && this.itemIndex !== 0) { + // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1; this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); } else { this.back(); - if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } } handled = true; break; - case "Spacebar": case " ": + case 'Spacebar': + case ' ': if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex); else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; - case "a": - if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === "edit") { + case 'a': + if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === 'edit') { this._selectedArray.clear(); this.childDocs.forEach(doc => this._selectedArray.set(doc, undefined)); handled = true; @@ -909,10 +980,10 @@ export class PresBox extends ViewBoxBaseComponent() { e.stopPropagation(); e.preventDefault(); } - } + }; /** - * + * */ @action viewPaths = () => { @@ -920,7 +991,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (srcContext) { this.togglePath(srcContext); } - } + }; getAllIndexes = (arr: Doc[], val: Doc): number[] => { const indexes = []; @@ -928,7 +999,7 @@ export class PresBox extends ViewBoxBaseComponent() { arr[i] === val && indexes.push(i); } return indexes; - } + }; // Adds the index in the pres path graphically @computed get order() { @@ -936,61 +1007,64 @@ export class PresBox extends ViewBoxBaseComponent() { const docs: Doc[] = []; const presCollection = Cast(this.rootDoc.presCollection, Doc, null); const dv = DocumentManager.Instance.getDocumentView(presCollection); - this.childDocs.filter(doc => Cast(doc.presentationTargetDoc, Doc, null)).forEach((doc, index) => { - const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); - const srcContext = Cast(tagDoc.context, Doc, null); - const width = NumCast(tagDoc._width) / 10; - const height = Math.max(NumCast(tagDoc._height) / 10, 15); - const edge = Math.max(width, height); - const fontSize = edge * 0.8; - const gap = 2; - if (presCollection === srcContext) { - // Case A: Document is contained within the collection - if (docs.includes(tagDoc)) { - const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length; - docs.push(tagDoc); - order.push( -
this.selectElement(doc)}> -
{index + 1}
-
); - } else { + this.childDocs + .filter(doc => Cast(doc.presentationTargetDoc, Doc, null)) + .forEach((doc, index) => { + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + const srcContext = Cast(tagDoc.context, Doc, null); + const width = NumCast(tagDoc._width) / 10; + const height = Math.max(NumCast(tagDoc._height) / 10, 15); + const edge = Math.max(width, height); + const fontSize = edge * 0.8; + const gap = 2; + if (presCollection === srcContext) { + // Case A: Document is contained within the collection + if (docs.includes(tagDoc)) { + const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length; + docs.push(tagDoc); + order.push( +
this.selectElement(doc)}> +
{index + 1}
+
+ ); + } else { + docs.push(tagDoc); + order.push( +
this.selectElement(doc)}> +
{index + 1}
+
+ ); + } + } else if (doc.presPinView && presCollection === tagDoc && dv) { + // Case B: Document is presPinView and is presCollection + const scale: number = 1 / NumCast(doc.presPinViewScale); + const height: number = dv.props.PanelHeight() * scale; + const width: number = dv.props.PanelWidth() * scale; + const indWidth = width / 10; + const indHeight = Math.max(height / 10, 15); + const indEdge = Math.max(indWidth, indHeight); + const indFontSize = indEdge * 0.8; + const xLoc: number = NumCast(doc.presPinViewX) - width / 2; + const yLoc: number = NumCast(doc.presPinViewY) - height / 2; docs.push(tagDoc); order.push( -
this.selectElement(doc)}> -
{index + 1}
-
); + <> +
this.selectElement(doc)}> +
{index + 1}
+
+
+ + ); } - } else if (doc.presPinView && presCollection === tagDoc && dv) { - // Case B: Document is presPinView and is presCollection - const scale: number = 1 / NumCast(doc.presPinViewScale); - const height: number = dv.props.PanelHeight() * scale; - const width: number = dv.props.PanelWidth() * scale; - const indWidth = width / 10; - const indHeight = Math.max(height / 10, 15); - const indEdge = Math.max(indWidth, indHeight); - const indFontSize = indEdge * 0.8; - const xLoc: number = NumCast(doc.presPinViewX) - (width / 2); - const yLoc: number = NumCast(doc.presPinViewY) - (height / 2); - docs.push(tagDoc); - order.push( - <> -
this.selectElement(doc)} - > -
{index + 1}
-
-
- ); - } - }); + }); return order; } @@ -1003,37 +1077,39 @@ export class PresBox extends ViewBoxBaseComponent() { * collection) */ @computed get paths() { - let pathPoints = ""; + let pathPoints = ''; const presCollection = Cast(this.rootDoc.presCollection, Doc, null); this.childDocs.forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); const srcContext = Cast(tagDoc?.context, Doc, null); if (tagDoc && presCollection === srcContext) { - const n1x = NumCast(tagDoc.x) + (NumCast(tagDoc._width) / 2); - const n1y = NumCast(tagDoc.y) + (NumCast(tagDoc._height) / 2); - if (index = 0) pathPoints = n1x + "," + n1y; - else pathPoints = pathPoints + " " + n1x + "," + n1y; + const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2; + const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2; + if ((index = 0)) pathPoints = n1x + ',' + n1y; + else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } else if (doc.presPinView && presCollection === tagDoc) { const n1x = NumCast(doc.presPinViewX); const n1y = NumCast(doc.presPinViewY); - if (index = 0) pathPoints = n1x + "," + n1y; - else pathPoints = pathPoints + " " + n1x + "," + n1y; + if ((index = 0)) pathPoints = n1x + ',' + n1y; + else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } }); - return (); + return ( + + ); } // Converts seconds to ms and updates presTransition @@ -1042,8 +1118,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presTransition = timeInMS); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presTransition = timeInMS)); + }; // Converts seconds to ms and updates presTransition setZoom = (number: String, change?: number) => { @@ -1051,8 +1127,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1.5) scale = 1.5; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presZoom = scale); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presZoom = scale)); + }; // Converts seconds to ms and updates presDuration setDurationTime = (number: String, change?: number) => { @@ -1060,8 +1136,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presDuration = timeInMS); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presDuration = timeInMS)); + }; /** * When the movement dropdown is changes @@ -1069,7 +1145,7 @@ export class PresBox extends ViewBoxBaseComponent() { @undoBatch updateMovement = action((movement: any, all?: boolean) => { const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach((doc) => { + array.forEach(doc => { switch (movement) { case PresMovement.Zoom: //Pan and zoom doc.presMovement = PresMovement.Zoom; @@ -1081,7 +1157,8 @@ export class PresBox extends ViewBoxBaseComponent() { doc.presJump = true; doc.presMovement = PresMovement.Jump; break; - case PresMovement.None: default: + case PresMovement.None: + default: doc.presMovement = PresMovement.None; break; } @@ -1092,31 +1169,31 @@ export class PresBox extends ViewBoxBaseComponent() { @action updateHideBefore = (activeItem: Doc) => { activeItem.presHideBefore = !activeItem.presHideBefore; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideBefore = activeItem.presHideBefore); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); + }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presHideAfter = !activeItem.presHideAfter; - Array.from(this._selectedArray.keys()).forEach((doc) => doc.presHideAfter = activeItem.presHideAfter); - } + Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); + }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { activeItem.openDocument = !activeItem.openDocument; - Array.from(this._selectedArray.keys()).forEach((doc) => { + Array.from(this._selectedArray.keys()).forEach(doc => { doc.openDocument = activeItem.openDocument; }); - } + }; @undoBatch @action updateEffectDirection = (effect: any, all?: boolean) => { const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach((doc) => { - const tagDoc = doc;// Cast(doc.presentationTargetDoc, Doc, null); + array.forEach(doc => { + const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null); switch (effect) { case PresEffect.Left: tagDoc.presEffectDirection = PresEffect.Left; @@ -1130,19 +1207,20 @@ export class PresBox extends ViewBoxBaseComponent() { case PresEffect.Bottom: tagDoc.presEffectDirection = PresEffect.Bottom; break; - case PresEffect.Center: default: + case PresEffect.Center: + default: tagDoc.presEffectDirection = PresEffect.Center; break; } }); - } + }; @undoBatch @action updateEffect = (effect: any, all?: boolean) => { const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach((doc) => { - const tagDoc = doc;//Cast(doc.presentationTargetDoc, Doc, null); + array.forEach(doc => { + const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null); switch (effect) { case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; @@ -1159,19 +1237,20 @@ export class PresBox extends ViewBoxBaseComponent() { case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break; - case PresEffect.None: default: + case PresEffect.None: + default: tagDoc.presEffect = PresEffect.None; break; } }); - } + }; _batch: UndoManager.Batch | undefined = undefined; @computed get transitionDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection); + const isPresCollection: boolean = targetDoc === this.layoutDoc.presCollection; const isPinWithView: boolean = BoolCast(activeItem.presPinView); if (activeItem && targetDoc) { const type = targetDoc.type; @@ -1182,155 +1261,301 @@ export class PresBox extends ViewBoxBaseComponent() { const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None'; activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return ( -
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}> +
e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onClick={action(e => { + e.stopPropagation(); + this.openMovementDropdown = false; + this.openEffectDropdown = false; + })}>
Movement - {isPresCollection || (isPresCollection && isPinWithView) ? + {isPresCollection || (isPresCollection && isPinWithView) ? (
- {this.scrollable ? "Scroll to pinned view" : !isPinWithView ? "No movement" : "Pan & Zoom to pinned view"} + {this.scrollable ? 'Scroll to pinned view' : !isPinWithView ? 'No movement' : 'Pan & Zoom to pinned view'}
- : -
{ e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + ) : ( +
{ + e.stopPropagation(); + this.openMovementDropdown = !this.openMovementDropdown; + })} + style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> {this.setMovementName(activeItem.presMovement, activeItem)} - -
e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> -
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>Pan
-
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>Jump cut
+ +
e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}> +
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}> + None +
+
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}> + Pan {'&'} Zoom +
+
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}> + Pan +
+
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}> + Jump cut +
- } -
+ )} +
Zoom (% screen filled)
- this.setZoom(e.target.value))} />% + this.setZoom(e.target.value))} />%
this.setZoom(String(zoom), 0.1))}> - +
this.setZoom(String(zoom), -0.1))}> - +
- this._batch = UndoManager.StartBatch("presZoom")} + onPointerDown={() => (this._batch = UndoManager.StartBatch('presZoom'))} onPointerUp={() => this._batch?.end()} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); this.setZoom(e.target.value); - }} /> -
+ }} + /> +
Movement Speed
- e.stopPropagation()} - onChange={action((e) => this.setTransitionTime(e.target.value))} /> s + e.stopPropagation()} onChange={action(e => this.setTransitionTime(e.target.value))} /> s
this.setTransitionTime(String(transitionSpeed), 1000))}> - +
this.setTransitionTime(String(transitionSpeed), -1000))}> - +
- this._batch = UndoManager.StartBatch("presTransition")} + onPointerDown={() => (this._batch = UndoManager.StartBatch('presTransition'))} onPointerUp={() => this._batch?.end()} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); this.setTransitionTime(e.target.value); - }} /> -
+ }} + /> +
Fast
Medium
Slow
- Visibility {"&"} Duration + Visibility {'&'} Duration
- {isPresCollection ? (null) :
{"Hide before presented"}
}>
this.updateHideBefore(activeItem)}>Hide before
} - {isPresCollection ? (null) :
{"Hide after presented"}
}>
this.updateHideAfter(activeItem)}>Hide after
} -
{"Open in lightbox view"}
}>
this.updateOpenDoc(activeItem)}>Lightbox
+ {isPresCollection ? null : ( + +
{'Hide before presented'}
+ + }> +
this.updateHideBefore(activeItem)}> + Hide before +
+
+ )} + {isPresCollection ? null : ( + +
{'Hide after presented'}
+ + }> +
this.updateHideAfter(activeItem)}> + Hide after +
+
+ )} + +
{'Open in lightbox view'}
+ + }> +
this.updateOpenDoc(activeItem)}> + Lightbox +
+
- {(type === DocumentType.AUDIO || type === DocumentType.VID) ? (null) : <> -
-
Slide Duration
-
- e.stopPropagation()} - onChange={action((e) => this.setDurationTime(e.target.value))} /> s + {type === DocumentType.AUDIO || type === DocumentType.VID ? null : ( + <> +
+
Slide Duration
+
+ e.stopPropagation()} onChange={action(e => this.setDurationTime(e.target.value))} /> s +
+
+
this.setDurationTime(String(duration), 1000))}> + +
+
this.setDurationTime(String(duration), -1000))}> + +
+
+
+ { + this._batch = UndoManager.StartBatch('presDuration'); + }} + onPointerUp={() => { + if (this._batch) this._batch.end(); + }} + onChange={(e: React.ChangeEvent) => { + e.stopPropagation(); + this.setDurationTime(e.target.value); + }} + /> +
+
Short
+
Medium
+
Long
-
-
this.setDurationTime(String(duration), 1000))}> - + + )} +
+ {isPresCollection ? null : ( +
+ Effects +
{ + e.stopPropagation(); + this.openEffectDropdown = !this.openEffectDropdown; + })} + style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + {effect.toString()} + +
e.stopPropagation()}> +
e.stopPropagation()} + onClick={() => this.updateEffect(PresEffect.None)}> + None
-
this.setDurationTime(String(duration), -1000))}> - +
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}> + Fade In +
+
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}> + Flip +
+
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}> + Rotate +
+
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}> + Bounce +
+
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}> + Roll
- { this._batch = UndoManager.StartBatch("presDuration"); }} - onPointerUp={() => { if (this._batch) this._batch.end(); }} - onChange={(e: React.ChangeEvent) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} - /> -
-
Short
-
Medium
-
Long
+
+
Effect direction
+
{this.effectDirection}
- } -
- {isPresCollection ? (null) :
- Effects -
{ e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> - {effect} - -
e.stopPropagation()}> -
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce
-
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll
-
-
-
-
Effect direction
-
- {this.effectDirection} +
+ {'Enter from left'}
}> +
this.updateEffectDirection(PresEffect.Left)}> + +
+ + {'Enter from right'}
}> +
this.updateEffectDirection(PresEffect.Right)}> + +
+ + +
{'Enter from top'}
+ + }> +
this.updateEffectDirection(PresEffect.Top)}> + +
+
+ +
{'Enter from bottom'}
+ + }> +
this.updateEffectDirection(PresEffect.Bottom)}> + +
+
+ +
{'Enter from center'}
+ + }> +
this.updateEffectDirection(PresEffect.Center)}>
+
-
-
{"Enter from left"}
}>
this.updateEffectDirection(PresEffect.Left)}>
-
{"Enter from right"}
}>
this.updateEffectDirection(PresEffect.Right)}>
-
{"Enter from top"}
}>
this.updateEffectDirection(PresEffect.Top)}>
-
{"Enter from bottom"}
}>
this.updateEffectDirection(PresEffect.Bottom)}>
-
{"Enter from center"}
}>
this.updateEffectDirection(PresEffect.Center)}>
-
-
} + )}
this.applyTo(this.childDocs)}> Apply to all
-
+
); } } @@ -1338,11 +1563,21 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get effectDirection(): string { let effect = ''; switch (this.activeItem.presEffectDirection) { - case 'left': effect = "Enter from left"; break; - case 'right': effect = "Enter from right"; break; - case 'top': effect = "Enter from top"; break; - case 'bottom': effect = "Enter from bottom"; break; - default: effect = "Enter from center"; break; + case 'left': + effect = 'Enter from left'; + break; + case 'right': + effect = 'Enter from right'; + break; + case 'top': + effect = 'Enter from top'; + break; + case 'bottom': + effect = 'Enter from bottom'; + break; + default: + effect = 'Enter from center'; + break; } return effect; } @@ -1355,7 +1590,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.updateMovement(activeItem.presMovement, true); this.updateEffect(activeItem.presEffect, true); this.updateEffectDirection(activeItem.presEffectDirection, true); - array.forEach((doc) => { + array.forEach(doc => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (tagDoc && targetDoc) { @@ -1365,63 +1600,86 @@ export class PresBox extends ViewBoxBaseComponent() { curDoc.presHideAfter = activeItem.presHideAfter; } }); - } + }; @computed get presPinViewOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - const presPinWithViewIcon = ; + const presPinWithViewIcon = ; return ( <> - {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)} + {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null}
-
{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}
}>
{ - activeItem.presPinView = !activeItem.presPinView; - targetDoc.presPinView = activeItem.presPinView; - if (activeItem.presPinView) { - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { - const scroll = targetDoc._scrollTop; - activeItem.presPinView = true; - activeItem.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeItem.presStartTime = targetDoc._currentTimecode; - activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinView = true; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeItem.presPinClipWidth = width; - activeItem.presPinView = true; + +
{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}
+ + }> +
{ + activeItem.presPinView = !activeItem.presPinView; + targetDoc.presPinView = activeItem.presPinView; + if (activeItem.presPinView) { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { + const scroll = targetDoc._scrollTop; + activeItem.presPinView = true; + activeItem.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { + activeItem.presStartTime = targetDoc._currentTimecode; + activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; + } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinView = true; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeItem.presPinClipWidth = width; + activeItem.presPinView = true; + } } - } - }}>{presPinWithViewIcon}
- {activeItem.presPinView ?
{"Update the pinned view with the view of the selected document"}
}>
{ - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { - const scroll = targetDoc._scrollTop; - activeItem.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeItem.presStartTime = targetDoc._currentTimecode; - activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const clipWidth = targetDoc._clipWidth; - activeItem.presPinClipWidth = clipWidth; - } else { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } - }}>Update
: (null)} + }}> + {presPinWithViewIcon} +
+
+ {activeItem.presPinView ? ( + +
{'Update the pinned view with the view of the selected document'}
+ + }> +
{ + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { + const scroll = targetDoc._scrollTop; + activeItem.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { + activeItem.presStartTime = targetDoc._currentTimecode; + activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const clipWidth = targetDoc._clipWidth; + activeItem.presPinClipWidth = clipWidth; + } else { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } + }}> + Update +
+
+ ) : null}
); @@ -1432,38 +1690,58 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; return ( <> - {this.panable ?
-
-
Pan X
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> + {this.panable ? ( +
+
+
Pan X
+
+ e.stopPropagation()} + onChange={action((e: React.ChangeEvent) => { + const val = e.target.value; + activeItem.presPinViewX = Number(val); + })} + /> +
-
-
-
Pan Y
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> +
+
Pan Y
+
+ e.stopPropagation()} + onChange={action((e: React.ChangeEvent) => { + const val = e.target.value; + activeItem.presPinViewY = Number(val); + })} + /> +
-
-
-
Scale
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> +
+
Scale
+
+ e.stopPropagation()} + onChange={action((e: React.ChangeEvent) => { + const val = e.target.value; + activeItem.presPinViewScale = Number(val); + })} + /> +
-
: (null)} + ) : null} ); } @@ -1473,18 +1751,26 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; return ( <> - {this.scrollable ?
-
-
Scroll
-
- e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} /> + {this.scrollable ? ( +
+
+
Scroll
+
+ e.stopPropagation()} + onChange={action((e: React.ChangeEvent) => { + const val = e.target.value; + activeItem.presPinViewScroll = Number(val); + })} + /> +
-
: (null)} + ) : null} ); } @@ -1494,13 +1780,13 @@ export class PresBox extends ViewBoxBaseComponent() { const list = this.childDocs.map((doc, i) => { if (i > this.itemIndex) { return ( - + ); } }); - return ( - list - ); + return list; } @computed get mediaOptionsDropdown() { @@ -1510,25 +1796,29 @@ export class PresBox extends ViewBoxBaseComponent() { const clipEnd: number = NumCast(activeItem.clipEnd); const duration = Math.round(NumCast(activeItem[`${Doc.LayoutFieldKey(activeItem)}-duration`]) * 10); const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc); - const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + ". " + this.childDocs[mediaStopDocInd - 1].title : ""; + const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + '. ' + this.childDocs[mediaStopDocInd - 1].title : ''; if (activeItem && targetDoc) { return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
- Start {"&"} End Time -
-
+ Start {'&'} End Time +
+
Start time (s)
-
- + e.stopPropagation()} - onChange={action((e: React.ChangeEvent) => { activeItem.presStartTime = Number(e.target.value); })} + onChange={action((e: React.ChangeEvent) => { + activeItem.presStartTime = Number(e.target.value); + })} />
@@ -1544,24 +1834,33 @@ export class PresBox extends ViewBoxBaseComponent() {
End time (s)
-
- + e.stopPropagation()} style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }} - type="number" value={NumCast(activeItem.presEndTime)} - onChange={action((e: React.ChangeEvent) => { activeItem.presEndTime = Number(e.target.value); })} + type="number" + value={NumCast(activeItem.presEndTime)} + onChange={action((e: React.ChangeEvent) => { + activeItem.presEndTime = Number(e.target.value); + })} />
- { - this._batch = UndoManager.StartBatch("presEndTime"); - const endBlock = document.getElementById("endTime"); + this._batch = UndoManager.StartBatch('presEndTime'); + const endBlock = document.getElementById('endTime'); if (endBlock) { endBlock.style.color = Colors.LIGHT_GRAY; endBlock.style.backgroundColor = Colors.MEDIUM_BLUE; @@ -1569,7 +1868,7 @@ export class PresBox extends ViewBoxBaseComponent() { }} onPointerUp={() => { this._batch?.end(); - const endBlock = document.getElementById("endTime"); + const endBlock = document.getElementById('endTime'); if (endBlock) { endBlock.style.color = Colors.BLACK; endBlock.style.backgroundColor = Colors.LIGHT_GRAY; @@ -1578,14 +1877,20 @@ export class PresBox extends ViewBoxBaseComponent() { onChange={(e: React.ChangeEvent) => { e.stopPropagation(); activeItem.presEndTime = Number(e.target.value); - }} /> - + { - this._batch = UndoManager.StartBatch("presStartTime"); - const startBlock = document.getElementById("startTime"); + this._batch = UndoManager.StartBatch('presStartTime'); + const startBlock = document.getElementById('startTime'); if (startBlock) { startBlock.style.color = Colors.LIGHT_GRAY; startBlock.style.backgroundColor = Colors.MEDIUM_BLUE; @@ -1593,7 +1898,7 @@ export class PresBox extends ViewBoxBaseComponent() { }} onPointerUp={() => { this._batch?.end(); - const startBlock = document.getElementById("startTime"); + const startBlock = document.getElementById('startTime'); if (startBlock) { startBlock.style.color = Colors.BLACK; startBlock.style.backgroundColor = Colors.LIGHT_GRAY; @@ -1602,9 +1907,10 @@ export class PresBox extends ViewBoxBaseComponent() { onChange={(e: React.ChangeEvent) => { e.stopPropagation(); activeItem.presStartTime = Number(e.target.value); - }} /> + }} + />
-
+
{clipStart} s
{clipEnd} s
@@ -1615,38 +1921,22 @@ export class PresBox extends ViewBoxBaseComponent() {
Start playing:
- activeItem.mediaStart = "manual"} - checked={activeItem.mediaStart === "manual"} - /> + (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} />
On click
- activeItem.mediaStart = "auto"} - checked={activeItem.mediaStart === "auto"} - /> + (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} />
Automatically
Stop playing:
- activeItem.mediaStop = "manual"} - checked={activeItem.mediaStop === "manual"} - /> + (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} />
At audio end time
- activeItem.mediaStop = "auto"} - checked={activeItem.mediaStop === "auto"} - /> + (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} />
On slide change
{/*
@@ -1670,7 +1960,7 @@ export class PresBox extends ViewBoxBaseComponent() {
-
+
); } } @@ -1678,84 +1968,137 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get newDocumentToolbarDropdown() { return (
-
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onPointerDown={e => e.stopPropagation()}>
-
{ this.layout = 'blank'; this.createNewSlide(this.layout); })} /> -
{ this.layout = 'title'; this.createNewSlide(this.layout); })}> +
{ + this.layout = 'blank'; + this.createNewSlide(this.layout); + })} + /> +
{ + this.layout = 'title'; + this.createNewSlide(this.layout); + })}>
Title
Subtitle
-
{ this.layout = 'header'; this.createNewSlide(this.layout); })}> -
Section header
+
{ + this.layout = 'header'; + this.createNewSlide(this.layout); + })}> +
+ Section header +
-
{ this.layout = 'content'; this.createNewSlide(this.layout); })}> -
Title
+
{ + this.layout = 'content'; + this.createNewSlide(this.layout); + })}> +
+ Title +
Text goes here
-
+
); } @observable openLayouts: boolean = false; @observable addFreeform: boolean = true; - @observable layout: string = ""; - @observable title: string = ""; + @observable layout: string = ''; + @observable title: string = ''; @computed get newDocumentDropdown() { return (
-
e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
Slide Title:

- { + { e.stopPropagation(); e.preventDefault(); - runInAction(() => this.title = e.target.value); - }}> - + runInAction(() => (this.title = e.target.value)); + }}>
Choose type:
-
this.addFreeform = !this.addFreeform)}>Text
-
this.addFreeform = !this.addFreeform)}>Freeform
+
(this.addFreeform = !this.addFreeform))}> + Text +
+
(this.addFreeform = !this.addFreeform))}> + Freeform +
-
+
Preset layouts:
-
this.layout = 'blank')} /> -
this.layout = 'title')}> +
(this.layout = 'blank'))} /> +
(this.layout = 'title'))}>
Title
Subtitle
-
this.layout = 'header')}> -
Section header
+
(this.layout = 'header'))}> +
+ Section header +
-
this.layout = 'content')}> -
Title
+
(this.layout = 'content'))}> +
+ Title +
Text goes here
-
this.layout = 'twoColumns')}> -
Title
-
Column one text
-
Column two text
+
(this.layout = 'twoColumns'))}> +
+ Title +
+
+ Column one text +
+
+ Column two text +
-
this.openLayouts = !this.openLayouts)}> - +
(this.openLayouts = !this.openLayouts))}> +
-
this.createNewSlide(this.layout, this.title, this.addFreeform)}> +
this.createNewSlide(this.layout, this.title, this.addFreeform)}> Create New Slide
-
+
); } @@ -1763,7 +2106,7 @@ export class PresBox extends ViewBoxBaseComponent() { let doc = undefined; if (layout) doc = this.createTemplate(layout); if (freeform && layout) doc = this.createTemplate(layout, title); - if (!freeform && !layout) doc = Docs.Create.TextDocument("", { _nativeWidth: 400, _width: 225, title: title }); + if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title }); if (doc) { const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const data = Cast(presCollection?.data, listSpec(Doc)); @@ -1773,10 +2116,10 @@ export class PresBox extends ViewBoxBaseComponent() { TabDocView.PinDoc(doc); this.gotoDocument(this.childDocs.length, this.activeItem); } else { - this.props.addDocTab(doc, "add:right"); + this.props.addDocTab(doc, 'add:right'); } } - } + }; createTemplate = (layout: string, input?: string) => { const activeItem: Doc = this.activeItem; @@ -1788,43 +2131,59 @@ export class PresBox extends ViewBoxBaseComponent() { y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20; } let doc = undefined; - const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24pt", }); - const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" }); - const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" }); - const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" }); - const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" }); - const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" }); - const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" }); + const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' }); + const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' }); + const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' }); + const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' }); + const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' }); + const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' }); + const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' }); switch (layout) { case 'blank': - doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y }); + doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y }); break; case 'title': - doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'header': - doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'content': - doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'twoColumns': - doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; default: break; } return doc; - } + }; // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @computed get presentDropdown() { return ( -
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> -
{ this.updateMinimize(); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }))}> +
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
{ + this.updateMinimize(); + this.turnOffEdit(true); + this.gotoDocument(this.itemIndex, this.activeItem); + }) + )}> Mini-player
-
{ this.layoutDoc.presStatus = "manual"; this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }))}> +
{ + this.layoutDoc.presStatus = 'manual'; + this.turnOffEdit(true); + this.gotoDocument(this.itemIndex, this.activeItem); + }) + )}> Sidebar player
@@ -1835,7 +2194,7 @@ export class PresBox extends ViewBoxBaseComponent() { @action nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc._currentFrame, "number", null); + const currentFrame = Cast(tagDoc._currentFrame, 'number', null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); @@ -1845,19 +2204,19 @@ export class PresBox extends ViewBoxBaseComponent() { CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); - } + }; @action prevKeyframe = (tagDoc: Doc, actItem: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); - const currentFrame = Cast(tagDoc._currentFrame, "number", null); + const currentFrame = Cast(tagDoc._currentFrame, 'number', null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); - } + }; /** * Returns the collection type as a string for headers @@ -1868,15 +2227,33 @@ export class PresBox extends ViewBoxBaseComponent() { let type: string = ''; if (activeItem) { switch (targetDoc.type) { - case DocumentType.PDF: type = "PDF"; break; - case DocumentType.RTF: type = "Text node"; break; - case DocumentType.COL: type = "Collection"; break; - case DocumentType.AUDIO: type = "Audio"; break; - case DocumentType.VID: type = "Video"; break; - case DocumentType.IMG: type = "Image"; break; - case DocumentType.WEB: type = "Web page"; break; - case DocumentType.MAP: type = "Map"; break; - default: type = "Other node"; break; + case DocumentType.PDF: + type = 'PDF'; + break; + case DocumentType.RTF: + type = 'Text node'; + break; + case DocumentType.COL: + type = 'Collection'; + break; + case DocumentType.AUDIO: + type = 'Audio'; + break; + case DocumentType.VID: + type = 'Video'; + break; + case DocumentType.IMG: + type = 'Image'; + break; + case DocumentType.WEB: + type = 'Web page'; + break; + case DocumentType.MAP: + type = 'Map'; + break; + default: + type = 'Other node'; + break; } } return type; @@ -1885,66 +2262,122 @@ export class PresBox extends ViewBoxBaseComponent() { @observable private openActiveColorPicker: boolean = false; @observable private openViewedColorPicker: boolean = false; - - @computed get progressivizeDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { - const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black"; - const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black"; + const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black'; + const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black'; return (
-
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> +
e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onPointerDown={e => e.stopPropagation()}>
{this.stringType} selected -
-
Contents
-
Edit
+
+
+ Contents +
+
+ Edit +
-
+
Active text color
-
{ this.openActiveColorPicker = !this.openActiveColorPicker; })}> -
+
{ + this.openActiveColorPicker = !this.openActiveColorPicker; + })}>
{this.activeColorPicker} -
+
Viewed font color
-
this.openViewedColorPicker = !this.openViewedColorPicker)}> -
+
(this.openViewedColorPicker = !this.openViewedColorPicker))}>
{this.viewedColorPicker} -
-
Zoom
-
Edit
+
+
+ Zoom +
+
+ Edit +
-
-
Scroll
-
Edit
+
+
+ Scroll +
+
+ Edit +
Frames
-
{ e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}> - +
{ + e.stopPropagation(); + this.prevKeyframe(targetDoc, activeItem); + }}> +
-
targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing)} > +
(targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}> {NumCast(targetDoc._currentFrame)}
-
{ e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}> - +
{ + e.stopPropagation(); + this.nextKeyframe(targetDoc, activeItem); + }}> +
-
{"Last frame"}
}>
{NumCast(targetDoc.lastFrame)}
+ +
{'Last frame'}
+ + }> +
{NumCast(targetDoc.lastFrame)}
+
{this.frameListHeader} {this.frameList}
-
console.log(" TODO: play frames")}>Play
+
console.log(' TODO: play frames')}> + Play +
@@ -1958,37 +2391,41 @@ export class PresBox extends ViewBoxBaseComponent() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const val = String(color.hex); - targetDoc["pres-text-color"] = val; + targetDoc['pres-text-color'] = val; return true; - } + }; @undoBatch @action switchPresented = (color: ColorState) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const val = String(color.hex); - targetDoc["pres-text-viewed-color"] = val; + targetDoc['pres-text-viewed-color'] = val; return true; - } + }; @computed get activeColorPicker() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - return !this.openActiveColorPicker ? (null) : ; + return !this.openActiveColorPicker ? null : ( + + ); } @computed get viewedColorPicker() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - return !this.openViewedColorPicker ? (null) : ; + return !this.openViewedColorPicker ? null : ( + + ); } @action @@ -1999,7 +2436,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (srcContext) this.togglePath(srcContext, true); } // Turn off the progressivize editors for each document - this.childDocs.forEach((doc) => { + this.childDocs.forEach(doc => { doc.editSnapZoomProgressivize = false; doc.editZoomProgressivize = false; const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); @@ -2008,7 +2445,7 @@ export class PresBox extends ViewBoxBaseComponent() { // targetDoc.editScrollProgressivize = false; } }); - } + }; //Toggle whether the user edits or not @action @@ -2016,14 +2453,15 @@ export class PresBox extends ViewBoxBaseComponent() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editZoomProgressivize) { - if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; targetDoc.zoomProgressivize = true; + if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; + targetDoc.zoomProgressivize = true; targetDoc.editZoomProgressivize = true; activeItem.editZoomProgressivize = true; } else { targetDoc.editZoomProgressivize = false; activeItem.editZoomProgressivize = false; } - } + }; //Toggle whether the user edits or not @action @@ -2031,12 +2469,15 @@ export class PresBox extends ViewBoxBaseComponent() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editScrollProgressivize) { - if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; activeItem.scrollProgressivize = true; } + if (!targetDoc.scrollProgressivize) { + targetDoc.scrollProgressivize = true; + activeItem.scrollProgressivize = true; + } targetDoc.editScrollProgressivize = true; } else { targetDoc.editScrollProgressivize = false; } - } + }; //Progressivize Zoom @action @@ -2052,7 +2493,7 @@ export class PresBox extends ViewBoxBaseComponent() { targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } - } + }; //Progressivize Zoom @action @@ -2068,7 +2509,7 @@ export class PresBox extends ViewBoxBaseComponent() { targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } - } + }; //Progressivize Child Docs @action @@ -2077,12 +2518,15 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; targetDoc._currentFrame = targetDoc.lastFrame; if (!targetDoc.editProgressivize) { - if (!activeItem.presProgressivize) { activeItem.presProgressivize = true; targetDoc.presProgressivize = true; } + if (!activeItem.presProgressivize) { + activeItem.presProgressivize = true; + targetDoc.presProgressivize = true; + } targetDoc.editProgressivize = true; } else { targetDoc.editProgressivize = false; } - } + }; @action progressivizeChild = (e: React.MouseEvent) => { @@ -2104,40 +2548,48 @@ export class PresBox extends ViewBoxBaseComponent() { targetDoc._currentFrame = 0; targetDoc.keyFrameEditing = true; } - } + }; @action checkMovementLists = (doc: Doc, xlist: any, ylist: any) => { const x: List = xlist; const y: List = ylist; const tags: JSX.Element[] = []; - let pathPoints = ""; //List of all of the pathpoints that need to be added + let pathPoints = ''; //List of all of the pathpoints that need to be added for (let i = 0; i < x.length - 1; i++) { if (y[i] || x[i]) { - if (i === 0) pathPoints = (x[i] - 11) + "," + (y[i] + 33); - else pathPoints = pathPoints + " " + (x[i] - 11) + "," + (y[i] + 33); - tags.push(
{i}
); + if (i === 0) pathPoints = x[i] - 11 + ',' + (y[i] + 33); + else pathPoints = pathPoints + ' ' + (x[i] - 11) + ',' + (y[i] + 33); + tags.push( +
+ {i} +
+ ); } } - tags.push(); + tags.push( + + + + ); return tags; - } + }; @observable toggleDisplayMovement = (doc: Doc) => { if (doc.displayMovement) doc.displayMovement = false; else doc.displayMovement = true; - } + }; @action checkList = (doc: Doc, list: any): number => { @@ -2149,22 +2601,54 @@ export class PresBox extends ViewBoxBaseComponent() { x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1]; return x[NumCast(doc._currentFrame)]; } else return 100; - } + }; @computed get progressivizeChildDocs() { const targetDoc: Doc = this.targetDoc; const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); const tags: JSX.Element[] = []; docs.forEach((doc, index) => { - if (doc["x-indexed"] && doc["y-indexed"]) { - tags.push(
{this.checkMovementLists(doc, doc["x-indexed"], doc["y-indexed"])}
); + if (doc['x-indexed'] && doc['y-indexed']) { + tags.push(
{this.checkMovementLists(doc, doc['x-indexed'], doc['y-indexed'])}
); } tags.push( -
{ if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : "#c8c8c8", top: NumCast(doc.y), left: NumCast(doc.x) }}> -
{ e.stopPropagation(); this.prevAppearFrame(doc, index); }} />
-
{doc.appearFrame}
-
{ e.stopPropagation(); this.nextAppearFrame(doc, index); }} />
-
); +
{ + if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; + }} + onPointerOver={() => { + if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; + }} + onClick={e => { + this.toggleDisplayMovement(doc); + e.stopPropagation(); + }} + style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : '#c8c8c8', top: NumCast(doc.y), left: NumCast(doc.x) }}> +
+ { + e.stopPropagation(); + this.prevAppearFrame(doc, index); + }} + /> +
+
{NumCast(doc.appearFrame)}
+
+ { + e.stopPropagation(); + this.nextAppearFrame(doc, index); + }} + /> +
+
+ ); }); return tags; } @@ -2173,25 +2657,25 @@ export class PresBox extends ViewBoxBaseComponent() { nextAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, "number", null); + const appearFrame = Cast(doc.appearFrame, 'number', null); if (appearFrame === undefined) { doc.appearFrame = 0; } doc.appearFrame = appearFrame + 1; - this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame)); - } + this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); + }; @action prevAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); - const appearFrame = Cast(doc.appearFrame, "number", null); + const appearFrame = Cast(doc.appearFrame, 'number', null); if (appearFrame === undefined) { doc.appearFrame = 0; } doc.appearFrame = Math.max(0, appearFrame - 1); - this.updateOpacityList(doc["opacity-indexed"], NumCast(doc.appearFrame)); - } + this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); + }; @action updateOpacityList = (list: any, frame: number) => { @@ -2216,10 +2700,10 @@ export class PresBox extends ViewBoxBaseComponent() { } list = x; } - } + }; @computed get moreInfoDropdown() { - return (
); + return
; } @computed @@ -2235,28 +2719,36 @@ export class PresBox extends ViewBoxBaseComponent() { } else { CurrentUserUtils.propertiesWidth = 250; } - } + }; @computed get toolbar() { - const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; - const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel"; + const propIcon = CurrentUserUtils.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = CurrentUserUtils.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); + const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation; const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; - return (mode === CollectionViewType.Carousel3D) ? (null) : ( + return mode === CollectionViewType.Carousel3D ? null : (
{/*
{"Add new slide"}
}>
this.newDocumentTools = !this.newDocumentTools)}>
*/} -
{"View paths"}
}> -
1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}> - + +
{'View paths'}
+ + }> +
1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} + className={'toolbar-button'} + onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}> +
- {isMini ? (null) : + {isMini ? null : ( <>
{/*
{this._expandBoolean ? "Minimize all" : "Expand all"}
}> @@ -2267,18 +2759,28 @@ export class PresBox extends ViewBoxBaseComponent() {
*/} -
{presKeyEvents ? "Keys are active" : "Keys are not active - click anywhere on the presentation trail to activate keys"}
}> + +
{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}
+ + }>
- +
-
{propTitle}
}> + +
{propTitle}
+ + }>
- 0 ? activeColor : inactiveColor }} /> + 0 ? activeColor : inactiveColor }} />
- } + )}
); } @@ -2292,35 +2794,45 @@ export class PresBox extends ViewBoxBaseComponent() { const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; return ( -
- {isMini ? (null) : } +
+ {isMini ? null : ( + + )}
- -
+
{ if (this.childDocs.length) { - this.layoutDoc.presStatus = "manual"; + this.layoutDoc.presStatus = 'manual'; this.gotoDocument(this.itemIndex, this.activeItem); } })}> - -
200 ? "inline-flex" : "none" }}>  Present
+ +
200 ? 'inline-flex' : 'none' }}>  Present
- {(mode === CollectionViewType.Carousel3D || isMini) ? (null) :
{ - if (this.childDocs.length) this.presentTools = !this.presentTools; - }))}> - - {this.presentDropdown} -
} + {mode === CollectionViewType.Carousel3D || isMini ? null : ( +
{ + if (this.childDocs.length) this.presentTools = !this.presentTools; + })}> + + {this.presentDropdown} +
+ )} {this.playButtons}
@@ -2332,7 +2844,7 @@ export class PresBox extends ViewBoxBaseComponent() { getList = (list: any): List => { const x: List = list; return x; - } + }; @action updateList = (list: any): List => { @@ -2341,7 +2853,7 @@ export class PresBox extends ViewBoxBaseComponent() { x.length + 1; x[x.length - 1] = NumCast(targetDoc._scrollY); return x; - } + }; @action newFrame = () => { @@ -2350,7 +2862,7 @@ export class PresBox extends ViewBoxBaseComponent() { const type: string = StrCast(targetDoc.type); if (!activeItem.frameList) activeItem.frameList = new List(); switch (type) { - case (DocumentType.PDF || DocumentType.RTF || DocumentType.WEB): + case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB: this.updateList(activeItem.frameList); break; case DocumentType.COL: @@ -2358,20 +2870,46 @@ export class PresBox extends ViewBoxBaseComponent() { default: break; } - } + }; @computed get frameListHeader() { - return (
-   Frames {this.panable ? Panable : this.scrollable ? Scrollable : (null)} -
-
{"Add frame by example"}
}>
{ e.stopPropagation(); this.newFrame(); }}> - e.stopPropagation()} /> -
-
{"Edit in collection"}
}>
{ e.stopPropagation(); console.log('New frame'); }}> - e.stopPropagation()} /> -
+ return ( +
+   Frames {this.panable ? Panable : this.scrollable ? Scrollable : null} +
+ +
{'Add frame by example'}
+ + }> +
{ + e.stopPropagation(); + this.newFrame(); + }}> + e.stopPropagation()} /> +
+
+ +
{'Edit in collection'}
+ + }> +
{ + e.stopPropagation(); + console.log('New frame'); + }}> + e.stopPropagation()} /> +
+
+
-
); + ); } @computed get frameList() { @@ -2379,66 +2917,118 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; const frameList: List = this.getList(activeItem.frameList); if (frameList) { - const frameItems = frameList.map((value) => -
- -
- ); - return ( - -
- {frameItems} -
- ); - } else return (null); - + const frameItems = frameList.map(value =>
); + return
{frameItems}
; + } else return null; } @computed get playButtonFrames() { const targetDoc: Doc = this.targetDoc; return ( <> - {this.targetDoc ?
= 0 ? "inline-flex" : "none" }}> -
{targetDoc._currentFrame}
-
-
{targetDoc.lastFrame}
-
: null} + {this.targetDoc ? ( +
= 0 ? 'inline-flex' : 'none' }}> +
{NumCast(targetDoc._currentFrame)}
+
+
{NumCast(targetDoc.lastFrame)}
+
+ ) : null} ); } @computed get playButtons() { - const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); - const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); + const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; + const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; // Case 1: There are still other frames and should go through all frames before going to next slide - return (
-
{"Loop"}
}>
this.layoutDoc.presLoop = !this.layoutDoc.presLoop}>
-
-
{ this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}>
-
{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}
}>
-
{ this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}>
-
-
{"Click to return to 1st slide"}
}>
this.gotoDocument(0, this.activeItem)}>1
-
this.gotoDocument(0, this.activeItem)} - style={{ display: this.props.PanelWidth() > 250 ? "inline-flex" : "none" }}> - Slide {this.itemIndex + 1} / {this.childDocs.length} - {this.playButtonFrames} + return ( +
+ +
{'Loop'}
+ + }> +
(this.layoutDoc.presLoop = !this.layoutDoc.presLoop)}> + +
+
+
+
{ + this.back(); + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } + }}> + +
+ +
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
+ + }> +
+ +
+
+
{ + this.next(); + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } + }}> + +
+
+ +
{'Click to return to 1st slide'}
+ + }> +
this.gotoDocument(0, this.activeItem)}> + 1 +
+
+
this.gotoDocument(0, this.activeItem)} style={{ display: this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}> + Slide {this.itemIndex + 1} / {this.childDocs.length} + {this.playButtonFrames} +
+
+ {this.props.PanelWidth() > 250 ? ( +
{ + this.layoutDoc.presStatus = 'edit'; + clearTimeout(this._presTimer); + }) + )}> + EXIT +
+ ) : ( +
(this.layoutDoc.presStatus = 'edit')))}> + +
+ )}
-
- {this.props.PanelWidth() > 250 ?
{ this.layoutDoc.presStatus = "edit"; clearTimeout(this._presTimer); }))}>EXIT
- :
this.layoutDoc.presStatus = "edit"))}> - -
} -
); + ); } @action startOrPause = () => { if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex); else this.pauseAutoPres(); - } + }; @action prevClicked = (e: PointerEvent) => { @@ -2447,7 +3037,7 @@ export class PresBox extends ViewBoxBaseComponent() { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } - } + }; @action nextClicked = (e: PointerEvent) => { @@ -2456,35 +3046,35 @@ export class PresBox extends ViewBoxBaseComponent() { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } - } + }; @undoBatch @action exitClicked = (e: PointerEvent) => { this.updateMinimize(); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); - } + }; @action startMarqueeCreateSlide = () => { PresBox.startMarquee = true; - } + }; AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => { var indexNum = 0; for (let i = 0; i < index.length; i++) { - indexNum += (index[i] * (10 ** (-i))); + indexNum += index[i] * 10 ** -i; } if (this._treeViewMap.get(treeViewDoc) !== indexNum) { this._treeViewMap.set(treeViewDoc, indexNum); const sorted = this.sort(this._treeViewMap); const curList = DocListCast(this.dataDoc[this.presFieldKey]); - if (sorted.length !== curList.length || sorted.some((doc,ind) => doc !== curList[ind])) { + if (sorted.length !== curList.length || sorted.some((doc, ind) => doc !== curList[ind])) { this.dataDoc[this.presFieldKey] = new List(sorted); // this is a flat array of Docs } } return this.childDocs; - } + }; RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => { if (!this._unmounting && this.isTree) { @@ -2492,10 +3082,10 @@ export class PresBox extends ViewBoxBaseComponent() { this.dataDoc[this.presFieldKey] = new List(this.sort(this._treeViewMap)); } return this.childDocs; - } + }; // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1]) - sort = (treeViewMap: Map) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0).map(kv => kv[0]); + sort = (treeViewMap: Map) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]); render() { // calling this method for keyEvents @@ -2503,42 +3093,73 @@ export class PresBox extends ViewBoxBaseComponent() { // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); - const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); - const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); - return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc) ? + const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation; + const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; + const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; + return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc) ? (
e.stopPropagation()}> -
-
{"Loop"}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.layoutDoc.presLoop = !this.layoutDoc.presLoop, false, false)}>
+
+ +
{'Loop'}
+ + }> +
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}> + +
+
-
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
-
{this.layoutDoc.presStatus === PresStatus.Autoplay ? "Pause" : "Autoplay"}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}>
-
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}>
+
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}> + +
+ +
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
+ + }> +
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}> + +
+
+
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}> + +
-
{"Click to return to 1st slide"}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}>1
+ +
{'Click to return to 1st slide'}
+ + }> +
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}> + 1 +
+
Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames}
-
- setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>EXIT
+
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}> + EXIT +
-
- : -
+
+ ) : ( +
{this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown}
- {mode !== CollectionViewType.Invalid ? - () { AddToMap={this.AddToMap} RemFromMap={this.RemFromMap} hierarchyIndex={[]} - /> : (null) - } + /> + ) : null}
- { // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack - {"Click on document to pin to presentaiton or make a marquee selection to pin your desired view"}
}> + { + // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack + {'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}
}> }
-
; +
+ ); } } -ScriptingGlobals.add(function navigateToDoc(bestTarget:Doc, activeItem:Doc) { +ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) { const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null); - const openInTab = (doc: Doc, finished?: () => void) => {CollectionDockingView.AddSplit(doc, "right"); finished?.(); }; - DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext]:[], - undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom)); -}); \ No newline at end of file + const openInTab = (doc: Doc, finished?: () => void) => { + CollectionDockingView.AddSplit(doc, 'right'); + finished?.(); + }; + DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom)); +}); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 574193614..c5177de90 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,4 +1,4 @@ -import { Tooltip } from "@material-ui/core"; +import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -6,13 +6,12 @@ import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field } from '../.. import { Id } from '../../../fields/FieldSymbols'; import { StrCast } from '../../../fields/Types'; import { DocUtils } from '../../documents/Documents'; -import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { ViewBoxBaseComponent } from "../DocComponent"; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; -import "./SearchBox.scss"; - +import './SearchBox.scss'; const DAMPENING_FACTOR = 0.9; const MAX_ITERATIONS = 25; @@ -29,13 +28,15 @@ export interface SearchBoxProps extends FieldViewProps { */ @observer export class SearchBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(SearchBox, fieldKey); + } public static Instance: SearchBox; private _inputRef = React.createRef(); - @observable _searchString = ""; - @observable _docTypeString = "all"; + @observable _searchString = ''; + @observable _docTypeString = 'all'; @observable _results: Map = new Map(); @observable _pageRanks: Map = new Map(); @observable _linkedDocsOut: Map> = new Map>(); @@ -52,7 +53,7 @@ export class SearchBox extends ViewBoxBaseComponent() { SearchBox.Instance = this; } - /** + /** * This method is called when the SearchBox component is first mounted. When the user opens * the search panel, the search input box is automatically selected. This allows the user to * type in the search input box immediately, without needing clicking on it first. @@ -75,7 +76,7 @@ export class SearchBox extends ViewBoxBaseComponent() { * This method is called when the text in the search input box is modified by the user. The * _searchString is updated to the new value of the text in the input box and submitSearch * is called to update the search results accordingly. - * + * * (Note: There is no longer a need to press enter to submit a search. Any update to the input * causes a search to be submitted automatically.) */ @@ -88,7 +89,7 @@ export class SearchBox extends ViewBoxBaseComponent() { * This method is called when the option in the select drop-down menu is changed. The * _docTypeString is updated to the new value of the option in the drop-down menu. This * is used to filter the results of the search to documents of a specific type. - * + * * (Note: This doesn't affect the results array, so there is no need to submit a new * search here. The results of the search on the _searchString query are simply filtered * by type directly before rendering them.) @@ -134,8 +135,8 @@ export class SearchBox extends ViewBoxBaseComponent() { docs.filter(d => d && !visited.includes(d)).forEach(d => { visited.push(d); const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const data = d[annos ? fieldKey + '-annotations' : fieldKey]; data && newarray.push(...DocListCast(data)); func(depth, d); }); @@ -156,14 +157,18 @@ export class SearchBox extends ViewBoxBaseComponent() { var depth = 0; while (docs.length > 0) { newarray = []; - await Promise.all(docs.filter(d => d).map(async d => { - const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; - const docs = await DocListCastAsync(data); - docs && newarray.push(...docs); - func(depth, d); - })); + await Promise.all( + docs + .filter(d => d) + .map(async d => { + const fieldKey = Doc.LayoutFieldKey(d); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const data = d[annos ? fieldKey + '-annotations' : fieldKey]; + const docs = await DocListCastAsync(data); + docs && newarray.push(...docs); + func(depth, d); + }) + ); docs = newarray; depth++; } @@ -177,11 +182,10 @@ export class SearchBox extends ViewBoxBaseComponent() { * right side of each search result. */ static formatType(type: String): String { - if (type === "pdf") { - return "PDF"; - } - else if (type === "image") { - return "Img"; + if (type === 'pdf') { + return 'PDF'; + } else if (type === 'image') { + return 'Img'; } return type.charAt(0).toUpperCase() + type.substring(1, 3); @@ -198,9 +202,40 @@ export class SearchBox extends ViewBoxBaseComponent() { @action searchCollection(query: string) { const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; - const blockedKeys = ["x", "y", "proto", "width", "autoHeight", "acl-Override", "acl-Public", "context", "zIndex", "height", "text-scrollHeight", "text-height", "cloneFieldFilter", "isPrototype", "text-annotations", - "dragFactory-count", "text-noTemplate", "aliases", "system", "layoutKey", "baseProto", "xMargin", "yMargin", "links", "layout", "layout_keyValue", "fitWidth", "viewType", "title-custom", - "panX", "panY", "viewScale"]; + const blockedKeys = [ + 'x', + 'y', + 'proto', + 'width', + 'autoHeight', + 'acl-Override', + 'acl-Public', + 'context', + 'zIndex', + 'height', + 'text-scrollHeight', + 'text-height', + 'cloneFieldFilter', + 'isPrototype', + 'text-annotations', + 'dragFactory-count', + 'text-noTemplate', + 'aliases', + 'system', + 'layoutKey', + 'baseProto', + 'xMargin', + 'yMargin', + 'links', + 'layout', + 'layout_keyValue', + 'fitWidth', + 'viewType', + 'title-custom', + 'panX', + 'panY', + 'viewScale', + ]; const collection = CollectionDockingView.Instance; query = query.toLowerCase(); @@ -211,10 +246,15 @@ export class SearchBox extends ViewBoxBaseComponent() { const docs = DocListCast(collection.rootDoc[Doc.LayoutFieldKey(collection.rootDoc)]); const docIDs: String[] = []; SearchBox.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { - const dtype = StrCast(doc.type, "string") as DocumentType; + const dtype = StrCast(doc.type, 'string') as DocumentType; if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth > 0) { const hlights = new Set(); - SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key)); + SearchBox.documentKeys(doc).forEach( + key => + Field.toString(doc[key] as Field) + .toLowerCase() + .includes(query) && hlights.add(key) + ); blockedKeys.forEach(key => hlights.delete(key)); if (Array.from(hlights.keys()).length > 0) { @@ -250,18 +290,16 @@ export class SearchBox extends ViewBoxBaseComponent() { this._results.forEach((_, linkedDoc) => { this._linkedDocsIn.get(linkedDoc)?.add(doc); }); - } - else { + } else { const linkedDocSet = new Set(); - Doc.GetProto(doc)[DirectLinksSym].forEach((link) => { + Doc.GetProto(doc)[DirectLinksSym].forEach(link => { const d1 = link?.anchor1 as Doc; const d2 = link?.anchor2 as Doc; if (doc === d1 && this._results.has(d2)) { linkedDocSet.add(d2); this._linkedDocsIn.get(d2)?.add(doc); - } - else if (doc === d2 && this._results.has(d1)) { + } else if (doc === d2 && this._results.has(d1)) { linkedDocSet.add(d1); this._linkedDocsIn.get(d1)?.add(doc); } @@ -289,8 +327,8 @@ export class SearchBox extends ViewBoxBaseComponent() { this._results.forEach((_, doc) => { let nextPageRank = pageRankFromAll; - this._linkedDocsIn.get(doc)?.forEach((linkedDoc) => { - nextPageRank += DAMPENING_FACTOR * (this._pageRanks.get(linkedDoc) ?? 0) / (this._linkedDocsOut.get(linkedDoc)?.size ?? 1); + this._linkedDocsIn.get(doc)?.forEach(linkedDoc => { + nextPageRank += (DAMPENING_FACTOR * (this._pageRanks.get(linkedDoc) ?? 0)) / (this._linkedDocsOut.get(linkedDoc)?.size ?? 1); }); nextPageRanks.set(doc, nextPageRank); @@ -328,7 +366,7 @@ export class SearchBox extends ViewBoxBaseComponent() { */ static documentKeys(doc: Doc) { const keys: { [key: string]: boolean } = {}; - Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false)); + Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))); return Array.from(Object.keys(keys)); } @@ -342,15 +380,13 @@ export class SearchBox extends ViewBoxBaseComponent() { const query = StrCast(this._searchString); Doc.SetSearchQuery(query); - Array.from(this._results.keys()).forEach(doc => - DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true) - ); + Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); this._results.clear(); if (query) { this.searchCollection(query); } - } + }; /** * This method resets the search by iterating through each result and removing all @@ -372,7 +408,7 @@ export class SearchBox extends ViewBoxBaseComponent() { */ selectElement = async (doc: Doc, finishFunc: () => void) => { await DocumentManager.Instance.jumpToDocument(doc, true, undefined, [], undefined, undefined, undefined, finishFunc); - } + }; /** * This method returns a JSX list of the options in the select drop-down menu, which @@ -380,9 +416,13 @@ export class SearchBox extends ViewBoxBaseComponent() { */ @computed public get selectOptions() { - const selectValues = ["all", "rtf", "image", "pdf", "web", "video", "audio", "collection"]; + const selectValues = ['all', 'rtf', 'image', 'pdf', 'web', 'video', 'audio', 'collection']; - return selectValues.map(value => ); + return selectValues.map(value => ( + + )); } /** @@ -393,39 +433,37 @@ export class SearchBox extends ViewBoxBaseComponent() { const isLinkSearch: boolean = this.props.linkSearch; - const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank + const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank const resultsJSX = Array(); - sortedResults.forEach((result) => { - var className = "searchBox-results-scroll-view-result"; + sortedResults.forEach(result => { + var className = 'searchBox-results-scroll-view-result'; if (this._selectedResult === result[0]) { - className += " searchBox-results-scroll-view-result-selected"; + className += ' searchBox-results-scroll-view-result-selected'; } const formattedType = SearchBox.formatType(StrCast(result[0].type)); const title = result[0].title; - if (this._docTypeString === "all" || this._docTypeString === result[0].type) { + if (this._docTypeString === 'all' || this._docTypeString === result[0].type) { validResults++; resultsJSX.push( -
{title}
}> -
this.makeLink(result[0]) : - e => { - this.onResultClick(result[0]); - e.stopPropagation(); - }} className={className}> -
- {title} -
-
- {formattedType} -
-
- {result[1].join(", ")} -
+ {title as string}
}> +
this.makeLink(result[0]) + : e => { + this.onResultClick(result[0]); + e.stopPropagation(); + } + } + className={className}> +
{title as string}
+
{formattedType}
+
{result[1].join(', ')}
); @@ -433,22 +471,31 @@ export class SearchBox extends ViewBoxBaseComponent() { }); return ( -
-
- {isLinkSearch ? (null) : } - e.key === "Enter" ? this.submitSearch() : null} type="text" placeholder="Search..." id="search-input" className="searchBox-input" style={{ width: isLinkSearch ? "100%" : undefined, borderRadius: isLinkSearch ? "5px" : undefined }} ref={this._inputRef} /> -
+
+
+ {isLinkSearch ? null : ( + + )} + (e.key === 'Enter' ? this.submitSearch() : null)} + type="text" + placeholder="Search..." + id="search-input" + className="searchBox-input" + style={{ width: isLinkSearch ? '100%' : undefined, borderRadius: isLinkSearch ? '5px' : undefined }} + ref={this._inputRef} + /> +
-
- {`${validResults}` + " result" + (validResults === 1 ? "" : "s")} -
-
- {resultsJSX} -
+
{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}
+
{resultsJSX}
-
+
); } -} \ No newline at end of file +} diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index bf06faeb9..e1360553a 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -1,52 +1,236 @@ - import { library } from '@fortawesome/fontawesome-svg-core'; import { - faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, - faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, - faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, - faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, - faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, - faThumbtack, faTree, faTv, faBook, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft, - faAlignRight, faAlignLeft + faTasks, + faReply, + faQuoteLeft, + faHandPointLeft, + faFolderOpen, + faAngleDoubleLeft, + faExternalLinkSquareAlt, + faMobile, + faThLarge, + faWindowClose, + faEdit, + faTrashAlt, + faPalette, + faAngleRight, + faBell, + faTrash, + faCamera, + faExpand, + faCaretDown, + faCaretLeft, + faCaretRight, + faCaretSquareDown, + faCaretSquareRight, + faArrowsAltH, + faPlus, + faMinus, + faTerminal, + faToggleOn, + faFile as fileSolid, + faExternalLinkAlt, + faLocationArrow, + faSearch, + faFileDownload, + faStop, + faCalculator, + faWindowMaximize, + faAddressCard, + faQuestionCircle, + faArrowLeft, + faArrowRight, + faArrowDown, + faArrowUp, + faBolt, + faBullseye, + faCaretUp, + faCat, + faCheck, + faChevronRight, + faClipboard, + faClone, + faCloudUploadAlt, + faCommentAlt, + faCompressArrowsAlt, + faCut, + faEllipsisV, + faEraser, + faExclamation, + faFileAlt, + faFileAudio, + faFilePdf, + faFilm, + faFilter, + faFont, + faGlobeAsia, + faHighlighter, + faLongArrowAltRight, + faMicrophone, + faMousePointer, + faMusic, + faObjectGroup, + faPause, + faPen, + faPenNib, + faPhone, + faPlay, + faPortrait, + faRedoAlt, + faStamp, + faStickyNote, + faThumbtack, + faTree, + faTv, + faBook, + faUndoAlt, + faVideo, + faAsterisk, + faBrain, + faImage, + faPaintBrush, + faTimes, + faEye, + faHome, + faLongArrowAltLeft, + faBars, + faTh, + faChevronLeft, + faAlignRight, + faAlignLeft, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import * as React from "react"; +import * as React from 'react'; import { Docs, DocumentOptions, DocUtils } from '../client/documents/Documents'; -import { DocumentType } from "../client/documents/DocumentTypes"; +import { DocumentType } from '../client/documents/DocumentTypes'; import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { SettingsManager, ColorScheme } from '../client/util/SettingsManager'; import { Transform } from '../client/util/Transform'; -import { UndoManager } from "../client/util/UndoManager"; +import { UndoManager } from '../client/util/UndoManager'; import { TabDocView } from '../client/views/collections/TabDocView'; -import { CollectionViewType } from "../client/views/collections/CollectionView"; -import { GestureOverlay } from "../client/views/GestureOverlay"; -import { AudioBox } from "../client/views/nodes/AudioBox"; +import { CollectionViewType } from '../client/views/collections/CollectionView'; +import { GestureOverlay } from '../client/views/GestureOverlay'; +import { AudioBox } from '../client/views/nodes/AudioBox'; import { DocumentView } from '../client/views/nodes/DocumentView'; -import { RichTextMenu } from "../client/views/nodes/formattedText/RichTextMenu"; -import { RadialMenu } from "../client/views/nodes/RadialMenu"; +import { RichTextMenu } from '../client/views/nodes/formattedText/RichTextMenu'; +import { RadialMenu } from '../client/views/nodes/RadialMenu'; import { Doc, DocListCast } from '../fields/Doc'; import { InkTool } from '../fields/InkField'; -import { List } from "../fields/List"; -import { ScriptField } from "../fields/ScriptField"; -import { Cast, FieldValue } from '../fields/Types'; +import { List } from '../fields/List'; +import { ScriptField } from '../fields/ScriptField'; +import { Cast, FieldValue, StrCast } from '../fields/Types'; import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero } from '../Utils'; -import { AudioUpload } from "./AudioUpload"; -import { Uploader } from "./ImageUpload"; -import "./AudioUpload.scss"; -import "./ImageUpload.scss"; -import "./MobileInterface.scss"; - -library.add(...[faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus, - faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard, - faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt, - faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, - faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, - faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft, - faAlignLeft, faAlignRight].map(m => m as any)); - +import { AudioUpload } from './AudioUpload'; +import { Uploader } from './ImageUpload'; +import './AudioUpload.scss'; +import './ImageUpload.scss'; +import './MobileInterface.scss'; + +library.add( + ...[ + faTasks, + faReply, + faQuoteLeft, + faHandPointLeft, + faFolderOpen, + faAngleDoubleLeft, + faExternalLinkSquareAlt, + faMobile, + faThLarge, + faWindowClose, + faEdit, + faTrashAlt, + faPalette, + faAngleRight, + faBell, + faTrash, + faCamera, + faExpand, + faCaretDown, + faCaretLeft, + faCaretRight, + faCaretSquareDown, + faCaretSquareRight, + faArrowsAltH, + faPlus, + faMinus, + faTerminal, + faToggleOn, + fileSolid, + faExternalLinkAlt, + faLocationArrow, + faSearch, + faFileDownload, + faStop, + faCalculator, + faWindowMaximize, + faAddressCard, + faQuestionCircle, + faArrowLeft, + faArrowRight, + faArrowDown, + faArrowUp, + faBolt, + faBullseye, + faCaretUp, + faCat, + faCheck, + faChevronRight, + faClipboard, + faClone, + faCloudUploadAlt, + faCommentAlt, + faCompressArrowsAlt, + faCut, + faEllipsisV, + faEraser, + faExclamation, + faFileAlt, + faFileAudio, + faFilePdf, + faFilm, + faFilter, + faFont, + faGlobeAsia, + faHighlighter, + faLongArrowAltRight, + faMicrophone, + faMousePointer, + faMusic, + faObjectGroup, + faPause, + faPen, + faPenNib, + faPhone, + faPlay, + faPortrait, + faRedoAlt, + faStamp, + faStickyNote, + faThumbtack, + faTree, + faTv, + faUndoAlt, + faBook, + faVideo, + faAsterisk, + faBrain, + faImage, + faPaintBrush, + faTimes, + faEye, + faHome, + faLongArrowAltLeft, + faBars, + faTh, + faChevronLeft, + faAlignLeft, + faAlignRight, + ].map(m => m as any) +); @observer export class MobileInterface extends React.Component { @@ -64,36 +248,38 @@ export class MobileInterface extends React.Component { @observable private _homeDoc: Doc = this._mainDoc; // home menu as a document @observable private _parents: Array = []; // array of parent docs (for pathbar) - @computed private get mainContainer() { return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : CurrentUserUtils.GuestMobile; } + @computed private get mainContainer() { + return Doc.UserDoc() ? FieldValue(Cast(Doc.UserDoc().activeMobile, Doc)) : CurrentUserUtils.GuestMobile; + } constructor(props: Readonly<{}>) { super(props); - this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), "myDashboards"); // to access documents in Dash Web + this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), 'myDashboards'); // to access documents in Dash Web MobileInterface.Instance = this; } @action componentDidMount = () => { // if the home menu is in list view -> adjust the menu toggle appropriately - this._menuListView = this._homeDoc._viewType === "stacking" ? true : false; + this._menuListView = this._homeDoc._viewType === 'stacking' ? true : false; CurrentUserUtils.ActiveTool = InkTool.None; // ink should intially be set to none Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home AudioBox.Enabled = true; // remove double click to avoid mobile zoom in - document.removeEventListener("dblclick", this.onReactDoubleClick); - document.addEventListener("dblclick", this.onReactDoubleClick); - } + document.removeEventListener('dblclick', this.onReactDoubleClick); + document.addEventListener('dblclick', this.onReactDoubleClick); + }; @action componentWillUnmount = () => { document.removeEventListener('dblclick', this.onReactDoubleClick); - } + }; // Prevent zooming in when double tapping the screen onReactDoubleClick = (e: MouseEvent) => { e.stopPropagation(); - } + }; // Switch the mobile view to the given doc @action @@ -110,7 +296,7 @@ export class MobileInterface extends React.Component { // Ensures that switching to home is not registed UndoManager.undoStack.length = 0; UndoManager.redoStack.length = 0; - } + }; // For toggling the hamburger menu @action @@ -120,29 +306,29 @@ export class MobileInterface extends React.Component { if (this._ink) { this.onSwitchInking(); } - } + }; /** * Method called when 'Library' button is pressed on the home screen */ switchToLibrary = async () => { this.switchCurrentView(this._library); - runInAction(() => this._homeMenu = false); + runInAction(() => (this._homeMenu = false)); this.toggleSidebar(); - } + }; /** * Back method for navigating through items */ @action back = () => { - const header = document.getElementById("header") as HTMLElement; + const header = document.getElementById('header') as HTMLElement; const doc = Cast(this._parents.pop(), Doc) as Doc; // Parent document // Case 1: Parent document is 'dashboards' - if (doc === Cast(this._library, Doc) as Doc) { + if (doc === (Cast(this._library, Doc) as Doc)) { this.dashboards = null; this.switchCurrentView(this._library); // Case 2: Parent document is the 'home' menu (root node) - } else if (doc === Cast(this._homeDoc, Doc) as Doc) { + } else if (doc === (Cast(this._homeDoc, Doc) as Doc)) { this._homeMenu = true; this._parents = []; this.dashboards = null; @@ -155,7 +341,7 @@ export class MobileInterface extends React.Component { header.textContent = String(doc.title); } this._ink = false; // turns ink off - } + }; /** * Return 'Home", which implies returning to 'Home' menu buttons @@ -171,7 +357,7 @@ export class MobileInterface extends React.Component { if (this._sidebarActive) { this.toggleSidebar(); } - } + }; /** * Return to primary Dashboard in library (Dashboards Doc) @@ -182,7 +368,7 @@ export class MobileInterface extends React.Component { this.switchCurrentView(this._library); this._homeMenu = false; this.dashboards = null; - } + }; /** * Note: window.innerWidth and window.screen.width compute different values. @@ -190,14 +376,14 @@ export class MobileInterface extends React.Component { * display resolution which computes differently. */ returnWidth = () => window.innerWidth; //The windows width - returnHeight = () => (window.innerHeight - 300); //Calculating the windows height (-300 to account for topbar) - whitebackground = () => "white"; + returnHeight = () => window.innerHeight - 300; //Calculating the windows height (-300 to account for topbar) + whitebackground = () => 'white'; /** * DocumentView for graphic display of all documents */ @computed get displayDashboards() { - return !this.mainContainer ? (null) : -
+ return !this.mainContainer ? null : ( +
-
; +
+ ); } /** @@ -233,20 +420,19 @@ export class MobileInterface extends React.Component { */ handleClick = async (doc: Doc) => { runInAction(() => { - if (doc.type !== "collection" && this._sidebarActive) { + if (doc.type !== 'collection' && this._sidebarActive) { this._parents.push(this._activeDoc); this.switchCurrentView(doc); this._homeMenu = false; this.toggleSidebar(); - } - else { + } else { this._parents.push(this._activeDoc); this.switchCurrentView(doc); this._homeMenu = false; this.dashboards = doc; } }); - } + }; /** * Called when an item in the library is clicked and should @@ -260,30 +446,31 @@ export class MobileInterface extends React.Component { this._homeMenu = false; this.dashboards = doc; this.toggleSidebar(); - } + }; // Renders the graphical pathbar renderPathbar = () => { const docPath = [...this._parents, this._activeDoc]; - const items = docPath.map((doc: Doc, index: any) => + const items = docPath.map((doc: Doc, index: any) => (
- {index === 0 ? (null) : } -
this.handlePathClick(doc, index)}>{doc.title} + {index === 0 ? null : } +
this.handlePathClick(doc, index)}> + {StrCast(doc.title)}
-
); - return (
-
- {items}
- {!this._parents.length ? (null) : -
- -
} -
-
); - } + )); + return ( +
+
{items}
+ {!this._parents.length ? null : ( +
+ +
+ )} +
+
+ ); + }; // Handles when user clicks on a document in the pathbar @action @@ -300,7 +487,7 @@ export class MobileInterface extends React.Component { this.switchCurrentView(doc); this._parents.length = index; } - } + }; // Renders the contents of the menu and sidebar @computed get renderDefaultContent() { @@ -309,7 +496,9 @@ export class MobileInterface extends React.Component {
- +
e.stopPropagation()}>