From 20c0190e820f2bd343693368b7ef55a91f19c880 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 14 Mar 2023 20:31:06 -0400 Subject: simplified Deserialize code. streamlined currentUserUtils to need fewer compiled functions by parameterizing more functions. . --- src/client/documents/Documents.ts | 13 +- src/client/util/CurrentUserUtils.ts | 145 +++++------ src/client/util/Scripting.ts | 4 +- src/client/util/SelectionManager.ts | 7 +- src/client/util/SerializationHelper.ts | 86 +------ src/client/views/DashboardView.tsx | 1 - src/client/views/GestureOverlay.tsx | 7 +- src/client/views/MainView.tsx | 20 -- src/client/views/OverlayView.tsx | 1 + .../views/collections/CollectionDockingView.tsx | 19 +- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 10 +- src/client/views/nodes/DocumentView.tsx | 2 + src/client/views/nodes/WebBox.tsx | 8 +- src/client/views/nodes/button/FontIconBox.tsx | 272 +++++++-------------- src/fields/Doc.ts | 57 ++--- src/fields/ScriptField.ts | 31 +-- src/fields/util.ts | 22 +- 18 files changed, 253 insertions(+), 454 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 251e432ef..457811e26 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -200,6 +200,8 @@ export class DocumentOptions { 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view'); 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view'); 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)'); + openFactoryLocation?: string; // an OpenWhere value to place the factory created document + openFactoryAsDelegate?: boolean; // lat?: number; lng?: number; infoWindowOpen?: boolean; @@ -220,6 +222,8 @@ export class DocumentOptions { autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. toolTip?: string; // tooltip to display on hover + toolType?: string; // type of pen tool + expertMode?: boolean; // something available only in expert (not novice) mode contextMenuFilters?: List; contextMenuScripts?: List; contextMenuLabels?: List; @@ -1353,6 +1357,8 @@ export namespace DocUtils { const script = scripts[key]; if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { doc[key] = ScriptField.MakeScript(script, { + self: Doc.name, + this: Doc.name, dragData: DragManager.DocumentDragData.name, value: 'any', _readOnly_: 'boolean', @@ -1862,11 +1868,8 @@ export namespace DocUtils { } ScriptingGlobals.add('Docs', Docs); -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { - return DocUtils.copyDragFactory(dragFactory); -}); -ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { - return DocUtils.delegateDragFactory(dragFactory); +ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) { + return dragFactory instanceof Doc ? (asDelegate ? DocUtils.delegateDragFactory(dragFactory) : DocUtils.copyDragFactory(dragFactory)) : dragFactory; }); ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title }); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c4fb4788c..2820c66ee 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -21,6 +21,7 @@ import { DashboardView } from "../views/DashboardView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; +import { OpenWhere } from "../views/nodes/DocumentView"; import { OverlayView } from "../views/OverlayView"; import { DragManager } from "./DragManager"; import { MakeTemplate } from "./DropConverter"; @@ -40,6 +41,8 @@ interface Button { numBtnMax?: number; switchToggle?: boolean; width?: number; + toolType?: string; // type of pen tool + expertMode?: boolean;// available only in expert mode btnList?: List; ignoreClick?: boolean; buttonText?: string; @@ -78,7 +81,7 @@ export class CurrentUserUtils { ]; 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 reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' }; const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { if (templateBtn) { DocUtils.AssignOpts(templateBtn,btnOpts); @@ -105,7 +108,7 @@ export class CurrentUserUtils { const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, system: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self])))"}, - { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: "openOnRight(self.doubleClickView)"}]; + { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(self.doubleClickView.${OpenWhere.addRight})`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined; @@ -121,11 +124,11 @@ export class CurrentUserUtils { const tempClicks = DocCast(doc[field]); const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, system: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ - { opts: { title: "onClick"}, script: "console.log( 'click')"}, - { opts: { title: "onDoubleClick"}, script: "console.log( 'double click')"}, - { opts: { title: "onChildClick"}, script: "console.log( 'child click')"}, - { opts: { title: "onChildDoubleClick"}, script: "console.log( 'child double click')"}, - { opts: { title: "onCheckedClick"}, script: "console.log( heading, checked, containingTreeView)"}, + { opts: { title: "onClick"}, script: "console.log('click')"}, + { opts: { title: "onDoubleClick"}, script: "console.log('click')"}, + { opts: { title: "onChildClick"}, script: "console.log('click')"}, + { opts: { title: "onChildDoubleClick"}, script: "console.log('click')"}, + { opts: { title: "onCheckedClick"}, script: "console.log(heading, checked, containingTreeView)"}, ]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; @@ -183,7 +186,7 @@ export class CurrentUserUtils { const allopts = {system: true, ...opts}; return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), - {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView"}); + {onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(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 @@ -211,7 +214,7 @@ export class CurrentUserUtils { /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyTrail 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}, + backgroundColor?: string, openFactoryAsDelegate?:boolean, openFactoryLocation?:string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, }[] { const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: false, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); const json = { @@ -257,13 +260,13 @@ export class CurrentUserUtils { creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, - {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200 }}, + {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _fitWidth: true}}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100, _fitWidth: true }}, {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, - {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }}, + {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _fitWidth: true, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, _isLinkButton: true }}, @@ -282,30 +285,34 @@ export class CurrentUserUtils { emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs)); 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 note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard 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);}'}, }, - { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, }, - { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, }, - { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, }, - { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, }, - { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, }, - { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc,scripts: { onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, - { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: { onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, - ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, })) + { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)}, + { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, + { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc,clickFactory: DocCast(doc.emptyTab)}, + { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)}, + { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, + { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc,clickFactory: DocCast(doc.emptyComparison)}, + { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc,clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, + { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript)}, + { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, + { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true }, + { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", clickFactory: "repl" as any, openFactoryLocation: OpenWhere.overlay}, + ].map(tuple => ( + { openFactoryLocation: OpenWhere.addRight, + scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', + onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }'}, + ...tuple, })) } /// 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, + const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), @@ -603,53 +610,50 @@ export class CurrentUserUtils { static textTools():Button[] { return [ - { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'}, + { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, - { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_);}'}}, - { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", ignoreClick: true, scripts: {script: '{ return setFontHighlight(value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, - { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, - { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} }, - { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} }, - { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} }, - + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, + { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}}, + { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, + { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}}, - { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }}, - { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} }, - { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} }, - { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}}, - { title: "Dictate",toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", scripts: {onClick:'{ return toggleDictation(_readOnly_);}'}}, - ]; + ]; } static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", false, _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }}, - // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} }, - { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} }, - { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} }, - { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} }, - { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} }, - { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, - { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }}, + { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, + { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, + { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, + { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, + { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip", toolType: "fillColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'} }, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(self.toolType, value, _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_);}'}, }]; + return [{ title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'}, }]; } static webTools() { return [ { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(_readOnly_); }' }}, { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(_readOnly_); }'}}, - //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, ]; } @@ -661,18 +665,18 @@ export class CurrentUserUtils { CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), - title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, - { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "tab")'}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, - { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, - { title: "Num",icon: "",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, - { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, - { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected - { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform", true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), funcs: {hidden: 'false', linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.RTF}")`} }, // Always available - { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), funcs: {hidden: 'false', linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.INK}")`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available - { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), funcs: {hidden: `!SelectionManager_selectedDocType("${DocumentType.WEB}")`, linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.WEB}")`, } }, // Only when Web is selected - { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), funcs: {hidden: `!SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`, linearViewIsExpanded: `SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected + title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, + { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, toolType:"tab", funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, width: 20, scripts: { onClick: 'pinWithView(_readOnly_, altKey)'}}, + { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected + { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, toolType:"tab", ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, + { title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, + { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, + { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available + { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected + { title: "Schema", icon: "Schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearViewIsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected ]; } @@ -684,6 +688,7 @@ export class CurrentUserUtils { color: Colors.WHITE, system: true, dontUndo: true, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, + toolType: params.toolType, expertMode: params.expertMode, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, _removeDropProperties: new List([ "_stayInCollection"]), }; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 6dcdcb71b..d32298c83 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -248,8 +248,10 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp ScriptingGlobals.resetScriptingGlobals(); } !signature.includes('XXX') && ScriptField._scriptFieldCache.set(script + ':' + signature, result as CompiledScript); - //console.log('COMPILED: ' + script + ':' + signature); return result; } ScriptingGlobals.add(CompileScript); +ScriptingGlobals.add(function runScript(self: Doc, script: ScriptField) { + return script?.script.run({ this: self, self: self }).result; +}); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index c0fc25376..0f4f77588 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -104,10 +104,11 @@ export namespace SelectionManager { return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking); } } -ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) { - if (colType === ('tab' as any)) { +ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { + if (Doc.noviceMode && expertMode) return false; + if (type === 'tab') { return SelectionManager.Views().lastElement()?.props.renderDepth === 0; } let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); - return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true; + return selected?.type === type || selected?.viewType === type || !type; }); diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts index 2d1f61cfb..76037a7e9 100644 --- a/src/client/util/SerializationHelper.ts +++ b/src/client/util/SerializationHelper.ts @@ -44,12 +44,8 @@ export namespace SerializationHelper { } if (!obj.__type) { - if (true || ClientUtils.RELEASE) { - console.warn("No property 'type' found in JSON."); - return undefined; - } else { - throw Error("No property 'type' found in JSON."); - } + console.warn("No property 'type' found in JSON."); + return undefined; } if (!(obj.__type in serializationTypes)) { @@ -58,9 +54,8 @@ export namespace SerializationHelper { const type = serializationTypes[obj.__type]; const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result))); - if (type.afterDeserialize) { - type.afterDeserialize(value); - } + type.afterDeserialize?.(value); + return value; } } @@ -68,75 +63,20 @@ export namespace SerializationHelper { const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise } } = {}; const reverseMap: { [ctor: string]: string } = {}; -export interface DeserializableOpts { - (constructor: { new (...args: any[]): any }): void; - withFields(fields: string[]): Function; -} - -export function Deserializable(name: string, afterDeserialize?: (obj: any) => void | Promise): DeserializableOpts; -export function Deserializable(constructor: { new (...args: any[]): any }): void; -export function Deserializable(constructor: { new (...args: any[]): any } | string, afterDeserialize?: (obj: any) => void): DeserializableOpts | void { - function addToMap(name: string, ctor: { new (...args: any[]): any }) { +export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void { + function addToMap(className: string, ctor: { new (...args: any[]): any }) { const schema = getDefaultModelSchema(ctor) as any; - if (schema.targetClass !== ctor) { - const newSchema = { ...schema, factory: () => new ctor() }; - setDefaultModelSchema(ctor, newSchema); + if (schema.targetClass !== ctor || constructorArgs) { + setDefaultModelSchema(ctor, { ...schema, factory: (context: any) => new ctor(...(constructorArgs ?? [])?.map(arg => context.json[arg])) }); } - if (!(name in serializationTypes)) { - serializationTypes[name] = { ctor, afterDeserialize }; - reverseMap[ctor.name] = name; + if (!(className in serializationTypes)) { + serializationTypes[className] = { ctor, afterDeserialize }; + reverseMap[ctor.name] = className; } else { - throw new Error(`Name ${name} has already been registered as deserializable`); + throw new Error(`Name ${className} has already been registered as deserializable`); } } - if (typeof constructor === 'string') { - return Object.assign( - (ctor: { new (...args: any[]): any }) => { - addToMap(constructor, ctor); - }, - { withFields: (fields: string[]) => Deserializable.withFields(fields, constructor, afterDeserialize) } - ); - } - addToMap(constructor.name, constructor); -} - -export namespace Deserializable { - export function withFields(fields: string[], name?: string, afterDeserialize?: (obj: any) => void | Promise) { - return function (constructor: { new (...fields: any[]): any }) { - Deserializable(name || constructor.name, afterDeserialize)(constructor); - let schema = getDefaultModelSchema(constructor); - if (schema) { - schema.factory = context => { - const args = fields.map(key => context.json[key]); - return new constructor(...args); - }; - // TODO A modified version of this would let us not reassign fields that we're passing into the constructor later on in deserializing - // fields.forEach(field => { - // if (field in schema.props) { - // let propSchema = schema.props[field]; - // if (propSchema === false) { - // return; - // } else if (propSchema === true) { - // propSchema = primitive(); - // } - // schema.props[field] = custom(propSchema.serializer, - // () => { - // return SKIP; - // }); - // } - // }); - } else { - schema = { - props: {}, - factory: context => { - const args = fields.map(key => context.json[key]); - return new constructor(...args); - }, - }; - setDefaultModelSchema(constructor, schema); - } - }; - } + return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor); } export function autoObject(): PropSchema { diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 7ebe8d0e3..2b586b0e2 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -233,7 +233,6 @@ export class DashboardView extends React.Component { /// 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) => { if (!doc) return false; - Doc.MainDocId = doc[Id]; Doc.AddDocToList(Doc.MyDashboards, 'data', doc); // this has the side-effect of setting the main container since we're assigning the active/guest dashboard diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 6058eaaf9..0feccb742 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -94,7 +94,7 @@ export class GestureOverlay extends Touchable { } static setupThumbButtons(doc: Doc) { - const docProtoData: { title: string; icon: string; drag?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [ + const docProtoData: { title: string; icon: string; drag?: string; toolType?: 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' }, { @@ -105,8 +105,8 @@ export class GestureOverlay extends Touchable { clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: 'orange', }, - { 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' }, + { title: 'interpret text', icon: 'font', toolType: 'inktotext', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'orange' }, + { title: 'ignore gestures', icon: 'signature', toolType: 'ignoregesture', pointerUp: "setToolglass('none')", pointerDown: 'setToolglass(self.toolType)', backgroundColor: 'green' }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ @@ -116,6 +116,7 @@ export class GestureOverlay extends Touchable { _height: 10, title: data.title, icon: data.icon, + toolType: data.toolType, _dropAction: data.pointerDown ? 'copy' : undefined, ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 945cd61db..2e04ca3dd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -230,8 +230,6 @@ export class MainView extends React.Component { if (window.location.pathname !== '/home') { const pathname = window.location.pathname.substr(1).split('/'); if (pathname.length > 1 && pathname[0] === 'doc') { - Doc.MainDocId = pathname[1]; - //!this.userDoc && DocServer.GetRefField(pathname[1]).then( action(field => { if (field instanceof Doc && field._viewType !== CollectionViewType.Docking) { @@ -482,7 +480,6 @@ export class MainView extends React.Component { fa.faHandPointUp, ] ); - this.initAuthenticationRouters(); } globalPointerDown = action((e: PointerEvent) => { @@ -521,23 +518,6 @@ export class MainView extends React.Component { document.oncontextmenu = () => false; }; - initAuthenticationRouters = async () => { - const received = Doc.MainDocId; - if (received && !this.userDoc) { - reaction( - () => Doc.GuestTarget, - target => target && DashboardView.createNewDashboard(), - { fireImmediately: true } - ); - } - // else { - // PromiseValue(this.userDoc.activeDashboard).then(dash => { - // if (dash instanceof Doc) DashboardView.openDashboard(dash); - // else Doc.createNewDashboard(); - // }); - // } - }; - @action createNewPresentation = () => { const pres = Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 08285ff0c..34e8cd6dd 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -255,4 +255,5 @@ export class OverlayView extends React.Component { // bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) { OverlayView.Instance.addWindow(, options); + addOverlayWindow('ScriptingRepl', { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); }); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1ead80bd0..9b6554d67 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -26,6 +26,9 @@ import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; import React = require('react'); import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; +import { OverlayView } from '../OverlayView'; +import { ScriptingRepl } from '../ScriptingRepl'; +import { ScriptField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -580,15 +583,21 @@ ScriptingGlobals.add( '(doc: any)' ); ScriptingGlobals.add( - function openOnRight(doc: any) { - return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); + function openDoc(doc: any, where: OpenWhere) { + switch (where) { + case OpenWhere.addRight: + return CollectionDockingView.AddSplit(doc, OpenWhereMod.right); + case OpenWhere.overlay: + if (doc === 'repl') OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); + else Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + } }, - 'opens up document in tab on right side of the screen', + 'opens up document in location specified', '(doc: any)' ); ScriptingGlobals.add( - function openInOverlay(doc: any) { - return Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + function openRepl() { + return 'openRepl'; }, 'opens up document in screen overlay layer', '(doc: any)' diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 48e5748a0..aed88aa1a 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -169,7 +169,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const cm = ContextMenu.Instance; - if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== Doc.MainDocId) { + if (cm && !e.isPropagationStopped()) { // 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...', diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 2bdcf472f..af2d148e0 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -2,7 +2,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; @@ -172,11 +172,7 @@ export class TreeView extends React.Component { childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.doc), Doc, null); - return ( - (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field - (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCastOrNull(this.doc[field]) - ); // otherwise use the document's data field + return DocListCast(this.props.dataDoc?.[field], DocListCast(layout?.[field], DocListCast(this.doc[field]))); } moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { @@ -759,7 +755,7 @@ export class TreeView extends React.Component { const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' }; const folderOp = this.childDocs?.length ? [makeFolder] : []; - const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' }; + const openAlias = { script: ScriptField.MakeFunction(`openDoc(getAlias(self), ${OpenWhere.addRight})`)!, icon: 'copy', label: 'Open Alias' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; return [ ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 964231e9d..02af30d0c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -80,6 +80,7 @@ export enum OpenWhere { replaceLeft = 'replace:left', inParent = 'inParent', inParentFromScreen = 'inParentFromScreen', + overlay = 'overlay', } export enum OpenWhereMod { none = '', @@ -612,6 +613,7 @@ export class DocumentViewInternal extends DocComponent this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), async selected => { if (selected) { + this._thumbTimer && clearTimeout(this._thumbTimer); this._webPageHasBeenRendered = true; } else if ( (!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) @@ -204,7 +209,8 @@ export class WebBox extends ViewBoxAnnotatableComponent() { @computed get numberButton() { const numBtnType: string = StrCast(this.rootDoc.numBtnType); const numScript = ScriptCast(this.rootDoc.script); - const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ value, _readOnly_: false }), 'set num value'); + const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value'); // Script for checking the outcome of the toggle - const checkResult = Number(numScript?.script.run({ value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); + const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); const label = !FontIconBox.GetShowLabels() ? null :
{this.label}
; @@ -150,7 +151,7 @@ export class FontIconBox extends DocComponent() { min={NumCast(this.rootDoc.numBtnMin, 0)} max={NumCast(this.rootDoc.numBtnMax, 100)} value={checkResult} - className={'menu-slider'} + className="menu-slider" onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))} onPointerUp={() => this._batch?.end()} onChange={e => { @@ -284,27 +285,23 @@ export class FontIconBox extends DocComponent() { text = 'User Default'; } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; - } else if (script?.script.originalScript.startsWith('setFont')) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - noviceList = ['Roboto', 'Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text']; - } + } else text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); } catch (e) { console.log(e); } // Get items to place into the list const list = this.buttonList - .filter(value => !Doc.noviceMode || noviceList.includes(value)) + .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value)) .map(value => (
script.script.run({ value }))}> + onClick={undoBatch(() => script.script.run({ self: this.rootDoc, value }))}> {value[0].toUpperCase() + value.slice(1)}
)); @@ -357,7 +354,7 @@ export class FontIconBox extends DocComponent() { ev.preventDefault(); ev.stopPropagation(); const s = this.colorScript; - s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)(); + s && undoBatch(() => s.script.run({ self: this.rootDoc, value: Utils.colorString(value), _readOnly_: false }).result)(); }; const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']; return ; @@ -368,7 +365,7 @@ export class FontIconBox extends DocComponent() { @computed get colorButton() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? 'transparent'; + const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; const label = !this.label || !FontIconBox.GetShowLabels() ? null : ( @@ -377,13 +374,6 @@ export class FontIconBox extends DocComponent() { ); - // 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 (
any; setDoc: () => void; setMode?: () => void }> = new Map([ + ['font', { + checkResult: () => RichTextMenu.Instance?.fontFamily, + setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), + setMode: () => Doc.UserDoc().textAlign = value, + }], + ['highlight', { + checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, + setDoc: () => value && RichTextMenu.Instance.setHighlight(value), + }], + ['fontColor', { + checkResult: () => RichTextMenu.Instance?.fontColor, + setDoc: () => value && RichTextMenu.Instance.setColor(value), + }], + ['fontSize', { + checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), + setDoc: () => { + if (typeof value === 'number') value = value.toString(); + if (value && Number(value).toString() === value) value += 'px'; + RichTextMenu.Instance.setFontSize(value); + }, + }], + ]); if (checkResult) { - return (selected ?? Doc.UserDoc())._fontHighlight; + return map.get(attr)?.checkResult(); } - color && RichTextMenu.Instance.setHighlight(color); + if (editorView?.state) map.get(attr)?.setDoc(); + else map.get(attr)?.setMode?.(); }); -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: boolean) { - if (checkResult) { - return RichTextMenu.Instance?.fontSize.replace('px', ''); - } - if (typeof size === 'number') size = size.toString(); - if (size && Number(size).toString() === size) size += 'px'; - RichTextMenu.Instance.setFontSize(size); -}); -ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.noAutoLink : false) ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor(); -}); -ScriptingGlobals.add(function toggleDictation(checkResult?: boolean) { +type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal'; +type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; +ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { const textView = RichTextMenu.Instance?.TextView; - if (checkResult) { - return textView?._recording ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (textView) runInAction(() => (textView._recording = !textView._recording)); -}); - -ScriptingGlobals.add(function toggleBold(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.bold : Doc.UserDoc().fontWeight === 'bold') ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleBold(); - else Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold'; -}); - -ScriptingGlobals.add(function toggleUnderline(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.underline : Doc.UserDoc().textDecoration === 'underline') ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleUnderline(); - else Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline'; -}); - -ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - if (checkResult) { - return (editorView ? RichTextMenu.Instance.italics : Doc.UserDoc().fontStyle === 'italics') ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (editorView) RichTextMenu.Instance?.toggleItalics(); - else Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics'; + const editorView = textView?.EditorView; + // prettier-ignore + const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) => + [ where, { checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false), + toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]); + // prettier-ignore + const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => + [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false), + toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]); + // prettier-ignore + const attrs:attrfuncs[] = [ + ['dictation', { checkResult: () => textView?._recording ? true:false, + toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }], + ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), + toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], + ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], + ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], + ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false), + toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] + + const map = new Map(attrs.concat(alignments).concat(listings)); + if (checkResult) return map.get(charStyle)?.checkResult() ? Colors.MEDIUM_BLUE : 'transparent'; + map.get(charStyle)?.toggle(); }); export function checkInksToGroup() { @@ -832,62 +771,39 @@ function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); // toggle: Set overlay status of selected document -ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - if (checkResult) { - if (selected?.type === DocumentType.INK) { - return BoolCast(selected.isInkMask) ? Colors.MEDIUM_BLUE : 'transparent'; - } - return ActiveIsInkMask() ? Colors.MEDIUM_BLUE : 'transparent'; - } - SetActiveIsInkMask(!ActiveIsInkMask()); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.isInkMask = !doc.isInkMask)); -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setFillColor(color?: string, checkResult?: boolean) { +ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); - if (checkResult) { - if (selected?.type === DocumentType.INK) { - return StrCast(selected.fillColor); - } - return ActiveFillColor(); - } - SetActiveFillColor(StrCast(color)); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.fillColor = color)); -}); - -ScriptingGlobals.add(function setStrokeWidth(width: number, checkResult?: boolean) { - if (checkResult) { - const selected = SelectionManager.Docs().lastElement(); - if (selected?.type === DocumentType.INK) { - return NumCast(selected.strokeWidth); - } - return ActiveInkWidth(); - } - SetActiveInkWidth(width.toString()); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.strokeWidth = Number(width))); -}); + // prettier-ignore + const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ + ['inkMask', { + checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.isInkMask) : ActiveIsInkMask()) ? Colors.MEDIUM_BLUE : 'transparent'), + setInk: (doc: Doc) => (doc.isInkMask = !doc.isInkMask), + setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), + }], + ['fillColor', { + checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ? Colors.MEDIUM_BLUE : 'transparent'), + setInk: (doc: Doc) => (doc.fillColor = StrCast(value)), + setMode: () => SetActiveFillColor(StrCast(value)), + }], + [ 'strokeWidth', { + checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.strokeWidth) : ActiveInkWidth()), + setInk: (doc: Doc) => (doc.strokeWidth = NumCast(value)), + setMode: () => SetActiveInkWidth(value.toString()), + }], + ['strokeColor', { + checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.color) : ActiveInkColor()), + setInk: (doc: Doc) => (doc.color = String(value)), + setMode: () => SetActiveInkColor(StrCast(value)), + }], + ]); -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setStrokeColor(color?: string, checkResult?: boolean) { if (checkResult) { - const selected = SelectionManager.Docs().lastElement(); - if (selected?.type === DocumentType.INK) { - return StrCast(selected.color); - } - return ActiveInkColor(); + return map.get(option)?.checkResult(); } - SetActiveInkColor(StrCast(color)); + map.get(option)?.setMode(); SelectionManager.Docs() .filter(doc => doc.type === DocumentType.INK) - .map(doc => (doc.color = String(color))); + .map(doc => map.get(option)?.setInk(doc)); }); /** WEB diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ee164ab31..de94ed5db 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -33,20 +33,19 @@ export namespace 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') { - if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ". In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s - return `"${field}"`; + switch (typeof field) { + case 'string': + if (field.startsWith('{"')) return `'${field}'`; // bcz: hack ... want to quote the string the right way. if there are nested "'s, then use ' instead of ". In this case, test for the start of a JSON string of the format {"property": ... } and use outer 's instead of "s + return `"${field}"`; + case 'number': + case 'boolean': + return String(field); } - if (typeof field === 'number' || typeof field === 'boolean') return String(field); - if (field === undefined || field === null) return 'null'; - return field[ToScriptString](); + return field?.[ToScriptString]?.() ?? 'null'; } export function toString(field: Field): string { - 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 ''; + if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field); + return field?.[ToString]?.() || ''; } export function IsField(field: any): field is Field; export function IsField(field: any, includeUndefined: true): field is Field | undefined; @@ -79,17 +78,14 @@ export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } -export function NumListCast(field: FieldResult) { - return Cast(field, listSpec('number'), []); +export function NumListCast(field: FieldResult, defaultVal: number[] = []) { + return Cast(field, listSpec('number'), defaultVal); } -export function StrListCast(field: FieldResult) { - return Cast(field, listSpec('string'), []); +export function StrListCast(field: FieldResult, defaultVal: string[] = []) { + return Cast(field, listSpec('string'), defaultVal); } -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 function DocListCast(field: FieldResult, defaultVal: Doc[] = []) { + return Cast(field, listSpec(Doc), defaultVal).filter(d => d instanceof Doc) as Doc[]; } export const WidthSym = Symbol('Width'); @@ -153,17 +149,8 @@ export function updateCachedAcls(doc: Doc) { } @scriptingGlobal -@Deserializable('Doc', updateCachedAcls).withFields(['id']) +@Deserializable('Doc', updateCachedAcls, ['id']) export class Doc extends RefField { - //TODO tfs: these should be temporary... - private static mainDocId: string | undefined; - public static get MainDocId() { - return this.mainDocId; - } - public static set MainDocId(id: string | undefined) { - this.mainDocId = id; - } - @observable public static CurrentlyLoading: Doc[]; // removes from currently loading display @action @@ -582,21 +569,13 @@ 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; - return r || r2 || r3 || r4; + return doc && other && Doc.GetProto(doc) === Doc.GetProto(other); } // Gets the data document for the document. Note: this is mis-named -- it does not specifically // return the doc's proto, but rather recursively searches through the proto inheritance chain // and returns the document who's proto is undefined or whose proto is marked as a base prototype ('isPrototype'). export function GetProto(doc: Doc): 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); return proto === doc ? proto : Doc.GetProto(proto); } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index b5eca78dd..feb419597 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -103,30 +103,9 @@ export class ScriptField extends ObjectField { } this.rawscript = rawscript; this.setterscript = setterscript; - this.script = script ?? (CompileScript('false', { addReturn: true }) as CompiledScript); + this.script = script ?? ScriptField.GetScriptFieldCache('false:') ?? (CompileScript('false', { addReturn: true }) as CompiledScript); } - // init(callback: (res: Field) => any) { - // const options = this.options!; - // const keys = Object.keys(options.options.capturedIds); - // Server.GetFields(keys).then(fields => { - // let captured: { [name: string]: Field } = {}; - // keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); - // const opts: ScriptOptions = { - // addReturn: options.options.addReturn, - // params: options.options.params, - // requiredType: options.options.requiredType, - // capturedVariables: captured - // }; - // const script = CompileScript(options.script, opts); - // if (!script.compiled) { - // throw new Error("Can't compile script"); - // } - // this._script = script; - // callback(this); - // }); - // } - [Copy](): ObjectField { return new ScriptField(this.script, this.setterscript, this.rawscript); } @@ -172,7 +151,7 @@ export class ComputedField extends ScriptField { _lastComputedResult: any; //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); + _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) ?? doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); [ToValue](doc: Doc) { return ComputedField.toValue(doc, this); @@ -181,12 +160,8 @@ export class ComputedField extends ScriptField { return new ComputedField(this.script, this.setterscript, this.rawscript); } - public static MakeScript(script: string, params: object = {}) { - const compiled = ScriptField.CompileScript(script, params, false); - return compiled.compiled ? new ComputedField(compiled) : undefined; - } public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { - const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); + const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables }); const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined; return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined; diff --git a/src/fields/util.ts b/src/fields/util.ts index 6ff13d5d3..70d9ed61f 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -48,15 +48,11 @@ export function TraceMobx() { } const _setterImpl = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { - if (SerializationHelper.IsSerializing()) { + if (SerializationHelper.IsSerializing() || typeof prop === 'symbol') { target[prop] = value; return true; } - if (typeof prop === 'symbol') { - target[prop] = value; - return true; - } if (value !== undefined) { value = value[SelfProxy] || value; } @@ -95,12 +91,8 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number delete target.__fields[prop]; } else { target.__fieldKeys && (target.__fieldKeys[prop] = true); - // if (target.__fields[prop] !== value) { - // console.log("ASSIGN " + prop + " " + value); - // } target.__fields[prop] = value; } - //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; if (writeToServer) { if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } }); @@ -143,13 +135,6 @@ export function denormalizeEmail(email: string) { return email.replace(/__/g, '.'); } -// playground mode allows the user to add/delete documents or make layout changes without them saving to the server -// let playgroundMode = false; - -// export function togglePlaygroundMode() { -// playgroundMode = !playgroundMode; -// } - /** * Copies parent's acl fields to the child */ @@ -317,7 +302,6 @@ export function setter(target: any, in_prop: string | symbol | number, value: an if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; - // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) { if (!prop.startsWith('__')) prop = prop.substring(1); @@ -377,9 +361,9 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea export function deleteProperty(target: any, prop: string | number | symbol) { if (typeof prop === 'symbol') { delete target[prop]; - return true; + } else { + target[SelfProxy][prop] = undefined; } - target[SelfProxy][prop] = undefined; return true; } -- cgit v1.2.3-70-g09d2 From 0e55893d0f7f2a0aa5098df73d0ece5a7f1a4ddf Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 15 Mar 2023 22:33:22 -0400 Subject: fixed up Clone() and export/import collection to work with links, presentations, and contexts better. --- .eslintrc.json | 4 ++ package-lock.json | 78 +++++++++++++--------- package.json | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/trails/PresBox.tsx | 7 +- src/decycler/decycler.d.ts | 2 + src/decycler/decycler.js | 51 ++++++++++++++ src/fields/Doc.ts | 75 ++++++++++++--------- src/server/ApiManagers/UploadManager.ts | 24 +++---- tsconfig.json | 24 ++----- 11 files changed, 170 insertions(+), 104 deletions(-) create mode 100644 src/decycler/decycler.d.ts create mode 100644 src/decycler/decycler.js (limited to 'src') diff --git a/.eslintrc.json b/.eslintrc.json index b9f8e1b7a..43bb53566 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -10,5 +10,9 @@ "object-shorthand": "off", "class-methods-use-this": "off", "single-quote": "off" + }, + "parserOptions": { + "ecmaVersion": 11, + "sourceType": "module" } } diff --git a/package-lock.json b/package-lock.json index 4695adf40..cc51ad9e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -590,15 +590,30 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@eslint-community/eslint-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", + "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", + "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", + "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", + "espree": "^9.5.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -660,6 +675,12 @@ } } }, + "@eslint/js": { + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", + "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "dev": true + }, "@ffmpeg/core": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.10.0.tgz", @@ -6683,12 +6704,15 @@ } }, "eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.34.0.tgz", - "integrity": "sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", + "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.4.1", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.1", + "@eslint/js": "8.36.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -6699,10 +6723,9 @@ "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.4.0", - "esquery": "^1.4.0", + "espree": "^9.5.0", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", @@ -6723,7 +6746,6 @@ "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" @@ -6811,6 +6833,15 @@ "estraverse": "^5.2.0" } }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -7852,23 +7883,6 @@ "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", @@ -7876,9 +7890,9 @@ "dev": true }, "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", + "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", "dev": true, "requires": { "acorn": "^8.8.0", diff --git a/package.json b/package.json index 00ce356f9..2c4c41917 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "cross-env": "^5.2.1", "css-loader": "^2.1.1", "dotenv": "^8.6.0", - "eslint": "^8.18.0", + "eslint": "^8.36.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-node": "^4.1.0", "eslint-config-prettier": "^8.5.0", diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 15d8144fc..f0c140ef1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1796,11 +1796,7 @@ export class CollectionFreeFormView extends CollectionSubView 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: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) }); !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' }); }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 02af30d0c..b7a760c1e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1002,6 +1002,7 @@ export class DocumentViewInternal extends DocComponent Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' }); } + moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); } if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && Doc.ActiveDashboard !== this.props.Document) { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index be40b3592..e79e7472a 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -570,9 +570,9 @@ export class PresBox extends ViewBoxBaseComponent() { } } else { if (bestTarget._panX !== activeItem.presPanX || bestTarget._panY !== activeItem.presPanY || bestTarget._viewScale !== activeItem.presViewScale) { - bestTarget._panX = activeItem.presPanX; - bestTarget._panY = activeItem.presPanY; - bestTarget._viewScale = activeItem.presViewScale; + bestTarget._panX = activeItem.presPanX ?? bestTarget._panX; + bestTarget._panY = activeItem.presPanY ?? bestTarget._panY; + bestTarget._viewScale = activeItem.presViewScale ?? bestTarget._viewScale; changed = true; } } @@ -717,6 +717,7 @@ export class PresBox extends ViewBoxBaseComponent() { zoomTime: activeItem.presMovement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime), effect: activeItem, noSelect: true, + openLocation: OpenWhere.addLeft, anchorDoc: activeItem, easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, zoomTextSelections: BoolCast(activeItem.presZoomText), diff --git a/src/decycler/decycler.d.ts b/src/decycler/decycler.d.ts new file mode 100644 index 000000000..84620f79c --- /dev/null +++ b/src/decycler/decycler.d.ts @@ -0,0 +1,2 @@ +export declare const decycle: Function; +export declare const retrocycle: Function; diff --git a/src/decycler/decycler.js b/src/decycler/decycler.js new file mode 100644 index 000000000..7fb8a45c7 --- /dev/null +++ b/src/decycler/decycler.js @@ -0,0 +1,51 @@ +/// +/// this is a modified copy of the npm project: https://www.npmjs.com/package/json-decycle +/// the original code is used as replacer when stringifying JSON objects that have cycles. +/// However, we want an additional replacer that stringifies Dash Fields and Docs in a custom way. +/// So this modified code allows for a custom replacer to be added to this object that will run before this replacer +/// + +const g = e => typeof e === 'object' && e != null && !(e instanceof Boolean) && !(e instanceof Date) && !(e instanceof Number) && !(e instanceof RegExp) && !(e instanceof String); +const b = e => String('#') + e.map(t => String(t).replace(/~/g, '~0').replace(/\//g, '~1')).join('/'); +// eslint-disable-next-line node/no-unsupported-features/es-syntax +export function decycle(replacer) { + const e = new WeakMap(); + return function (n, rr) { + const r = replacer(n, rr); + if (n !== '$ref' && g(r)) { + if (e.has(r)) return { $ref: b(e.get(r)) }; + e.set(r, [...(e.get(this) === undefined ? [] : e.get(this)), n]); + } + return r; + }; +} +// eslint-disable-next-line node/no-unsupported-features/es-syntax +export function retrocycle() { + const e = new WeakMap(); + const t = new WeakMap(); + const n = new Set(); + function r(o) { + const c = o.$ref.slice(1).split('/'); + let s; + let a = this; + // eslint-disable-next-line no-plusplus + for (let p = 0; p < c.length; p++) { + s = c[p].replace(/~1/g, '/').replace(/~0/g, '~'); + a = a[s]; + } + const f = e.get(o); + f[t.get(o)] = a; + } + return function (c, s) { + if (c === '$ref') n.add(this); + else if (g(s)) { + const f = c === '' && Object.keys(this).length === 1; + if (f) n.forEach(r, this); + else { + e.set(s, this); + t.set(s, c); + } + } + return s; + }; +} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index de94ed5db..168e29dd5 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -10,6 +10,7 @@ import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGloba import { SelectionManager } from '../client/util/SelectionManager'; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; import { UndoManager } from '../client/util/UndoManager'; +import { decycle } from '../decycler/decycler'; import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; import { DateField } from './DateField'; import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols'; @@ -25,7 +26,6 @@ import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Ty 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); @@ -701,22 +701,12 @@ export namespace Doc { return bestAlias ?? Doc.MakeAlias(doc); } - export async function makeClone( - doc: Doc, - cloneMap: Map, - linkMap: Map, - rtfs: { copy: Doc; key: string; field: RichTextField }[], - exclusions: string[], - topLevelExclusions: 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, ...topLevelExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; + const filter = [...exclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; await Promise.all( Object.keys(doc).map(async key => { if (filter.includes(key)) return; @@ -727,10 +717,10 @@ export namespace Doc { 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))); + 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 + 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) { @@ -740,13 +730,12 @@ export namespace Doc { } } }; - 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)); + const docAtKey = doc[key]; + if (docAtKey instanceof Doc) { + if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || (key === 'proto' && cloneMap.has(doc[Id])) || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { + assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); + } else { + assignKey(docAtKey); } } else { if (field instanceof RefField) { @@ -765,8 +754,8 @@ export namespace Doc { }) ); for (const link of Array.from(doc[DirectLinksSym])) { - const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, [], dontCreate, asBranch); - linkMap.set(link, linkClone); + const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); + linkMap.set(link[Id], linkClone); } if (!dontCreate) { Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); @@ -779,11 +768,29 @@ export namespace Doc { Doc.AddFileOrphan(copy); return copy; } + export function repairClone(doc: Doc, cloned: Doc[], visited: Set) { + if (visited.has(doc)) return; + visited.add(doc); + Object.keys(doc).map(key => { + const docAtKey = DocCast(doc[key]); + if (docAtKey && !Doc.IsSystem(docAtKey)) { + if (!cloned.includes(docAtKey)) { + doc[key] = undefined; + } else { + repairClone(docAtKey, cloned, visited); + } + } + }); + } export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { - const linkMap = 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'], ['context'], dontCreate, asBranch); - Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true)); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch); + const repaired = new Set(); + const linkedDocs = Array.from(linkMap.values()); + const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; + clonedDocs.map(clone => Doc.repairClone(clone, Array.from(cloneMap.values()), repaired)); + linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); @@ -797,7 +804,7 @@ export namespace Doc { 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 }; + return { clone: copy, map: cloneMap, linkMap }; } export async function Zip(doc: Doc) { @@ -806,9 +813,10 @@ export namespace Doc { // a.href = url; // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); - const { clone, map } = await Doc.MakeClone(doc, true); + const { clone, map, linkMap } = await Doc.MakeClone(doc, true); + clone.LINKS = new List(Array.from(linkMap.values())); function replacer(key: any, value: any) { - if (['branchOf', 'cloneOf', 'context', 'cursors'].includes(key)) return undefined; + if (['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; else if (value instanceof Doc) { if (key !== 'field' && Number.isNaN(Number(key))) { const __fields = value[FieldsSym](); @@ -833,7 +841,7 @@ export namespace Doc { const docs: { [id: string]: any } = {}; Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); - const docString = JSON.stringify({ id: doc[Id], docs }, replacer); + const docString = JSON.stringify({ id: doc[Id], docs }, decycle(replacer)); const zip = new JSZip(); @@ -1520,7 +1528,8 @@ export namespace Doc { const response = await fetch(upload, { method: 'POST', body: formData }); const json = await response.json(); if (json !== 'error') { - const doc = await DocServer.GetRefField(json); + const doc = DocCast(await DocServer.GetRefField(json)); + (await DocListCastAsync(doc?.LINKS))?.forEach(link => LinkManager.Instance.addLink(link)); return doc; } } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index fe4c475c9..9bacbd5c8 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -1,19 +1,19 @@ -import ApiManager, { Registration } from './ApiManager'; -import { Method, _success } from '../RouteManager'; import * as formidable from 'formidable'; -import v4 = require('uuid/v4'); -const AdmZip = require('adm-zip'); -import { extname, basename, dirname } from 'path'; import { createReadStream, createWriteStream, unlink, writeFile } from 'fs'; -import { publicDirectory, filesDirectory } from '..'; -import { Database } from '../database'; -import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils'; +import { basename, dirname, extname, normalize } from 'path'; import * as sharp from 'sharp'; -import { AcceptableMedia, Upload } from '../SharedMediaTypes'; -import { normalize } from 'path'; +import { filesDirectory, publicDirectory } from '..'; +import { retrocycle } from '../../decycler/decycler'; +import { DashUploadUtils, InjectSize, SizeSuffix } from '../DashUploadUtils'; +import { Database } from '../database'; +import { Method, _success } from '../RouteManager'; import RouteSubscriber from '../RouteSubscriber'; -const imageDataUri = require('image-data-uri'); +import { AcceptableMedia, Upload } from '../SharedMediaTypes'; +import ApiManager, { Registration } from './ApiManager'; import { SolrManager } from './SearchManager'; +import v4 = require('uuid/v4'); +const AdmZip = require('adm-zip'); +const imageDataUri = require('image-data-uri'); const fs = require('fs'); export enum Directory { @@ -252,7 +252,7 @@ export default class UploadManager extends ApiManager { }); const json = zip.getEntry('doc.json'); try { - const data = JSON.parse(json.getData().toString('utf8')); + const data = JSON.parse(json.getData().toString('utf8'), retrocycle()); const datadocs = data.docs; id = getId(data.id); const docs = Object.keys(datadocs).map(key => datadocs[key]); diff --git a/tsconfig.json b/tsconfig.json index 993ab13b9..bff9255db 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,34 +2,22 @@ "compilerOptions": { "target": "es5", "downlevelIteration": true, - // "module": "system", "removeComments": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, + "moduleDetection": "auto", "strict": true, "jsx": "react", "allowJs": true, "sourceMap": true, "outDir": "dist", - "lib": [ - "dom", - "es2015" - ], - "typeRoots": [ - "node_modules/@types", - "./src/typings" - ], - "types": [ - "youtube", - "node" - ] + "lib": ["dom", "es2015"], + "typeRoots": ["node_modules/@types", "./src/typings"], + "types": ["youtube", "node"] }, // "exclude": [ // "node_modules", // "static" // ], - "typeRoots": [ - "./node_modules/@types", - "./src/typings" - ] -} \ No newline at end of file + "typeRoots": ["./node_modules/@types", "./src/typings"] +} -- cgit v1.2.3-70-g09d2 From d2bca182a311e95515bbff8fb378b29918fe99d7 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 16 Mar 2023 12:00:59 -0400 Subject: fixed export/import collectoin --- src/fields/Doc.ts | 34 +++++++++++++++++++-------------- src/server/ApiManagers/UploadManager.ts | 4 +++- 2 files changed, 23 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 168e29dd5..6543679ad 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -732,7 +732,7 @@ export namespace Doc { }; const docAtKey = doc[key]; if (docAtKey instanceof Doc) { - if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || (key === 'proto' && cloneMap.has(doc[Id])) || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { + if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || key === 'proto'|| ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); } else { assignKey(docAtKey); @@ -757,8 +757,8 @@ export namespace Doc { const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); linkMap.set(link[Id], linkClone); } - if (!dontCreate) { Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); + if (!dontCreate) { asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc); if (!Doc.IsPrototype(copy)) { Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy)); @@ -768,16 +768,19 @@ export namespace Doc { Doc.AddFileOrphan(copy); return copy; } - export function repairClone(doc: Doc, cloned: Doc[], visited: Set) { - if (visited.has(doc)) return; - visited.add(doc); - Object.keys(doc).map(key => { - const docAtKey = DocCast(doc[key]); + export function repairClone(clone: Doc, cloneMap: Map, visited: Set) { + if (visited.has(clone)) return; + visited.add(clone); + Object.keys(clone).filter(key => key !== "cloneOf").map(key => { + const docAtKey = DocCast(clone[key]); if (docAtKey && !Doc.IsSystem(docAtKey)) { - if (!cloned.includes(docAtKey)) { - doc[key] = undefined; + if (!Array.from(cloneMap.values()).includes(docAtKey)) { + if (cloneMap.has(docAtKey[Id])) { + clone[key] = cloneMap.get(docAtKey[Id]); + } + else clone[key] = undefined; } else { - repairClone(docAtKey, cloned, visited); + repairClone(docAtKey, cloneMap, visited); } } }); @@ -789,7 +792,7 @@ export namespace Doc { const repaired = new Set(); const linkedDocs = Array.from(linkMap.values()); const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; - clonedDocs.map(clone => Doc.repairClone(clone, Array.from(cloneMap.values()), repaired)); + clonedDocs.map(clone => Doc.repairClone(clone, cloneMap, repaired)); linkedDocs.map((link: Doc) => LinkManager.Instance.addLink(link, true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { @@ -813,7 +816,7 @@ export namespace Doc { // a.href = url; // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); - const { clone, map, linkMap } = await Doc.MakeClone(doc, true); + const { clone, map, linkMap } = await Doc.MakeClone(doc, false); clone.LINKS = new List(Array.from(linkMap.values())); function replacer(key: any, value: any) { if (['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; @@ -841,7 +844,7 @@ export namespace Doc { const docs: { [id: string]: any } = {}; Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); - const docString = JSON.stringify({ id: doc[Id], docs }, decycle(replacer)); + const docString = JSON.stringify({ id: clone[Id], docs }, decycle(replacer)); const zip = new JSZip(); @@ -1527,8 +1530,11 @@ export namespace Doc { formData.append('remap', 'true'); const response = await fetch(upload, { method: 'POST', body: formData }); const json = await response.json(); + console.log(json) if (json !== 'error') { - const doc = DocCast(await DocServer.GetRefField(json)); + await DocServer.GetRefFields(json.docids as string[]); + const doc = DocCast(await DocServer.GetRefField(json.id)); + console.log("Doc = ", doc, doc?.title); (await DocListCastAsync(doc?.LINKS))?.forEach(link => LinkManager.Instance.addLink(link)); return doc; } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 9bacbd5c8..5da3dfd3f 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -228,6 +228,7 @@ export default class UploadManager extends ApiManager { form.parse(req, async (_err, fields, files) => { remap = fields.remap !== 'false'; let id: string = ''; + let docids: string[] = []; try { for (const name in files) { const f = files[name]; @@ -257,6 +258,7 @@ export default class UploadManager extends ApiManager { id = getId(data.id); const docs = Object.keys(datadocs).map(key => datadocs[key]); docs.forEach(mapFn); + docids = docs.map(doc => doc.id) await Promise.all( docs.map( (doc: any) => @@ -279,7 +281,7 @@ export default class UploadManager extends ApiManager { unlink(path_2, () => {}); } SolrManager.update(); - res.send(JSON.stringify(id || 'error')); + res.send(JSON.stringify({id, docids} || 'error')); } catch (e) { console.log(e); } -- cgit v1.2.3-70-g09d2 From de0df48cba8e89256a3208fbadfd5afaaa9e22d3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 16 Mar 2023 21:10:23 -0400 Subject: added importing and exporting of assets (images, pdfs, etc) to collection importer/exporter --- src/JSZipUtils.js | 142 ++++++++++++++++++++++++++++++++ src/fields/Doc.ts | 82 ++++++++++++------ src/server/ApiManagers/UploadManager.ts | 28 ++++--- 3 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 src/JSZipUtils.js (limited to 'src') diff --git a/src/JSZipUtils.js b/src/JSZipUtils.js new file mode 100644 index 000000000..5ce1bd471 --- /dev/null +++ b/src/JSZipUtils.js @@ -0,0 +1,142 @@ +var JSZipUtils = {}; +// just use the responseText with xhr1, response with xhr2. +// The transformation doesn't throw away high-order byte (with responseText) +// because JSZip handles that case. If not used with JSZip, you may need to +// do it, see https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data +JSZipUtils._getBinaryFromXHR = function (xhr) { + // for xhr.responseText, the 0xFF mask is applied by JSZip + return xhr.response || xhr.responseText; +}; + +// taken from jQuery +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch (e) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject('Microsoft.XMLHTTP'); + } catch (e) {} +} + +// Create the request object +var createXHR = + typeof window !== 'undefined' && window.ActiveXObject + ? /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function () { + return createStandardXHR() || createActiveXHR(); + } + : // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +/** + * @param {string} path The path to the resource to GET. + * @param {function|{callback: function, progress: function}} options + * @return {Promise|undefined} If no callback is passed then a promise is returned + */ +JSZipUtils.getBinaryContent = function (path, options) { + var promise, resolve, reject; + var callback; + + if (!options) { + options = {}; + } + + // backward compatible callback + if (typeof options === 'function') { + callback = options; + options = {}; + } else if (typeof options.callback === 'function') { + // callback inside options object + callback = options.callback; + } + + if (!callback && typeof Promise !== 'undefined') { + promise = new Promise(function (_resolve, _reject) { + resolve = _resolve; + reject = _reject; + }); + } else { + resolve = function (data) { + callback(null, data); + }; + reject = function (err) { + callback(err, null); + }; + } + + /* + * Here is the tricky part : getting the data. + * In firefox/chrome/opera/... setting the mimeType to 'text/plain; charset=x-user-defined' + * is enough, the result is in the standard xhr.responseText. + * cf https://developer.mozilla.org/En/XMLHttpRequest/Using_XMLHttpRequest#Receiving_binary_data_in_older_browsers + * In IE <= 9, we must use (the IE only) attribute responseBody + * (for binary data, its content is different from responseText). + * In IE 10, the 'charset=x-user-defined' trick doesn't work, only the + * responseType will work : + * http://msdn.microsoft.com/en-us/library/ie/hh673569%28v=vs.85%29.aspx#Binary_Object_upload_and_download + * + * I'd like to use jQuery to avoid this XHR madness, but it doesn't support + * the responseType attribute : http://bugs.jquery.com/ticket/11461 + */ + try { + var xhr = createXHR(); + + xhr.open('GET', path, true); + + // recent browsers + if ('responseType' in xhr) { + xhr.responseType = 'arraybuffer'; + } + + // older browser + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + xhr.onreadystatechange = function (event) { + // use `xhr` and not `this`... thanks IE + if (xhr.readyState === 4) { + if (xhr.status === 200 || xhr.status === 0) { + try { + resolve(JSZipUtils._getBinaryFromXHR(xhr)); + } catch (err) { + reject(new Error(err)); + } + } else { + reject(new Error('Ajax error for ' + path + ' : ' + this.status + ' ' + this.statusText)); + } + } + }; + + if (options.progress) { + xhr.onprogress = function (e) { + options.progress({ + path: path, + originalEvent: e, + percent: (e.loaded / e.total) * 100, + loaded: e.loaded, + total: e.total, + }); + }; + } + + xhr.send(); + } catch (e) { + reject(new Error(e), null); + } + + // returns a promise or undefined depending on whether a callback was + // provided + return promise; +}; + +// export +module.exports = JSZipUtils; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6543679ad..deda8aa1f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -26,6 +26,7 @@ import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Ty 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 * as JSZipUtils from '../JSZipUtils'; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key); @@ -732,7 +733,7 @@ export namespace Doc { }; const docAtKey = doc[key]; if (docAtKey instanceof Doc) { - if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || key === 'proto'|| ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { + if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || key === 'proto' || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); } else { assignKey(docAtKey); @@ -757,7 +758,7 @@ export namespace Doc { const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); linkMap.set(link[Id], linkClone); } - Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); + Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); if (!dontCreate) { asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc); if (!Doc.IsPrototype(copy)) { @@ -768,22 +769,23 @@ export namespace Doc { Doc.AddFileOrphan(copy); return copy; } - export function repairClone(clone: Doc, cloneMap: Map, visited: Set) { + export function repairClone(clone: Doc, cloneMap: Map, visited: Set) { if (visited.has(clone)) return; visited.add(clone); - Object.keys(clone).filter(key => key !== "cloneOf").map(key => { - const docAtKey = DocCast(clone[key]); - if (docAtKey && !Doc.IsSystem(docAtKey)) { - if (!Array.from(cloneMap.values()).includes(docAtKey)) { - if (cloneMap.has(docAtKey[Id])) { - clone[key] = cloneMap.get(docAtKey[Id]); + Object.keys(clone) + .filter(key => key !== 'cloneOf') + .map(key => { + const docAtKey = DocCast(clone[key]); + if (docAtKey && !Doc.IsSystem(docAtKey)) { + if (!Array.from(cloneMap.values()).includes(docAtKey)) { + if (cloneMap.has(docAtKey[Id])) { + clone[key] = cloneMap.get(docAtKey[Id]); + } else clone[key] = undefined; + } else { + repairClone(docAtKey, cloneMap, visited); } - else clone[key] = undefined; - } else { - repairClone(docAtKey, cloneMap, visited); } - } - }); + }); } export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { const linkMap = new Map(); @@ -818,8 +820,9 @@ export namespace Doc { // a.click(); const { clone, map, linkMap } = await Doc.MakeClone(doc, false); clone.LINKS = new List(Array.from(linkMap.values())); + const proms = [] as string[]; function replacer(key: any, value: any) { - if (['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; + if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; else if (value instanceof Doc) { if (key !== 'field' && Number.isNaN(Number(key))) { const __fields = value[FieldsSym](); @@ -829,9 +832,14 @@ export namespace Doc { } } 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 ImageField) { + const extension = value.url.href.replace(/.*\./, ''); + proms.push(value.url.href.replace('.' + extension, '_o.' + extension)); + return { url: value.url.href, __type: 'image' }; + } else if (value instanceof PdfField) { + proms.push(value.url.href); + 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' }; @@ -846,6 +854,32 @@ export namespace Doc { Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); const docString = JSON.stringify({ id: clone[Id], docs }, decycle(replacer)); + let generateZIP = (proms: string[]) => { + var zip = new JSZip(); + var count = 0; + var zipFilename = 'dashExport.zip'; + + proms + .filter(url => url.startsWith(window.location.origin)) + .forEach((url, i) => { + var filename = proms[i].replace(window.location.origin + '/', '').replace(/\//g, '%%%'); + // loading a file and add it in a zip file + JSZipUtils.getBinaryContent(url, function (err: any, data: any) { + if (err) { + throw err; // or handle the error + } + zip.file(filename, data, { binary: true }); + count++; + if (count == proms.length) { + zip.file('doc.json', docString); + zip.generateAsync({ type: 'blob' }).then(function (content) { + saveAs(content, zipFilename); + }); + } + }); + }); + }; + generateZIP(proms); const zip = new JSZip(); zip.file('doc.json', docString); @@ -857,10 +891,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). @@ -1530,12 +1564,12 @@ export namespace Doc { formData.append('remap', 'true'); const response = await fetch(upload, { method: 'POST', body: formData }); const json = await response.json(); - console.log(json) + console.log(json); if (json !== 'error') { await DocServer.GetRefFields(json.docids as string[]); const doc = DocCast(await DocServer.GetRefField(json.id)); - console.log("Doc = ", doc, doc?.title); (await DocListCastAsync(doc?.LINKS))?.forEach(link => LinkManager.Instance.addLink(link)); + doc.LINKS = undefined; return doc; } } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 5da3dfd3f..6e28268a9 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -235,18 +235,22 @@ export default class UploadManager extends ApiManager { const path_2 = Array.isArray(f) ? '' : f.path; const zip = new AdmZip(path_2); zip.getEntries().forEach((entry: any) => { - if (!entry.entryName.startsWith('files/')) return; - let directory = dirname(entry.entryName) + '/'; - const extension = extname(entry.entryName); - const base = basename(entry.entryName).split('.')[0]; + let entryName = entry.entryName.replace(/%%%/g, '/'); + if (!entryName.startsWith('files/')) { + return; + } + const extension = extname(entryName); + const pathname = publicDirectory + '/' + entry.entryName; + const targetname = publicDirectory + '/' + entryName; try { zip.extractEntryTo(entry.entryName, publicDirectory, true, false); - directory = '/' + directory; - - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_o' + extension)); - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_s' + extension)); - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_m' + extension)); - createReadStream(publicDirectory + directory + base + extension).pipe(createWriteStream(publicDirectory + directory + base + '_l' + extension)); + createReadStream(pathname).pipe(createWriteStream(targetname)); + if (extension !== '.pdf') { + createReadStream(pathname).pipe(createWriteStream(targetname.replace('_o' + extension, '_s' + extension))); + createReadStream(pathname).pipe(createWriteStream(targetname.replace('_o' + extension, '_m' + extension))); + createReadStream(pathname).pipe(createWriteStream(targetname.replace('_o' + extension, '_l' + extension))); + } + unlink(pathname, () => {}); } catch (e) { console.log(e); } @@ -258,7 +262,7 @@ export default class UploadManager extends ApiManager { id = getId(data.id); const docs = Object.keys(datadocs).map(key => datadocs[key]); docs.forEach(mapFn); - docids = docs.map(doc => doc.id) + docids = docs.map(doc => doc.id); await Promise.all( docs.map( (doc: any) => @@ -281,7 +285,7 @@ export default class UploadManager extends ApiManager { unlink(path_2, () => {}); } SolrManager.update(); - res.send(JSON.stringify({id, docids} || 'error')); + res.send(JSON.stringify({ id, docids } || 'error')); } catch (e) { console.log(e); } -- cgit v1.2.3-70-g09d2 From 4ecac6f070759e79c254f4a356d02a871ac6abb5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Mar 2023 09:24:34 -0400 Subject: fixed copy paste to clone links if both anchors are copied. adjusted API for clone to have a cloneLinks field. --- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/PreviewCursor.tsx | 10 ++----- src/fields/Doc.ts | 51 ++++++++++++++++++++---------------- 3 files changed, 32 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 3fbf6e445..f849b21e3 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -372,7 +372,7 @@ export class KeyManager { list.push(doc); } if (count === docids.length) { - const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d)).clone : d))); + const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d, ['links'])).clone : d))); if (added.length) { added.map(doc => (doc.context = targetDataDoc)); undoBatch(() => { diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 3712fff58..95ae65d7a 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -81,14 +81,8 @@ export class PreviewCursor extends React.Component<{}> { const batch = UndoManager.StartBatch('cloning'); { - const docs = await Promise.all( - docids - .filter((did, i) => i) - .map(async did => { - const doc = Cast(await DocServer.GetRefField(did), Doc, null); - return clone ? (await Doc.MakeClone(doc)).clone : doc; - }) - ); + const toCopy = await Promise.all(docids.slice(1).map(async did => Cast(await DocServer.GetRefField(did), Doc, null))); + const docs = clone ? (await Promise.all(Doc.MakeClones(toCopy, false))).map(res => res.clone) : toCopy; const firstx = docs.length ? NumCast(docs[0].x) + ptx - newPoint[0] : 0; const firsty = docs.length ? NumCast(docs[0].y) + pty - newPoint[1] : 0; docs.map(doc => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index deda8aa1f..3169031b4 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -702,28 +702,28 @@ export namespace Doc { return bestAlias ?? Doc.MakeAlias(doc); } - 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[], cloneLinks: 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 = new Doc(undefined, true); cloneMap.set(doc[Id], copy); const filter = [...exclusions, ...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 assignKey = (val: any) => (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)); + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch))); + 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 + assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)); // reference documents except copy documents that are expanded template fields } else { - !dontCreate && assignKey(ObjectField.MakeCopy(field)); + 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 }); @@ -734,7 +734,7 @@ export namespace Doc { const docAtKey = doc[key]; if (docAtKey instanceof Doc) { if (!Doc.IsSystem(docAtKey) && (key === 'annotationOn' || key === 'proto' || ((key === 'anchor1' || key === 'anchor2') && doc.author === Doc.CurrentUserEmail))) { - assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); + assignKey(await Doc.makeClone(docAtKey, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)); } else { assignKey(docAtKey); } @@ -742,7 +742,7 @@ export namespace Doc { if (field instanceof RefField) { assignKey(field); } else if (cfield instanceof ComputedField) { - !dontCreate && assignKey(cfield[Copy]()); + assignKey(cfield[Copy]()); // ComputedField.MakeFunction(cfield.script.originalScript)); } else if (field instanceof ObjectField) { await copyObjectField(field); @@ -754,18 +754,21 @@ export namespace Doc { } }) ); - for (const link of Array.from(doc[DirectLinksSym])) { - const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); - linkMap.set(link[Id], linkClone); - } - Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); - if (!dontCreate) { - asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc); - if (!Doc.IsPrototype(copy)) { - Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy)); + Array.from(doc[DirectLinksSym]).forEach(async link => { + if ( + cloneLinks || + ((cloneMap.has(DocCast(link.anchor1)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor1)?.annotationOn)?.[Id])) && (cloneMap.has(DocCast(link.anchor2)?.[Id]) || cloneMap.has(DocCast(DocCast(link.anchor2)?.annotationOn)?.[Id]))) + ) { + linkMap.set(link[Id], await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, cloneLinks, asBranch)); } - cloneMap.set(doc[Id], copy); + }); + 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)); } + cloneMap.set(doc[Id], copy); + Doc.AddFileOrphan(copy); return copy; } @@ -787,10 +790,14 @@ export namespace Doc { } }); } - export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { + export function MakeClones(docs: Doc[], cloneLinks: boolean, asBranch = false, cloneMap: Map = new Map()) { + return docs.map(doc => Doc.MakeClone(doc, cloneLinks, asBranch, cloneMap)); + } + + export async function MakeClone(doc: Doc, cloneLinks = true, 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 copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], cloneLinks, asBranch); const repaired = new Set(); const linkedDocs = Array.from(linkMap.values()); const clonedDocs = [...Array.from(cloneMap.values()), ...linkedDocs]; @@ -818,7 +825,7 @@ export namespace Doc { // a.href = url; // a.download = `DocExport-${this.props.Document[Id]}.zip`; // a.click(); - const { clone, map, linkMap } = await Doc.MakeClone(doc, false); + const { clone, map, linkMap } = await Doc.MakeClone(doc); clone.LINKS = new List(Array.from(linkMap.values())); const proms = [] as string[]; function replacer(key: any, value: any) { -- cgit v1.2.3-70-g09d2 From bdccbc7a37216ffc88920ec48f9119a8ada0be60 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Mar 2023 10:54:30 -0400 Subject: cleaned up highlighting styles a bit. fixed stackedTimeline screen to local xf. --- src/client/documents/Documents.ts | 3 ++- src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/StyleProvider.tsx | 15 +++++++-------- .../views/collections/CollectionStackedTimeline.scss | 2 +- .../views/collections/CollectionStackedTimeline.tsx | 3 ++- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 ++++ src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/button/FontIconBox.scss | 20 -------------------- 10 files changed, 20 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 457811e26..ff6c8d440 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -245,6 +245,7 @@ export class DocumentOptions { childContextMenuIcons?: List; followLinkZoom?: boolean; // whether to zoom to the target of a link hideLinkButton?: boolean; // whether the blue link counter button should be hidden + disableDocBrushing?: boolean; // whether to suppress border highlighting hideDecorationTitle?: boolean; hideOpenButton?: boolean; hideResizeHandles?: boolean; @@ -584,7 +585,7 @@ export namespace Docs { DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%' }, + options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40 }, }, ], [ diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index ebbe20077..11e9dd9c9 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -133,7 +133,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const containerDoc = dv.rootDoc; //containerDoc.followAllLinks = // containerDoc.noShadow = - // containerDoc.noHighlighting = + // containerDoc.disableDocBrushing = // containerDoc._forceActive = containerDoc._fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox; containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index ce764c7bf..dba5d703f 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -112,16 +112,15 @@ export function DefaultStyleProvider(doc: Opt, props: Opt dv.rootDoc === doc); + const highlightIndex = Doc.isBrushedHighlightedDegree(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0); + const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? 'black' : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex]; - const excludeTypes = [DocumentType.FONTICON]; - let highlighting = !props?.disableDocBrushing && highlightIndex && !excludeTypes.includes(doc.type as any) && doc._viewType !== CollectionViewType.Linear; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way - if (highlighting && props?.focus !== emptyFunction && StrCast(doc.title) !== '[pres element template]') { + if (highlightIndex) { return { highlightStyle, - highlightColor: highlightIndex !== Doc.DocBrushStatus.highlighted && SelectionManager.Views().some(dv => dv.rootDoc === doc) ? 'black' : highlightColor, + highlightColor, highlightIndex, highlightStroke: doc.type === DocumentType.INK, }; @@ -218,7 +217,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { } @computed get tabBorderColor() { const highlight = DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting); - if (highlight?.highlightIndex >= Doc.DocBrushStatus.highlighted) return highlight.highlightColor; + if (highlight?.highlightIndex === Doc.DocBrushStatus.highlighted) return highlight.highlightColor; return 'transparent'; } @computed get tabColor() { diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 890ecc1b2..0b95d712c 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -445,7 +445,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive)); - timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.bottomControlsHeight); + timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight); setPlayheadTime = (time: number) => (this._ele!.currentTime = this.layoutDoc._currentTimecode = time); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b7a760c1e..6ae102a0c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1489,6 +1489,10 @@ export class DocumentViewInternal extends DocComponent { diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index a1ca777b3..7fe1436c7 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -43,10 +43,6 @@ cursor: pointer; flex-direction: column; - &:hover { - background-color: rgba(0, 0, 0, 0.3) !important; - } - svg { width: 50% !important; height: 50%; @@ -68,10 +64,6 @@ justify-content: center; align-items: center; justify-items: center; - - &:hover { - filter: brightness(0.85) !important; - } } &.tglBtn, @@ -220,10 +212,6 @@ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); border-radius: 3px; } - - &:hover { - background-color: rgba(0, 0, 0, 0.3) !important; - } } &.colorBtnLabel { @@ -248,10 +236,6 @@ align-content: center; align-items: center; - &:hover { - background-color: rgba(0, 0, 0, 0.3) !important; - } - .menuButton-dropdownList { position: absolute; width: 150px; @@ -283,10 +267,6 @@ cursor: pointer; background: transparent; - &:hover { - background-color: rgba(0, 0, 0, 0.3) !important; - } - &.slider { color: $white; cursor: pointer; -- cgit v1.2.3-70-g09d2 From 8aeab8c3f25c556e39f3e9e58f9c321e79459df8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Mar 2023 12:22:03 -0400 Subject: fixed scriptingbox to remove script if text is empty. fixed scripting with capturedvariables not to cache scripts with lists of captured documents. fixed runtime warnings with stackedTimelined and AudioBox --- src/client/util/Scripting.ts | 8 +++++++- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/MainView.tsx | 3 +-- src/client/views/StyleProvider.tsx | 2 +- src/client/views/TemplateMenu.tsx | 1 - .../views/collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 10 +++------- src/client/views/nodes/DocumentView.tsx | 4 ---- src/client/views/nodes/ScriptingBox.tsx | 16 +++++++++------- src/fields/Doc.ts | 9 +-------- 10 files changed, 24 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index d32298c83..f17a98616 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -7,7 +7,9 @@ import * as typescriptlib from '!!raw-loader!./type_decls.d'; import * as ts from 'typescript'; import { Doc, Field } from '../../fields/Doc'; +import { ToScriptString } from '../../fields/FieldSymbols'; import { ObjectField } from '../../fields/ObjectField'; +import { RefField } from '../../fields/RefField'; import { ScriptField } from '../../fields/ScriptField'; import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals'; export { ts }; @@ -180,7 +182,11 @@ function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, inde export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { const captured = options.capturedVariables ?? {}; - const signature = Object.keys(captured).reduce((p, v) => p + `${v}=${captured[v] instanceof ObjectField ? 'XXX' : captured[v].toString()}`, ''); + const signature = Object.keys(captured).reduce((p, v) => { + const formatCapture = (obj: any) => `${v}=${obj instanceof RefField ? 'XXX' : obj.toString()}`; + if (captured[v] instanceof Array) return p + (captured[v] as any).map(formatCapture); + return p + formatCapture(captured[v]); + }, ''); const found = ScriptField.GetScriptFieldCache(script + ':' + signature); if (found) return found as CompiledScript; const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index af868ba9c..9389fed01 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -518,7 +518,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ) }>
- {} +
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2e04ca3dd..118635a38 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -10,7 +10,7 @@ import 'normalize.css'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; -import { DocCast, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; @@ -57,7 +57,6 @@ import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview } from './nodes/LinkDocPreview'; import { RadialMenu } from './nodes/RadialMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; -import { PresBox } from './nodes/trails'; import { OverlayView } from './OverlayView'; import { AnchorMenu } from './pdf/AnchorMenu'; import { PreviewCursor } from './PreviewCursor'; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index dba5d703f..b11117b11 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -5,7 +5,7 @@ import { action, runInAction } from 'mobx'; import { extname } from 'path'; import { Doc, Opt } from '../../fields/Doc'; import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; -import { DashColor, emptyFunction, lightOrDark } from '../../Utils'; +import { DashColor, lightOrDark } from '../../Utils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 863829a51..681ff66e0 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,7 +1,6 @@ import { action, computed, observable, ObservableSet, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast } from '../../fields/Doc'; -import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index f9249e7d5..302d4a464 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -565,7 +565,7 @@ export class CollectionStackedTimeline extends CollectionSubView Math.max(m, o.level), 0) + 2; - return ( + return this.clipDuration === 0 ? null : (
{ - e.stopPropagation(); - }} - onChange={(e: React.ChangeEvent) => { - this.zoom(Number(e.target.value)); - }} + onPointerDown={(e: React.PointerEvent) => e.stopPropagation()} + onChange={(e: React.ChangeEvent) => this.zoom(Number(e.target.value))} />
)} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6ae102a0c..b7a760c1e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1489,10 +1489,6 @@ export class DocumentViewInternal extends DocComponent (params[p.split(':')[0].trim()] = p.split(':')[1].trim())); - const result = CompileScript(this.rawText, { - editable: true, - transformer: DocumentIconContainer.getTransformer(), - params, - typecheck: false, - }); - Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : new ScriptField(undefined, undefined, this.rawText), true); + const result = !this.rawText.trim() + ? ({ compiled: false, errors: undefined } as any) + : CompileScript(this.rawText, { + editable: true, + transformer: DocumentIconContainer.getTransformer(), + params, + typecheck: false, + }); + Doc.SetInPlace(this.rootDoc, this.fieldKey, result.compiled ? new ScriptField(result, undefined, this.rawText) : undefined, true); this.onError(result.compiled ? undefined : result.errors); return result.compiled; }; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 3169031b4..44314dca2 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -518,20 +518,13 @@ export namespace Doc { } export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { if (key.startsWith('_')) key = key.substring(1); - const hasProto = doc.proto instanceof Doc; + const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined; const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1; if (onDeleg || !hasProto || (!onProto && !defaultProto)) { doc[key] = value; } 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; - - if (proto) { - proto[key] = value; - } - } export function GetAllPrototypes(doc: Doc): Doc[] { const protos: Doc[] = []; let d: Opt = doc; -- cgit v1.2.3-70-g09d2 From c885ae59ea378648dcc70b6f17dac2d3999c60b1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Mar 2023 17:03:32 -0400 Subject: fixed clicking and dragging stackedTimeline anchors. updated followLink parameters --- src/client/util/LinkFollower.ts | 2 +- .../collections/CollectionStackedTimeline.tsx | 39 ++++++++++++---------- src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 17 ++++------ src/client/views/nodes/LinkAnchorBox.tsx | 2 +- src/client/views/nodes/LinkDocPreview.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 2 +- 7 files changed, 33 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index eacbcc0e3..785018990 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -27,7 +27,7 @@ export class LinkFollower { // follows a link - if the target is on screen, it highlights/pans to it. // if the target isn't onscreen, then it will open up the target in the lightbox, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); - public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => { + public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, altKey: boolean) => { const batch = UndoManager.StartBatch('follow link click'); runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value LinkFollower.traverseLink( diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 302d4a464..d4e83f609 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; +import { emptyFunction, formatTime, OmitKeys, returnFalse, returnNone, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; @@ -180,12 +180,15 @@ export class CollectionStackedTimeline extends CollectionSubView { - const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; + const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); if (this.layoutDoc.autoPlayAnchors) { if (this.props.playing()) this.props.Pause(); @@ -449,9 +452,9 @@ export class CollectionStackedTimeline extends CollectionSubView { if (anchorDoc.isLinkButton) { - LinkFollower.FollowLink(undefined, anchorDoc, this.props, false); + LinkFollower.FollowLink(undefined, anchorDoc, false); } - const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; + const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) { if (this.props.playing()) this.props.Pause(); @@ -598,10 +601,7 @@ export class CollectionStackedTimeline extends CollectionSubView { - this.props.playFrom(start, this.anchorEnd(d.anchor)); - e.stopPropagation(); + pointerEvents: 'none', }}> time < NumCast(this.props.mark[this.props.endTag]) && this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false); + LinkFollower.FollowLink(undefined, this.props.mark, false); } this._lastTimecode = time; } @@ -765,7 +765,9 @@ class StackedTimelineAnchor extends React.Component this._disposer?.(); } + @observable noEvents = false; // starting the drag event for anchor resizing + @action onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { //this.props._timeline?.setPointerCapture(e.pointerId); const newTime = (e: PointerEvent) => { @@ -783,8 +785,8 @@ class StackedTimelineAnchor extends React.Component } return false; }; + this.noEvents = true; var undo: UndoManager.Batch | undefined; - setupMoveUpEvents( this, e, @@ -793,11 +795,11 @@ class StackedTimelineAnchor extends React.Component this.props.setTime(newTime(e)); return changeAnchor(anchor, left, newTime(e)); }, - e => { + action(e => { this.props.setTime(newTime(e)); - // this.props._timeline?.releasePointerCapture(e.pointerId); undo?.end(); - }, + this.noEvents = false; + }), emptyFunction ); }; @@ -828,6 +830,7 @@ class StackedTimelineAnchor extends React.Component ref={action((r: DocumentView | null) => (anchor.view = r))} Document={mark} DataDoc={undefined} + pointerEvents={this.noEvents ? returnNone : undefined} styleProvider={this.props.styleProvider} renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} @@ -858,15 +861,15 @@ class StackedTimelineAnchor extends React.Component render() { const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height); return ( - <> +
{inner.view} {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : ( <> -
this.onAnchorDown(e, this.props.mark, true)} /> -
this.onAnchorDown(e, this.props.mark, false)} /> +
this.onAnchorDown(e, this.props.mark, true)} /> +
this.onAnchorDown(e, this.props.mark, false)} /> )} - +
); } } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 4741fc6f2..29e7cd3ad 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -140,7 +140,7 @@ export class LinkMenuItem extends React.Component { : undefined; if (focusDoc) this.props.docView.props.focus(focusDoc, { instant: true }); - LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false); + LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, false); } } ); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b7a760c1e..805e58cd0 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -655,7 +655,7 @@ export class DocumentViewInternal extends DocComponent 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part @@ -1136,16 +1136,13 @@ export class DocumentViewInternal extends DocComponent {!this._retryThumb || !this.thumbShown() ? null : ( diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 1b37dc7ab..e12548f18 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -39,7 +39,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { this.onPointerMove, emptyFunction, (e, doubleTap) => { - if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, this.props, false); + if (doubleTap) LinkFollower.FollowLink(this.rootDoc, anchorContainerDoc, false); else this.props.select(false); }, false diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 16b352e4f..fcc5b6975 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -171,7 +171,7 @@ export class LinkDocPreview extends React.Component { followLink = () => { LinkDocPreview.Clear(); if (this._linkDoc && this._linkSrc) { - LinkFollower.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false); + LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false); } else if (this.props.hrefs?.length) { const webDoc = Array.from(SearchBox.staticSearchCollection(Doc.MyFilesystem, this.props.hrefs[0]).keys()).lastElement() ?? diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 3b101a0c6..d1f3397f5 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -82,7 +82,7 @@ class RegionAnnotation extends React.Component { e.stopPropagation(); } else if (e.button === 0) { e.stopPropagation(); - LinkFollower.FollowLink(undefined, this.annoTextRegion, this.props, false); + LinkFollower.FollowLink(undefined, this.annoTextRegion,false); } }; -- cgit v1.2.3-70-g09d2 From 081f328c117ffdf7ab284be86cdf0342041e7708 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Mar 2023 15:32:06 -0400 Subject: cleaned up pointer events so that nested documents can be selected directly without selecting their container. fixed following link to video timeline marker. fixed focusing on groups. added didMove to DocFocusOptions to restore ability to do toggle on/off of target. fixed lockingPosition of ink strokes. fixed clicking on inkstrokes in groups to use visiblePainted instead of all for pointer events. --- src/client/documents/Documents.ts | 3 ++- src/client/util/DocumentManager.ts | 3 ++- src/client/views/DocComponent.tsx | 5 +++++ src/client/views/InkingStroke.tsx | 2 +- src/client/views/PropertiesView.tsx | 10 ++++----- src/client/views/StyleProvider.tsx | 25 +++++++++++----------- .../collections/CollectionStackedTimeline.tsx | 5 +---- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 ++++++++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 - src/client/views/nodes/DocumentContentsView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 22 +++++++++---------- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 6 +++--- src/fields/Doc.ts | 8 +++---- 14 files changed, 62 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ff6c8d440..3e89c8347 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -584,7 +584,7 @@ export namespace Docs { [ DocumentType.FONTICON, { - layout: { view: FontIconBox, dataField: defaultDataKey }, + layout: { view: FontIconBox, dataField: 'icon' }, options: { allowClickBeforeDoubleClick: true, hideLinkButton: true, _width: 40, _height: 40 }, }, ], @@ -1686,6 +1686,7 @@ export namespace DocUtils { x: Cast(doc.x, 'number', null), y: Cast(doc.y, 'number', null), backgroundColor: '#ACCEF7', + hideAllLinks: true, _width: 15, _height: 15, _xPadding: 0, diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index f2c554866..947613801 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -265,6 +265,7 @@ export class DocumentManager { let rootContextView = await new Promise(res => { const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc)); if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!); + options.didMove = true; docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation as OpenWhere); this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); }); @@ -299,7 +300,7 @@ export class DocumentManager { PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500); Doc.linkFollowHighlight(docView.rootDoc, undefined, options.effect); if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc); - if (options.toggleTarget) docView.rootDoc.hidden = !docView.rootDoc.hidden; + if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden; if (options.effect) docView.rootDoc[AnimationSym] = options.effect; if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.textHtml) { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 7c81d92d4..0b92fd864 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -16,6 +16,7 @@ import { Touchable } from './Touchable'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) export interface DocComponentProps { Document: Doc; + fieldKey?: string; LayoutTemplate?: () => Opt; LayoutTemplateString?: string; } @@ -37,6 +38,10 @@ export function DocComponent

() { @computed get dataDoc() { return this.props.Document[DataSym] as Doc; } + // key where data is stored + @computed get fieldKey() { + return this.props.fieldKey; + } protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 4f08a8e22..3861331b5 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -418,7 +418,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { inkScaleX, inkScaleY, '', - this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? 'none' : 'visiblepainted'), + this.props.pointerEvents?.() ?? 'visiblepainted', 0.0, false, downHdlr, diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 03b4100a7..f3a5a5393 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1678,8 +1678,8 @@ export class PropertiesView extends React.Component {

Center Target (no zoom)

Zoom %

-
+
this.setZoom(String(zoom), 0.1))}> @@ -1706,11 +1706,11 @@ export class PropertiesView extends React.Component {
- {!targZoom || this.sourceAnchor?.followLinkZoomScale === 0 ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)} + {!targZoom ? null : PresBox.inputter('0', '1', '100', zoom, true, this.setZoom, 30)}
, props: Opt doc && BoolCast(doc._lockedPosition); + const lockedPosition = () => doc && BoolCast(doc._lockedPosition); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); const showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle); @@ -268,7 +268,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt 0 ? ( + return doc && doc.pointerEvents === 'none' && lockedPosition() && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 ? (
toggleLockedPosition(doc)}> - +
) : null; } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index d4e83f609..4941bc722 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -817,10 +817,7 @@ class StackedTimelineAnchor extends React.Component // renders anchor LabelBox renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as any }); - const focusFunc = (doc: Doc, options: DocFocusOptions) => { - this.props.playLink(mark); - this.props.focus(doc, options); - }; + const focusFunc = (doc: Doc, options: DocFocusOptions) => this.props.playLink(mark); return { anchor, view: ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f0c140ef1..ac90c67a5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -293,6 +293,13 @@ export class CollectionFreeFormView extends CollectionSubView= -1e-4 && curTime <= endTime); } + groupFocus = (anchor: Doc, options: DocFocusOptions) => { + options.docTransform = new Transform(-NumCast(this.rootDoc.panX) + NumCast(anchor.x), -NumCast(this.rootDoc.panY) + NumCast(anchor.y), 1); + const res = this.props.focus(this.rootDoc, options); + options.docTransform = undefined; + return res; + }; + focus = (anchor: Doc, options: DocFocusOptions) => { const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; @@ -301,6 +308,7 @@ export class CollectionFreeFormView extends CollectionSubView SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); if (!Doc.noviceMode) { moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: 'concierge-bell' }); moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => (this.Document._chromeHidden = !this.Document._chromeHidden), icon: 'project-diagram' }); @@ -1003,6 +1003,8 @@ export class DocumentViewInternal extends DocComponent Utils.CopyText(Doc.globalServerPath(this.props.Document)), icon: 'fingerprint' }); } moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); + + (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); } if (this.props.removeDocument && !Doc.IsSystem(this.rootDoc) && Doc.ActiveDashboard !== this.props.Document) { @@ -1130,6 +1132,7 @@ export class DocumentViewInternal extends DocComponent ); } + pointerEventsFunc = () => this.pointerEvents; @computed get contents() { TraceMobx(); return ( @@ -1137,12 +1140,9 @@ export class DocumentViewInternal extends DocComponent {!this._retryThumb || !this.thumbShown() ? null : ( @@ -1163,7 +1163,7 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints} @@ -1501,7 +1501,7 @@ export class DocumentViewInternal extends DocComponent {!borderPath.path ? ( animRenderDoc diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index b3a3c3ae4..339887757 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -101,7 +101,7 @@ export class FontIconBox extends DocComponent() { return StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); } Icon = (color: string) => { - const icon = StrCast(this.dataDoc.icon, 'user') as any; + const icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; const trailsIcon = () => ; return !icon ? null : icon === 'pres-trail' ? trailsIcon() : ; }; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index d1f3397f5..db6b1f011 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,5 +1,5 @@ import React = require('react'); -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -7,10 +7,10 @@ import { List } from '../../../fields/List'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { LinkFollower } from '../../util/LinkFollower'; import { undoBatch } from '../../util/UndoManager'; +import { OpenWhere } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { AnchorMenu } from './AnchorMenu'; import './Annotation.scss'; -import { OpenWhere } from '../nodes/DocumentView'; interface IAnnotationProps extends FieldViewProps { anno: Doc; @@ -82,7 +82,7 @@ class RegionAnnotation extends React.Component { e.stopPropagation(); } else if (e.button === 0) { e.stopPropagation(); - LinkFollower.FollowLink(undefined, this.annoTextRegion,false); + LinkFollower.FollowLink(undefined, this.annoTextRegion, false); } }; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 44314dca2..40ef67f92 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -891,10 +891,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). -- cgit v1.2.3-70-g09d2 From b25f333713706a7456ab418960082bcabe830f11 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Mar 2023 20:48:38 -0400 Subject: fixed copying of docs with template layout docs. fixed clone to search through RTFs for referenced documents to clone. renamed nested documents docId instead of docid for consistency with other nested field ids. --- src/client/util/CurrentUserUtils.ts | 6 +-- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/SidebarAnnos.tsx | 4 +- src/client/views/collections/CollectionSubView.tsx | 10 ++--- src/client/views/nodes/DocumentView.tsx | 6 +-- .../nodes/formattedText/DashDocCommentView.tsx | 16 ++++---- .../views/nodes/formattedText/DashDocView.tsx | 16 ++++---- .../views/nodes/formattedText/DashFieldView.tsx | 8 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 ++-- .../views/nodes/formattedText/RichTextRules.ts | 26 ++++++------ src/client/views/nodes/formattedText/nodes_rts.ts | 8 ++-- src/fields/Doc.ts | 46 ++++++++++++++-------- src/fields/RichTextField.ts | 26 ++++++------ src/fields/RichTextUtils.ts | 10 ++--- 14 files changed, 103 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2820c66ee..c038fd6ab 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -148,7 +148,7 @@ export class CurrentUserUtils { { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" }, { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; const reqdNoteList = reqdTempOpts.map(opts => { - const reqdOpts = {...opts, title: "text", width:200, autoHeight: true, fitWidth: true, system: true}; + const reqdOpts = {...opts, title: "text", width:200, autoHeight: true, fitWidth: true}; const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined; return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note"); }); @@ -224,11 +224,11 @@ export class CurrentUserUtils { { type: "paragraph", attrs: {}, content: [{ type: "dashField", - attrs: { fieldKey: "author", docid: "", hideKey: false }, + attrs: { fieldKey: "author", docId: "", hideKey: false }, marks: [{ type: "strong" }] }, { type: "dashField", - attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, + attrs: { fieldKey: "creationDate", docId: "", hideKey: false }, marks: [{ type: "strong" }] }] }] diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index f849b21e3..0f8b46dbe 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -372,7 +372,7 @@ export class KeyManager { list.push(doc); } if (count === docids.length) { - const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d, ['links'])).clone : d))); + const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d, true)).clone : d))); if (added.length) { added.map(doc => (doc.context = targetDataDoc)); undoBatch(() => { diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 7519cbb05..02dd15960 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -84,7 +84,7 @@ export class SidebarAnnos extends React.Component { Doc.GetProto(target)[key] = val; return { type: 'dashField', - attrs: { fieldKey: key, docid: '', hideKey: false, editable: true }, + attrs: { fieldKey: key, docId: '', hideKey: false, editable: true }, marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }], }; }); @@ -111,7 +111,7 @@ export class SidebarAnnos extends React.Component { { type: 'pFontSize', attrs: { fontSize: '8px' } }, { type: 'em' }, ], - attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false }, + attrs: { fieldKey: 'text', docId: anchor[Id], hideKey: true, editable: false }, }, ], }, diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e46220f02..bd74c9399 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -269,10 +269,10 @@ export function CollectionSubView(moreProps?: X) { if (FormattedTextBox.IsFragment(html)) { const href = FormattedTextBox.GetHref(html); if (href) { - const docid = FormattedTextBox.GetDocFromUrl(href); - if (docid) { + const docId = FormattedTextBox.GetDocFromUrl(href); + if (docId) { // prosemirror text containing link to dash document - DocServer.GetRefField(docid).then(f => { + DocServer.GetRefField(docId).then(f => { if (f instanceof Doc) { if (options.x || options.y) { f.x = options.x as number; @@ -311,8 +311,8 @@ export function CollectionSubView(moreProps?: X) { } else { const path = window.location.origin + '/doc/'; if (text.startsWith(path)) { - const docid = text.replace(Doc.globalServerPath(), '').split('?')[0]; - DocServer.GetRefField(docid).then(f => { + const docId = text.replace(Doc.globalServerPath(), '').split('?')[0]; + DocServer.GetRefField(docId).then(f => { if (f instanceof Doc) { if (options.x || options.y) { f.x = options.x as number; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 36e8facf5..edf508c95 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1578,8 +1578,8 @@ export class DocumentView extends React.Component { } public static showBackLinks(linkSource: Doc) { - const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + '-pivotish'; - DocServer.GetRefField(docid).then(docx => { + const docId = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + '-pivotish'; + DocServer.GetRefField(docId).then(docx => { const rootAlias = () => { const rootAlias = Doc.MakeAlias(linkSource); rootAlias.x = rootAlias.y = 0; @@ -1592,7 +1592,7 @@ export class DocumentView extends React.Component { /*rootAlias()*/ ], { title: linkSource.title + '-pivot', _width: 500, _height: 500 }, - docid + docId ); linkCollection.linkSource = linkSource; if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript('updateLinkCollection(self)'); diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index fcd6e0c55..aa269d8d6 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -32,7 +32,7 @@ export class DashDocCommentView { }; this.root = ReactDOM.createRoot(this.dom); - this.root.render(); + this.root.render(); (this as any).dom = this.dom; } @@ -48,7 +48,7 @@ export class DashDocCommentView { } interface IDashDocCommentViewInternal { - docid: string; + docId: string; view: any; getPos: any; } @@ -63,13 +63,13 @@ export class DashDocCommentViewInternal extends React.Component dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); + DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight()); e.preventDefault(); e.stopPropagation(); } onPointerEnterCollapsed(e: any) { - DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); + DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false)); e.preventDefault(); e.stopPropagation(); } @@ -82,7 +82,7 @@ export class DashDocCommentViewInternal extends React.Component { - expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); + 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) {} @@ -100,12 +100,12 @@ export class DashDocCommentViewInternal extends React.Component { try { @@ -119,7 +119,7 @@ export class DashDocCommentViewInternal extends React.Component
-
- DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())} checked={DragManager.GetRaiseWhenDragged()} /> -
Raise on drag
-
FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} />
Show button labels
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4805a748b..33d468950 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -201,6 +201,7 @@ export class CollectionStackingView extends CollectionSubView this.props.isAnyChildContentActive(); + @action moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; @@ -651,8 +654,8 @@ export class CollectionStackingView extends CollectionSubView (this._scroll = e.currentTarget.scrollTop))} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} - onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}> + onWheel={e => this.isContentActive() && e.stopPropagation()}> {this.renderedSections} {!this.showAddAGroup ? null : (
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0ea614472..840eede81 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -120,7 +120,6 @@ export class CollectionFreeFormView extends CollectionSubView - this.onClickHandler.script.run( + this.onClickHandler?.script.run( { this: this.layoutDoc, self: this.rootDoc, @@ -1065,7 +1066,7 @@ export class DocumentViewInternal extends DocComponent this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; panelHeight = () => this.props.PanelHeight() - this.headerMargin; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); - onClickFunc = () => this.onClickHandler; + onClickFunc = () => this.onClickHandler as any as ScriptField; // bcz: typing HACK. check and fix. setHeight = (height: number) => (this.layoutDoc._height = height); setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); isContentActive = (outsideReaction?: boolean) => { @@ -1116,17 +1117,14 @@ export class DocumentViewInternal extends DocComponent ); } - pointerEventsFunc = () => this.pointerEvents; + contentPointerEvents = () => (this.onClickHandler ? 'none' : this.pointerEvents); @computed get contents() { TraceMobx(); return (
{!this._retryThumb || !this.thumbShown() ? null : ( @@ -1147,7 +1145,7 @@ export class DocumentViewInternal extends DocComponent this.props.isContentActive() && e.stopPropagation()} + ref={r => + r?.addEventListener( + 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) + (e: WheelEvent) => { + if (this.isContentActive()) { + if (!NumCast(this.layoutDoc._scrollTop) && e.deltaY <= 0) e.preventDefault(); + e.stopPropagation(); + } + }, + { passive: false } + ) + } style={{ ...(this.props.dontScale ? {} -- cgit v1.2.3-70-g09d2 From d5f60e34e8ac9f99c0b442346db5386068af5de2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Mar 2023 10:06:39 -0400 Subject: changed showing keyvalue panes to not create a document, but to use the LayoutTemplateString. --- src/client/documents/Documents.ts | 8 ++++---- src/client/util/DictationManager.ts | 10 ---------- src/client/views/StyleProvider.tsx | 5 ++--- src/client/views/animationtimeline/Keyframe.tsx | 8 -------- .../views/collections/CollectionDockingView.tsx | 20 ++++++++++---------- src/client/views/collections/TabDocView.tsx | 12 ++++++++---- src/client/views/nodes/DocumentView.tsx | 6 ++++-- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/KeyValuePair.tsx | 2 +- 9 files changed, 30 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3e89c8347..cb429d436 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -762,7 +762,6 @@ export namespace Docs { ...(template.options || {}), layout: layout.view?.LayoutString(layout.dataField), data: template.data, - layout_keyValue: KeyValueBox.LayoutString(''), }; Object.entries(options).map(pair => { if (typeof pair[1] === 'string' && pair[1].startsWith('@')) { @@ -1021,9 +1020,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), { lat, lng, infoWindowOpen, ...options }, id); } - export function KVPDocument(document: Doc, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options }); - } + // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView) + // export function KVPDocument(document: Doc, options: DocumentOptions = {}) { + // return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options }); + // } export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id); diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 203d4ad62..e116ae14b 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -323,16 +323,6 @@ export namespace DictationManager { }, ], - [ - 'open fields', - { - action: (target: DocumentView) => { - const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 }); - target.props.addDocTab(kvp, OpenWhere.addRight); - }, - }, - ], - [ 'new outline', { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 096396a41..d1e85a65b 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -21,6 +21,7 @@ import { FieldViewProps } from './nodes/FieldView'; import { SliderBox } from './nodes/SliderBox'; import './StyleProvider.scss'; import React = require('react'); +import { KeyValueBox } from './nodes/KeyValueBox'; export enum StyleProp { TreeViewIcon = 'treeViewIcon', @@ -131,10 +132,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { TimelineMenu.Instance.addItem('button', 'Toggle Fade Only', () => { kf.type = kf.type === KeyframeFunc.KeyframeType.fade ? KeyframeFunc.KeyframeType.default : KeyframeFunc.KeyframeType.fade; }), - TimelineMenu.Instance.addItem( - 'button', - 'Show Data', - action(() => { - const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 }); - CollectionDockingView.AddSplit(kvp, OpenWhereMod.right); - }) - ), TimelineMenu.Instance.addItem( 'button', 'Delete', diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 057c1e30f..e38905cbc 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -28,13 +28,12 @@ import React = require('react'); import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; import { OverlayView } from '../OverlayView'; import { ScriptingRepl } from '../ScriptingRepl'; -import { ScriptField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; @observer export class CollectionDockingView extends CollectionSubView() { @observable public static Instance: CollectionDockingView | undefined; - public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) { + public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) { return { type: 'react-component', component: 'DocumentFrameRenderer', @@ -42,6 +41,7 @@ export class CollectionDockingView extends CollectionSubView() { width: width, props: { documentId: document[Id], + keyValue, panelName, // name of tab that can be used to close or replace its contents }, }; @@ -146,10 +146,10 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action - public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean): boolean { + public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean, keyValue?: boolean): boolean { const instance = CollectionDockingView.Instance; if (!instance) return false; - const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName); + const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue); if (!panelName && stack) { const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config); const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout); @@ -171,10 +171,10 @@ export class CollectionDockingView extends CollectionSubView() { } @undoBatch - public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string) { + public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1 ? CollectionDockingView.CloseSplit(doc) - : CollectionDockingView.AddSplit(doc, location, stack, panelName); + : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue); } // @@ -182,7 +182,7 @@ export class CollectionDockingView extends CollectionSubView() { // @undoBatch @action - public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string) { + public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document); if (!CollectionDockingView.Instance) return false; const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); @@ -193,7 +193,7 @@ export class CollectionDockingView extends CollectionSubView() { const instance = CollectionDockingView.Instance; const glayRoot = instance._goldenLayout.root; if (!instance) return false; - const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName); + const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue); if (!pullSide && stack) { stack.addChild(docContentConfig, undefined); @@ -601,6 +601,6 @@ ScriptingGlobals.add( 'opens up document in screen overlay layer', '(doc: any)' ); -ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { - CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, shiftKey); +ScriptingGlobals.add(function useRightSplit(doc: any, addToRightSplit?: boolean) { + CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, addToRightSplit); }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 0ab94e2e3..631b9add5 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -35,10 +35,12 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import React = require('react'); +import { KeyValueBox } from '../nodes/KeyValueBox'; const _global = (window /* browser */ || global) /* node */ as any; interface TabDocViewProps { documentId: FieldId; + keyValue?: boolean; glContainer: any; } @observer @@ -348,7 +350,8 @@ export class TabDocView extends React.Component { addDocTab = (doc: Doc, location: OpenWhere) => { SelectionManager.DeselectAll(); const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':'); - const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; + const keyValue = whereFields[1]?.includes('KeyValue'); + const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none; if (doc.dockingConfig) return DashboardView.openDashboard(doc); // prettier-ignore switch (whereFields[0]) { @@ -365,9 +368,9 @@ export class TabDocView extends React.Component { case OpenWhere.dashboard: return DashboardView.openDashboard(doc); case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc); case OpenWhere.close: return CollectionDockingView.CloseSplit(doc, whereMods); - case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack); - case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack); - case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack); + case OpenWhere.replace: return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue); + case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, undefined, keyValue); + case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue); } }; remDocTab = (doc: Doc | Doc[]) => { @@ -419,6 +422,7 @@ export class TabDocView extends React.Component { this._lastView = this._view; })} renderDepth={0} + LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString('data') : undefined} Document={this._document} DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} ContainingCollectionView={undefined} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 796d2b3ca..9e4c21a29 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -54,6 +54,7 @@ import { ScriptingBox } from './ScriptingBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); +import { KeyValueBox } from './KeyValueBox'; const { Howl } = require('howler'); interface Window { @@ -88,6 +89,7 @@ export enum OpenWhereMod { right = 'right', top = 'top', bottom = 'bottom', + rightKeyValue = 'rightKeyValue', } export interface DocFocusOptions { @@ -1000,7 +1002,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' }); + helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' }); !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' }); !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' }); @@ -1836,7 +1838,7 @@ export class DocumentView extends React.Component { transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, height: - isButton || this.props.forceAutoHeight + isButton || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.props.forceAutoHeight ? undefined : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`), }}> diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 60417430f..9f9d93a82 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -45,7 +45,7 @@ export class KeyValueBox extends React.Component { return NumCast(this.props.Document.schemaSplitPercentage, 50); } get fieldDocToLayout() { - return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document; + return this.props.fieldKey ? DocCast(this.props.Document[this.props.fieldKey], DocCast(this.props.Document)) : this.props.Document; } @action diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index e74ef4a39..c4adc7f1a 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -48,7 +48,7 @@ export class KeyValuePair extends React.Component { if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' }); + ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, ((OpenWhere.addRight as string) + 'KeyValue') as OpenWhere), icon: 'layer-group' }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } }; -- cgit v1.2.3-70-g09d2 From 923f0fdb0f039a923e4e6f870158bd2f2ba32db0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Mar 2023 10:57:47 -0400 Subject: fixed nested collections to not grab pointerwheel events if not active --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 840eede81..aea7a3ad9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1075,7 +1075,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.Document._isGroup) return; // group style collections neither pan nor zoom + if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom PresBox.Instance?.pauseAutoPres(); if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; e.stopPropagation(); -- cgit v1.2.3-70-g09d2