From 810f86195188503b04d64f9d58ea4dfc3a639398 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 22 Mar 2022 11:32:47 -0400 Subject: fixed temporal media merge that had reverted a lot of things. --- src/client/util/CurrentUserUtils.ts | 248 ++++++--------- src/client/util/DictationManager.ts | 353 +++++++++++---------- src/client/util/DocumentManager.ts | 8 +- src/client/util/DragManager.ts | 2 +- src/client/util/DropConverter.ts | 4 +- src/client/util/History.ts | 6 +- .../util/Import & Export/DirectoryImportBox.tsx | 7 +- src/client/util/LinkManager.ts | 2 +- src/client/util/ScriptManager.ts | 12 +- src/client/util/Scripting.ts | 109 +------ src/client/util/ScriptingGlobals.ts | 81 +++++ src/client/util/UndoManager.ts | 3 +- 12 files changed, 384 insertions(+), 451 deletions(-) create mode 100644 src/client/util/ScriptingGlobals.ts (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a8b0da369..c0e3f66eb 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -7,7 +7,6 @@ import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; -import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { nullAudio } from "../../fields/URLField"; @@ -16,9 +15,7 @@ import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; -import { Networking } from "../Network"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; @@ -32,7 +29,7 @@ import { DragManager } from "./DragManager"; import { makeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; -import { Scripting } from "./Scripting"; +import { ScriptingGlobals } from "./ScriptingGlobals"; import { SearchUtil } from "./SearchUtil"; import { SelectionManager } from "./SelectionManager"; import { ColorScheme } from "./SettingsManager"; @@ -51,7 +48,6 @@ interface Button { numBtnMax?: number; switchToggle?: boolean; script?: string; - checkResult?: string; width?: number; list?: string[]; ignoreClick?: boolean; @@ -162,95 +158,41 @@ export class CurrentUserUtils { }); } - if (doc["template-button-switch"] === undefined) { - const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; - - const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true }); - const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true }); - const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true }); - const labelTemplate = { - doc: { - type: "doc", content: [{ - type: "paragraph", - content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }] - }] - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS"); - Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'"); - // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'"); - // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true"); - Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]"); - // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false"); - const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true }); - box.isTemplateDoc = makeTemplate(box, true, "switch"); - - doc["template-button-switch"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true, - btnType: ButtonType.ToolButton - }); - } - - if (doc["template-button-detail"] === undefined) { - const { TextDocument, MasonryDocument, CarouselDocument } = Docs.Create; - - const openInTarget = ScriptField.MakeScript("openOnRight(self.doubleClickView)"); - const carousel = CarouselDocument([], { - title: "data", _height: 350, _itemIndex: 0, "_carousel-caption-xMargin": 10, "_carousel-caption-yMargin": 10, - onChildDoubleClick: openInTarget, backgroundColor: "#9b9b9b3F", system: true - }); - - const details = TextDocument("", { title: "details", _height: 200, _autoHeight: true, system: true }); - const short = TextDocument("", { title: "shortDescription", treeViewOpen: true, treeViewExpandedView: "layout", _height: 75, _autoHeight: true, system: true }); - const long = TextDocument("", { title: "longDescription", treeViewOpen: false, treeViewExpandedView: "layout", _height: 150, _autoHeight: true, system: true }); - - const buxtonFieldKeys = ["year", "originalPrice", "degreesOfFreedom", "company", "attribute", "primaryKey", "secondaryKey", "dimensions"]; - const detailedTemplate = { - doc: { - type: "doc", content: buxtonFieldKeys.map(fieldKey => ({ - type: "paragraph", - content: [{ type: "dashField", attrs: { fieldKey } }] - })) - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" ")); - - const shared = { _autoHeight: true, _xMargin: 0 }; - const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" }; - const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title", system: true }; - - const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); - descriptionWrapper._columnHeaders = new List([ - new SchemaHeaderField("[A Short Description]", "dimgray", undefined, undefined, undefined, false), - new SchemaHeaderField("[Long Description]", "dimgray", undefined, undefined, undefined, true), - new SchemaHeaderField("[Details]", "dimgray", undefined, undefined, undefined, true), - ]); - const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, _chromeHidden: true, system: true }); - detailView.isTemplateDoc = makeTemplate(detailView); - - details.title = "Details"; - short.title = "A Short Description"; - long.title = "Long Description"; - - doc["template-button-detail"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(detailView) as any as Doc, - title: "detailView", - icon: "window-maximize", - system: true, - btnType: ButtonType.ToolButton, - }); - } + // if (doc["template-button-switch"] === undefined) { + // const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; + + // const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true }); + // const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true }); + // const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true }); + // const labelTemplate = { + // doc: { + // type: "doc", content: [{ + // type: "paragraph", + // content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }] + // }] + // }, + // selection: { type: "text", anchor: 1, head: 1 }, + // storedMarks: [] + // }; + // Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS"); + // Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'"); + // // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'"); + // // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true"); + // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]"); + // // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false"); + // const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true }); + // box.isTemplateDoc = makeTemplate(box, true, "switch"); + + // doc["template-button-switch"] = CurrentUserUtils.createToolButton({ + // onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + // dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true, + // btnType: ButtonType.ToolButton + // }); + // } const requiredTypes = [ doc["template-button-slides"] as Doc, doc["template-mobile-button"] as Doc, - doc["template-button-detail"] as Doc, doc["template-button-link"] as Doc, //doc["template-button-switch"] as Doc] ]; @@ -538,11 +480,11 @@ export class CurrentUserUtils { // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static async setupCreatorButtons(doc: Doc) { let alreadyCreatedButtons: string[] = []; - const dragCreatorSet = await Cast(doc.myItemCreators, Doc, null); + const dragCreatorSet = Cast(doc.myItemCreators, Doc, null); if (dragCreatorSet) { - const dragCreators = await Cast(dragCreatorSet.data, listSpec(Doc)); + const dragCreators = Cast(dragCreatorSet.data, listSpec(Doc)); if (dragCreators) { - const dragDocs = await Promise.all(dragCreators); + const dragDocs = await Promise.all(Array.from(dragCreators)); alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title)); } } @@ -735,12 +677,16 @@ export class CurrentUserUtils { ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, - _dropAction: data.pointerDown ? "copy" : undefined, ignoreClick: data.ignoreClick, + _dropAction: data.pointerDown ? "copy" : undefined, + ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, clipboard: data.clipboard, - onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, + onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, + onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, backgroundColor: data.backgroundColor, - _removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, system: true + _removeDropProperties: new List(["dropAction"]), + dragFactory: data.dragFactory, + system: true })); } @@ -987,37 +933,37 @@ export class CurrentUserUtils { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], - script: 'setFont' + script: 'setFont(value, _readOnly_)' }, - { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize' }, - { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor' }, - { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold()', checkResult: 'toggleBold(true)' }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic()', checkResult: 'toggleItalic(true)' }, - { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline()', checkResult: 'toggleUnderline(true)' }, - { title: "Bullet List", toolTip: "Bullet", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet")', checkResult: 'setBulletList("bullet", true)' }, - { title: "Number List", toolTip: "Number", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal")', checkResult: 'setBulletList("decimal", true)' }, + { title: "Font size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' }, + { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor(value, _readOnly_)' }, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold(_readOnly_)' }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic(_readOnly_)' }, + { title: "Underline", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' }, + { title: "Bullet List", toolTip: "Bullet", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' }, + { title: "Number List", toolTip: "Number", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal", _readOnly_)' }, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'}, - { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left")', checkResult: 'setAlignment("left", true)' }, - { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center")', checkResult: 'setAlignment("center", true)' }, - { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right")', checkResult: 'setAlignment("right", true)' }, + { title: "Left align", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' }, + { title: "Center align", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' }, + { title: "Right align", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' }, ]; return tools; } static inkTools(doc: Doc) { const tools: Button[] = [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen")', checkResult: 'setActiveInkTool("pen" , true)' }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser")', checkResult: 'setActiveInkTool("eraser" , true)' }, - // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")', checkResult: 'setActiveInkTool("highlighter", true)' }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle")', checkResult: 'setActiveInkTool("circle" , true)' }, - // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")', checkResult: 'setActiveInkTool("square" , true)' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line")', checkResult: 'setActiveInkTool("line" , true)' }, - { title: "Fill color", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor" }, - { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth' }, - { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor' }, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen", _readOnly_)' }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser", _readOnly_)' }, + // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle", _readOnly_)' }, + // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line", _readOnly_)' }, + { title: "Fill color", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" }, + { title: "Stroke width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' }, + { title: "Stroke color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' }, ]; return tools; } @@ -1031,8 +977,7 @@ export class CurrentUserUtils { btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", - click: 'toggleSchemaPreview()', - checkResult: 'toggleSchemaPreview(true)' + click: 'toggleSchemaPreview(_readOnly_)', }, ]; return tools; @@ -1041,10 +986,10 @@ export class CurrentUserUtils { static webTools(doc: Doc) { const tools: Button[] = [ - { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack()' }, - { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward()' }, + { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack(_readOnly_)' }, + { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: '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, script: 'webSetURL' }, + { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL(value, _readOnly_)' }, ]; return tools; @@ -1059,17 +1004,17 @@ export class CurrentUserUtils { CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid], - script: 'setView', + script: 'setView(value, _readOnly_)', }, // Always show { title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", - script: "setBackgroundColor", hidden: 'selectedDocumentType()' + script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' }, // Only when a document is selected { title: "Header Color", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", - script: "setHeaderColor", hidden: 'selectedDocumentType()', + script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', }, // Only when a document is selected - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay()', checkResult: 'toggleOverlay(true)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay(_readOnly_)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected { title: "Text", type: "textTools", subMenu: true, expanded: 'selectedDocumentType("rtf")' }, // Always available { title: "Ink", type: "inkTools", subMenu: true, expanded: 'selectedDocumentType("ink")' }, // Always available @@ -1083,7 +1028,7 @@ export class CurrentUserUtils { if (doc.contextMenuBtns === undefined) { const docList: Doc[] = []; - (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded, checkResult }) => { + (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => { const menuDocList: Doc[] = []; if (subMenu) { // default is textTools @@ -1105,7 +1050,8 @@ export class CurrentUserUtils { tools = CurrentUserUtils.textTools(doc); break; } - tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle, checkResult }) => { + tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle }) => { + const computed = click ? ComputedField.MakeFunction(click) as any : "transparent"; menuDocList.push(Docs.Create.FontIconDocument({ _nativeWidth: width ? width : 25, _nativeHeight: 25, @@ -1116,7 +1062,7 @@ export class CurrentUserUtils { numBtnType, numBtnMin, numBtnMax, - script, + script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined, btnType: btnType, btnList: new List(list), ignoreClick: ignoreClick, @@ -1128,10 +1074,10 @@ export class CurrentUserUtils { title, switchToggle, color: Colors.WHITE, - backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent", + backgroundColor: computed, _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { doc: Doc.name }) : undefined + onClick: click ? ScriptField.MakeScript(click) : undefined })); }); docList.push(CurrentUserUtils.linearButtonList({ @@ -1141,7 +1087,7 @@ export class CurrentUserUtils { linearViewExpandable: true, icon: title, _height: 30, - backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent", + // backgroundColor: hidden ? ComputedField.MakeFunction(hidden, { }, { _readOnly_: true }) as any : "transparent", linearViewIsExpanded: expanded ? !(ComputedField.MakeFunction(expanded) as any) : undefined, hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined, }, menuDocList)); @@ -1153,7 +1099,7 @@ export class CurrentUserUtils { _height: 25, icon, toolTip, - script, + script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined, btnType, btnList: new List(list), ignoreClick, @@ -1164,11 +1110,11 @@ export class CurrentUserUtils { dontUndo: true, title, color: Colors.WHITE, - backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult) as any : "transparent", + // backgroundColor: checkResult ? ComputedField.MakeFunction(checkResult, {}, {_readOnly_:true}) as any : "transparent", _dropAction: "alias", hidden: hidden ? ComputedField.MakeFunction(hidden) as any : undefined, _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }) : undefined + onClick: click ? ScriptField.MakeScript(click, { scriptContext: "any" }, { _readOnly_: false }) : undefined })); } }); @@ -1383,7 +1329,7 @@ export class CurrentUserUtils { if (response) { const result: { id: string, email: string, cacheDocumentIds: string } = JSON.parse(response); Doc.CurrentUserEmail = result.email; - resolvedPorts = JSON.parse(await Networking.FetchFromServer("/resolvedPorts")); + resolvedPorts = JSON.parse(await (await fetch("/resolvedPorts")).text()); DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, result.email); result.cacheDocumentIds && (await DocServer.GetRefFields(result.cacheDocumentIds.split(";"))); return result; @@ -1566,7 +1512,7 @@ export class CurrentUserUtils { @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } } -Scripting.addGlobal(function openDragFactory(dragFactory: Doc) { +ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { const copy = Doc.copyDragFactory(dragFactory); if (copy) { CollectionDockingView.AddSplit(copy, "right"); @@ -1574,27 +1520,27 @@ Scripting.addGlobal(function openDragFactory(dragFactory: Doc) { view && SelectionManager.SelectView(view, false); } }); -Scripting.addGlobal(function MySharedDocs() { return Doc.SharingDoc(); }, +ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, "document containing all shared Docs"); -Scripting.addGlobal(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, +ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode"); -Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, +ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); -Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, +ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called"); -Scripting.addGlobal(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, +ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); -Scripting.addGlobal(function createNewFolder() { return MainView.Instance.createNewFolder(); }, +ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); -Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, +ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); -Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); }, +ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); -Scripting.addGlobal(function shareDashboard(dashboard: Doc) { +ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); -Scripting.addGlobal(async function removeDashboard(dashboard: Doc) { +ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); if (dashboards && dashboards.length > 1) { if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboards.find(doc => doc !== dashboard)!); @@ -1602,7 +1548,7 @@ Scripting.addGlobal(async function removeDashboard(dashboard: Doc) { } }, "Remove Dashboard from Dashboards"); -Scripting.addGlobal(async function addToDashboards(dashboard: Doc) { +ScriptingGlobals.add(async function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]); @@ -1626,7 +1572,7 @@ Scripting.addGlobal(async function addToDashboards(dashboard: Doc) { /** * Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard. */ -Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) { +ScriptingGlobals.add(function dynamicOffScreenDocs(dashboard: Doc) { if (dashboard[DataSym] instanceof Doc) { const allDocs = DocListCast(dashboard["data-all"]); const onScreenTab = DocListCast(dashboard.data)[0]; @@ -1638,7 +1584,7 @@ Scripting.addGlobal(function dynamicOffScreenDocs(dashboard: Doc) { } return []; }); -Scripting.addGlobal(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) { +ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) { let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined; if (selected && checkParent) { const parentDoc: Doc = Cast(selected.context, Doc, null); @@ -1649,11 +1595,11 @@ Scripting.addGlobal(function selectedDocumentType(docType?: DocumentType, colTyp else if (selected && !colType && !docType) return false; else return true; }); -Scripting.addGlobal(function makeTopLevelFolder() { +ScriptingGlobals.add(function makeTopLevelFolder() { const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined }; return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder); }); -Scripting.addGlobal(function toggleComicMode() { +ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }); \ No newline at end of file diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index a93b2f573..a6dcda4bc 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -1,4 +1,6 @@ import * as interpreter from "words-to-numbers"; +// @ts-ignore bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module? +import type { } from "@types/dom-speech-recognition"; import { Doc, Opt } from "../../fields/Doc"; import { List } from "../../fields/List"; import { RichTextField } from "../../fields/RichTextField"; @@ -13,6 +15,7 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { SelectionManager } from "./SelectionManager"; import { UndoManager } from "./UndoManager"; + /** * This namespace provides a singleton instance of a manager that * handles the listening and text-conversion of user speech. @@ -102,17 +105,17 @@ export namespace DictationManager { try { results = await (pendingListen = listenImpl(options)); pendingListen = undefined; - if (results) { - Utils.CopyText(results); - if (overlay) { - DictationOverlay.Instance.isListening = false; - const execute = options?.tryExecute; - DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results; - DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; - } - options?.tryExecute && await DictationManager.Commands.execute(results); - } - } catch (e) { + // if (results) { + // Utils.CopyText(results); + // if (overlay) { + // DictationOverlay.Instance.isListening = false; + // const execute = options?.tryExecute; + // DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results; + // DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; + // } + // options?.tryExecute && await DictationManager.Commands.execute(results); + // } + } catch (e: any) { console.log(e); if (overlay) { DictationOverlay.Instance.isListening = false; @@ -188,7 +191,7 @@ export namespace DictationManager { current && sessionResults.push(current); sessionResults.length && resolve(sessionResults.join(inter || interSession)); } else { - resolve(current); + resolve(current || ""); } current = undefined; sessionResults = []; @@ -222,168 +225,168 @@ export namespace DictationManager { } - export namespace Commands { - - export const dictationFadeDuration = 2000; - - export type IndependentAction = (target: DocumentView) => any | Promise; - export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; - - export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; - export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] }; - - export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value); - export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry); - - export const execute = async (phrase: string) => { - return UndoManager.RunInBatch(async () => { - const targets = SelectionManager.Views(); - if (!targets || !targets.length) { - return; - } - - phrase = phrase.toLowerCase(); - const entry = Independent.get(phrase); - - if (entry) { - let success = false; - const restrictTo = entry.restrictTo; - for (const target of targets) { - if (!restrictTo || validate(target, restrictTo)) { - await entry.action(target); - success = true; - } - } - return success; - } - - for (const entry of Dependent) { - const regex = entry.expression; - const matches = regex.exec(phrase); - regex.lastIndex = 0; - if (matches !== null) { - let success = false; - const restrictTo = entry.restrictTo; - for (const target of targets) { - if (!restrictTo || validate(target, restrictTo)) { - await entry.action(target, matches); - success = true; - } - } - return success; - } - } - - return false; - }, "Execute Command"); - }; - - const ConstructorMap = new Map([ - [DocumentType.COL, listSpec(Doc)], - [DocumentType.AUDIO, AudioField], - [DocumentType.IMG, ImageField], - [DocumentType.IMPORT, listSpec(Doc)], - [DocumentType.RTF, "string"] - ]); - - const tryCast = (view: DocumentView, type: DocumentType) => { - const ctor = ConstructorMap.get(type); - if (!ctor) { - return false; - } - return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; - }; - - const validate = (target: DocumentView, types: DocumentType[]) => { - for (const type of types) { - if (tryCast(target, type)) { - return true; - } - } - return false; - }; - - const interpretNumber = (number: string) => { - const initial = parseInt(number); - if (!isNaN(initial)) { - return initial; - } - const converted = interpreter.wordsToNumbers(number, { fuzzy: true }); - if (converted === null) { - return NaN; - } - return typeof converted === "string" ? parseInt(converted) : converted; - }; - - const Independent = new Map([ - - ["clear", { - action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(), - restrictTo: [DocumentType.COL] - }], - - ["open fields", { - action: (target: DocumentView) => { - const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 }); - target.props.addDocTab(kvp, "add:right"); - } - }], - - ["new outline", { - action: (target: DocumentView) => { - const newBox = Docs.Create.TextDocument("", { _width: 400, _height: 200, title: "My Outline", _autoHeight: true }); - const proto = newBox.proto!; - const prompt = "Press alt + r to start dictating here..."; - const head = 3; - const anchor = head + prompt.length; - const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`; - proto.data = new RichTextField(proseMirrorState); - proto.backgroundColor = "#eeffff"; - target.props.addDocTab(newBox, "add:right"); - } - }] - - ]); - - const Dependent = new Array( - - { - expression: /create (\w+) documents of type (image|nested collection)/g, - action: (target: DocumentView, matches: RegExpExecArray) => { - const count = interpretNumber(matches[1]); - const what = matches[2]; - const dataDoc = Doc.GetProto(target.props.Document); - const fieldKey = "data"; - if (isNaN(count)) { - return; - } - for (let i = 0; i < count; i++) { - let created: Doc | undefined; - switch (what) { - case "image": - created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); - break; - case "nested collection": - created = Docs.Create.FreeformDocument([], {}); - break; - } - created && Doc.AddDocToList(dataDoc, fieldKey, created); - } - }, - restrictTo: [DocumentType.COL] - }, - - { - expression: /view as (freeform|stacking|masonry|schema|tree)/g, - action: (target: DocumentView, matches: RegExpExecArray) => { - const mode = matches[1]; - mode && (target.props.Document._viewType = mode); - }, - restrictTo: [DocumentType.COL] - } - - ); - - } + // export namespace Commands { + + // export const dictationFadeDuration = 2000; + + // export type IndependentAction = (target: DocumentView) => any | Promise; + // export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] }; + + // export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise; + // export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] }; + + // export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value); + // export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry); + + // export const execute = async (phrase: string) => { + // return UndoManager.RunInBatch(async () => { + // const targets = SelectionManager.Views(); + // if (!targets || !targets.length) { + // return; + // } + + // phrase = phrase.toLowerCase(); + // const entry = Independent.get(phrase); + + // if (entry) { + // let success = false; + // const restrictTo = entry.restrictTo; + // for (const target of targets) { + // if (!restrictTo || validate(target, restrictTo)) { + // await entry.action(target); + // success = true; + // } + // } + // return success; + // } + + // for (const entry of Dependent) { + // const regex = entry.expression; + // const matches = regex.exec(phrase); + // regex.lastIndex = 0; + // if (matches !== null) { + // let success = false; + // const restrictTo = entry.restrictTo; + // for (const target of targets) { + // if (!restrictTo || validate(target, restrictTo)) { + // await entry.action(target, matches); + // success = true; + // } + // } + // return success; + // } + // } + + // return false; + // }, "Execute Command"); + // }; + + // const ConstructorMap = new Map([ + // [DocumentType.COL, listSpec(Doc)], + // [DocumentType.AUDIO, AudioField], + // [DocumentType.IMG, ImageField], + // [DocumentType.IMPORT, listSpec(Doc)], + // [DocumentType.RTF, "string"] + // ]); + + // const tryCast = (view: DocumentView, type: DocumentType) => { + // const ctor = ConstructorMap.get(type); + // if (!ctor) { + // return false; + // } + // return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined; + // }; + + // const validate = (target: DocumentView, types: DocumentType[]) => { + // for (const type of types) { + // if (tryCast(target, type)) { + // return true; + // } + // } + // return false; + // }; + + // const interpretNumber = (number: string) => { + // const initial = parseInt(number); + // if (!isNaN(initial)) { + // return initial; + // } + // const converted = interpreter.wordsToNumbers(number, { fuzzy: true }); + // if (converted === null) { + // return NaN; + // } + // return typeof converted === "string" ? parseInt(converted) : converted; + // }; + + // const Independent = new Map([ + + // ["clear", { + // action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(), + // restrictTo: [DocumentType.COL] + // }], + + // ["open fields", { + // action: (target: DocumentView) => { + // const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 }); + // target.props.addDocTab(kvp, "add:right"); + // } + // }], + + // ["new outline", { + // action: (target: DocumentView) => { + // const newBox = Docs.Create.TextDocument("", { _width: 400, _height: 200, title: "My Outline", _autoHeight: true }); + // const proto = newBox.proto!; + // const prompt = "Press alt + r to start dictating here..."; + // const head = 3; + // const anchor = head + prompt.length; + // const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`; + // proto.data = new RichTextField(proseMirrorState); + // proto.backgroundColor = "#eeffff"; + // target.props.addDocTab(newBox, "add:right"); + // } + // }] + + // ]); + + // const Dependent = new Array( + + // { + // expression: /create (\w+) documents of type (image|nested collection)/g, + // action: (target: DocumentView, matches: RegExpExecArray) => { + // const count = interpretNumber(matches[1]); + // const what = matches[2]; + // const dataDoc = Doc.GetProto(target.props.Document); + // const fieldKey = "data"; + // if (isNaN(count)) { + // return; + // } + // for (let i = 0; i < count; i++) { + // let created: Doc | undefined; + // switch (what) { + // case "image": + // created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"); + // break; + // case "nested collection": + // created = Docs.Create.FreeformDocument([], {}); + // break; + // } + // created && Doc.AddDocToList(dataDoc, fieldKey, created); + // } + // }, + // restrictTo: [DocumentType.COL] + // }, + + // { + // expression: /view as (freeform|stacking|masonry|schema|tree)/g, + // action: (target: DocumentView, matches: RegExpExecArray) => { + // const mode = matches[1]; + // mode && (target.props.Document._viewType = mode); + // }, + // restrictTo: [DocumentType.COL] + // } + + // ); + + // } } \ No newline at end of file diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 66b6a1e44..0a00ab6e0 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -9,7 +9,7 @@ import { CollectionView } from '../views/collections/CollectionView'; import { LightboxView } from '../views/LightboxView'; import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; -import { Scripting } from './Scripting'; +import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; export class DocumentManager { @@ -198,7 +198,7 @@ export class DocumentManager { originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(didFocus); - res(); + res(ViewAdjustment.doNothing); }) }); } else { @@ -227,7 +227,7 @@ export class DocumentManager { willZoom, afterFocus: (didFocus: boolean) => new Promise(res => { !noSelect && focusAndFinish(didFocus); - res(); + res(ViewAdjustment.doNothing); }) }); // focus on the target in the context } else if (delay > 1500) { @@ -252,7 +252,7 @@ export class DocumentManager { } } -Scripting.addGlobal(function DocFocusOrOpen(doc: any) { +ScriptingGlobals.add(function DocFocusOrOpen(doc: any) { const dv = DocumentManager.Instance.getDocumentView(doc); if (dv && dv.props.Document === doc) { dv.props.focus(doc, { willZoom: true }); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index ae3fa3170..c9c499fff 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -499,7 +499,7 @@ export namespace DragManager { screenX: e.screenX, screenY: e.screenY, detail: e.detail, - view: e.view ? e.view : new Window, + view: e.view ? e.view : new Window as any, nativeEvent: new DragEvent("dashDragAutoScroll"), currentTarget: target, target: target, diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 32817eefd..082b6d8bd 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -7,7 +7,7 @@ import { Docs } from "../documents/Documents"; import { ScriptField, ComputedField } from "../../fields/ScriptField"; import { RichTextField } from "../../fields/RichTextField"; import { ImageField } from "../../fields/URLField"; -import { Scripting } from "./Scripting"; +import { ScriptingGlobals } from "./ScriptingGlobals"; import { listSpec } from "../../fields/Schema"; // @@ -81,5 +81,5 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { data.droppedDocuments[i] = dbox; }); } -Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }, +ScriptingGlobals.add(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }, "converts the dropped data to buttons", "(dragData: any)"); \ No newline at end of file diff --git a/src/client/util/History.ts b/src/client/util/History.ts index cbe36b401..e6f75a7db 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -103,8 +103,7 @@ export namespace HistoryUtil { return undefined; } const parser = requiredFields[required]; - let value = opts[required]; - value = parse(parser, value); + const value = parse(parser, opts[required]); if (value !== null && value !== undefined) { current[required] = value; } @@ -114,8 +113,7 @@ export namespace HistoryUtil { continue; } const parser = optionalFields[opt]; - let value = opts[opt]; - value = parse(parser, value); + const value = parse(parser, opts[opt]); if (value !== undefined) { current[opt] = value; } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 925b74efa..39e9251a5 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,9 +1,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { BatchedArray } from "array-batcher"; -import "fs"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import * as path from 'path'; +import { extname } from "path"; import Measure, { ContentRect } from "react-measure"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; @@ -86,7 +85,7 @@ export class DirectoryImportBox extends React.Component { for (let i = 0; i < files.length; i++) { const file = files.item(i); if (file && !unsupported.includes(file.type)) { - const ext = path.extname(file.name).toLowerCase(); + const ext = extname(file.name).toLowerCase(); if (AcceptableMedia.imageFormats.includes(ext)) { validated.push(file); } @@ -120,7 +119,7 @@ export class DirectoryImportBox extends React.Component { } const { accessPaths, exifData } = result; const path = Utils.prepend(accessPaths.agnostic.client); - const document = await DocUtils.DocumentFromType(type, path, { _width: 300, title: name }); + const document = type && await DocUtils.DocumentFromType(type, path, { _width: 300 }); const { data, error } = exifData; if (document) { Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 62b13e2c6..df2c02a8d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -194,7 +194,7 @@ export class LinkManager { afterFocus: (didFocus: boolean) => { finished?.(); res(ViewAdjustment.resetView); - return new Promise(res2 => res2()); + return new Promise(res2 => res2(ViewAdjustment.doNothing)); } }); } else { diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts index 94806a7ba..42a6493ea 100644 --- a/src/client/util/ScriptManager.ts +++ b/src/client/util/ScriptManager.ts @@ -1,9 +1,9 @@ import { Doc, DocListCast } from "../../fields/Doc"; import { List } from "../../fields/List"; -import { Scripting } from "./Scripting"; -import { StrCast, Cast } from "../../fields/Types"; import { listSpec } from "../../fields/Schema"; +import { Cast, StrCast } from "../../fields/Types"; import { Docs } from "../documents/Documents"; +import { ScriptingGlobals } from "./ScriptingGlobals"; export class ScriptManager { @@ -44,7 +44,7 @@ export class ScriptManager { public deleteScript(scriptDoc: Doc): boolean { if (scriptDoc.name) { - Scripting.removeGlobal(StrCast(scriptDoc.name)); + ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name)); } const scriptList = this.getAllScripts(); const index = scriptList.indexOf(scriptDoc); @@ -60,7 +60,7 @@ export class ScriptManager { public static addScriptToGlobals(scriptDoc: Doc): void { - Scripting.removeGlobal(StrCast(scriptDoc.name)); + ScriptingGlobals.removeGlobal(StrCast(scriptDoc.name)); const params = Cast(scriptDoc["data-params"], listSpec("string"), []); const paramNames = params.reduce((o: string, p: string) => { @@ -86,9 +86,9 @@ export class ScriptManager { }); if (parameters === "(") { - Scripting.addGlobal(f, StrCast(scriptDoc.description)); + ScriptingGlobals.add(f, StrCast(scriptDoc.description)); } else { - Scripting.addGlobal(f, StrCast(scriptDoc.description), parameters); + ScriptingGlobals.add(f, StrCast(scriptDoc.description), parameters); } } } \ No newline at end of file diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 40b94024e..3b0a47b54 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -1,16 +1,15 @@ -import * as ts from "typescript"; -export { ts }; - // export const ts = (window as any).ts; - // // @ts-ignore // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts' // // @ts-ignore // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' - // @ts-ignore import * as typescriptlib from '!!raw-loader!./type_decls.d'; -import { Doc, Field } from '../../fields/Doc'; +import * as ts from "typescript"; +import { Doc, Field } from "../../fields/Doc"; +import { scriptingGlobals, ScriptingGlobals } from "./ScriptingGlobals"; +export { ts }; + export interface ScriptSuccess { success: true; @@ -47,98 +46,6 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is return false; } -export namespace Scripting { - export function addGlobal(global: { name: string }): void; - export function addGlobal(name: string, global: any): void; - - export function addGlobal(global: { name: string }, decription?: string, params?: string): void; - - export function addGlobal(first: any, second?: any, third?: string) { - let n: any; - let obj: any; - - if (second !== undefined) { - if (typeof first === "string") { - n = first; - obj = second; - } else { - obj = first; - n = first.name; - _scriptingDescriptions[n] = second; - if (third !== undefined) { - _scriptingParams[n] = third; - } - } - } else if (first && typeof first.name === "string") { - n = first.name; - obj = first; - } else { - throw new Error("Must either register an object with a name, or give a name and an object"); - } - if (n === undefined || n === "undefined") { - return false; - } else if (_scriptingGlobals.hasOwnProperty(n)) { - throw new Error(`Global with name ${n} is already registered, choose another name`); - } - _scriptingGlobals[n] = obj; - } - - export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) { - return { ..._scriptingGlobals, ...(globals || {}) }; - } - - export function setScriptingGlobals(globals: { [key: string]: any }) { - scriptingGlobals = globals; - } - - export function removeGlobal(name: string) { - if (getGlobals().includes(name)) { - delete _scriptingGlobals[name]; - if (_scriptingDescriptions[name]) { - delete _scriptingDescriptions[name]; - } - if (_scriptingParams[name]) { - delete _scriptingParams[name]; - } - return true; - } - return false; - } - - export function resetScriptingGlobals() { - scriptingGlobals = _scriptingGlobals; - } - - // const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]); - export function printNodeType(node: any, indentation = "") { - console.log(indentation + ts.SyntaxKind[node.kind]); - } - - export function getGlobals() { - return Object.keys(_scriptingGlobals); - } - - export function getGlobalObj() { - return _scriptingGlobals; - } - - export function getDescriptions() { - return _scriptingDescriptions; - } - - export function getParameters() { - return _scriptingParams; - } -} - -export function scriptingGlobal(constructor: { new(...args: any[]): any }) { - Scripting.addGlobal(constructor); -} - -export const _scriptingGlobals: { [name: string]: any } = {}; -let scriptingGlobals: { [name: string]: any } = _scriptingGlobals; -const _scriptingDescriptions: { [name: string]: any } = {}; -const _scriptingParams: { [name: string]: any } = {}; function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error); @@ -273,7 +180,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp if (options.params && !options.params.this) options.params.this = Doc.name; if (options.params && !options.params.self) options.params.self = Doc.name; if (options.globals) { - Scripting.setScriptingGlobals(options.globals); + ScriptingGlobals.setScriptingGlobals(options.globals); } const host = new ScriptingCompilerHost; if (options.traverser) { @@ -331,9 +238,9 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const result = Run(outputText, paramNames, diagnostics, script, options); if (options.globals) { - Scripting.resetScriptingGlobals(); + ScriptingGlobals.resetScriptingGlobals(); } return result; } -Scripting.addGlobal(CompileScript); \ No newline at end of file +ScriptingGlobals.add(CompileScript); diff --git a/src/client/util/ScriptingGlobals.ts b/src/client/util/ScriptingGlobals.ts new file mode 100644 index 000000000..f151acd81 --- /dev/null +++ b/src/client/util/ScriptingGlobals.ts @@ -0,0 +1,81 @@ + +import * as ts from "typescript"; +export { ts }; + +export namespace ScriptingGlobals { + export function add(global: { name: string }): void; + export function add(name: string, global: any): void; + export function add(global: { name: string }, decription?: string, params?: string): void; + export function add(first: any, second?: any, third?: string) { + let n: any; + let obj: any; + + if (second !== undefined) { + if (typeof first === "string") { + n = first; + obj = second; + } else { + obj = first; + n = first.name; + _scriptingDescriptions[n] = second; + if (third !== undefined) { + _scriptingParams[n] = third; + } + } + } else if (first && typeof first.name === "string") { + n = first.name; + obj = first; + } else { + throw new Error("Must either register an object with a name, or give a name and an object"); + } + if (n === undefined || n === "undefined") { + return false; + } else if (_scriptingGlobals.hasOwnProperty(n)) { + throw new Error(`Global with name ${n} is already registered, choose another name`); + } + _scriptingGlobals[n] = obj; + } + export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) { + return { ..._scriptingGlobals, ...(globals || {}) }; + } + + export function setScriptingGlobals(globals: { [key: string]: any }) { + scriptingGlobals = globals; + } + + export function removeGlobal(name: string) { + if (getGlobals().includes(name)) { + delete _scriptingGlobals[name]; + if (_scriptingDescriptions[name]) { + delete _scriptingDescriptions[name]; + } + if (_scriptingParams[name]) { + delete _scriptingParams[name]; + } + return true; + } + return false; + } + + export function resetScriptingGlobals() { scriptingGlobals = _scriptingGlobals; } + + // const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]); + export function printNodeType(node: any, indentation = "") { console.log(indentation + ts.SyntaxKind[node.kind]); } + + export function getGlobals() { return Object.keys(_scriptingGlobals); } + + export function getGlobalObj() { return _scriptingGlobals; } + + export function getDescriptions() { return _scriptingDescriptions; } + + export function getParameters() { return _scriptingParams; } +} + +export function scriptingGlobal(constructor: { new(...args: any[]): any }) { + ScriptingGlobals.add(constructor); +} + +const _scriptingGlobals: { [name: string]: any } = {}; +export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals; +const _scriptingDescriptions: { [name: string]: any } = {}; +const _scriptingParams: { [name: string]: any } = {}; \ No newline at end of file diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 44e44d335..d1f1a0099 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,5 +1,4 @@ import { observable, action, runInAction } from "mobx"; -import 'source-map-support/register'; import { Without } from "../../Utils"; function getBatchName(target: any, key: string | symbol): string { @@ -107,7 +106,7 @@ export namespace UndoManager { } export function FilterBatches(fieldTypes: string[]) { const fieldCounts: { [key: string]: number } = {}; - const lastStack = UndoManager.undoStack.lastElement(); + const lastStack = UndoManager.undoStack.slice(-1)[0];//.lastElement(); if (lastStack) { lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1)); const fieldCount2: { [key: string]: number } = {}; -- cgit v1.2.3-70-g09d2 From 6e61005b3b6dd8deeb3ca43a8c6241d7778a1998 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 24 Mar 2022 09:58:01 -0400 Subject: fixed videoBox play issue. cleaned up global referencing a bit. --- src/client/util/CurrentUserUtils.ts | 1 + src/client/views/MainView.tsx | 2 +- .../collectionLinear/CollectionLinearView.tsx | 54 +++++++++++----------- .../CollectionMulticolumnView.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 6 +-- src/client/views/nodes/VideoBox.tsx | 6 +-- src/client/views/nodes/WebBox.tsx | 2 +- 7 files changed, 37 insertions(+), 36 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c0e3f66eb..22ab4beb8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1501,6 +1501,7 @@ export class CurrentUserUtils { return tbox; } + public static get DockedBtns() { return Cast(Doc.UserDoc().dockedBtns, Doc, null); } public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); } public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); } public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8c0795881..7cb40f701 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -184,7 +184,7 @@ export class MainView extends React.Component { fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines, - fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt]); + fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSearchPlus, fa.faVolumeUp, fa.faVolumeDown]); this.initAuthenticationRouters(); } diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 160134b60..1d142d595 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -8,6 +8,7 @@ import { Id } from '../../../../fields/FieldSymbols'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; import { DocUtils } from '../../../documents/Documents'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; @@ -206,36 +207,35 @@ export class CollectionLinearView extends CollectionSubView() { }}> {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))} - {DocumentLinksButton.StartLink && StrCast(this.layoutDoc.title) === "docked buttons" ? e.stopPropagation()} > - - Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'} - - -
{"Toggle description pop-up"}
} placement="top"> - - Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"} + {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.DockedBtns ? null : + e.stopPropagation()} > + + Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'} -
-
Exit linking mode
} placement="top"> - - Stop +
{"Toggle description pop-up"}
} placement="top"> + + Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"} + +
+ +
Exit linking mode
} placement="top"> + + Stop + +
+
} + {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.DockedBtns ? (null) : + + + Currently playing: + {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => + DocumentManager.Instance.jumpToDocument(clip, true)}> + {clip.title + (i == CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")} + )} -
- -
: null} - {CollectionStackedTimeline.CurrentlyPlaying && CollectionStackedTimeline.CurrentlyPlaying.length != 0 && StrCast(this.layoutDoc.title) === "docked buttons" ? - - Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => - { - DocumentManager.Instance.jumpToDocument(clip, true); - }}>{clip.title + (i == CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")} - )} - - : null} + } ; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 2bdf92417..6929a1cd8 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -214,7 +214,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { } return this.props.addDocTab(doc, where); } - getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { + getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return { if (CollectionStackedTimeline.CurrentlyPlaying) { - const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc); + const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc); index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1); } } @@ -212,8 +212,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent { if (CollectionStackedTimeline.CurrentlyPlaying) { - const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc.doc as Doc); + const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(this.layoutDoc); index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1); } } @@ -488,8 +488,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - VideoBox.convertDataUri(dataUrl, this.layoutDoc[Id] + "-thumb" + (new Date()).getTime(), true).then( + VideoBox.convertDataUri(dataUrl, this.layoutDoc[Id] + "-thumb" + (new Date()).getTime()).then( returnedfilename => setTimeout(action(() => this.layoutDoc.thumb = new ImageField(returnedfilename)), 500)); }) .catch(function (error: any) { -- cgit v1.2.3-70-g09d2 From 2c3264bca271af170b620cfc44ecdfafb77aacc0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 28 Mar 2022 12:20:07 -0400 Subject: enabled dragging documents onto tab bar without shift. modified link pivot document to automatically update when links are added and be a stacking view --- src/client/util/DragManager.ts | 4 +-- src/client/views/nodes/DocumentLinksButton.tsx | 35 ++++++++------------------ src/client/views/nodes/DocumentView.tsx | 26 ++++++++++++++++++- src/client/views/nodes/WebBox.scss | 2 +- 4 files changed, 39 insertions(+), 28 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c9c499fff..0d6a77f71 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -458,9 +458,9 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; } - if (e?.shiftKey && dragData.draggedDocuments.length === 1) { + if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { dragData.dropAction = dragData.userDropAction || "same"; - if (dragData.dropAction === "move") { + if (dragData.dropAction === "move" || dragData.dropAction === "same") { dragData.removeDocument?.(dragData.draggedDocuments[0]); } AbortDrag(); diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 7e6ca4248..804e82856 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -2,9 +2,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DocListCastAsync, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; -import { Cast, StrCast } from "../../../fields/Types"; +import { ScriptField } from "../../../fields/ScriptField"; +import { StrCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils"; import { DocServer } from "../../DocServer"; @@ -20,7 +21,6 @@ import { DocumentView } from "./DocumentView"; import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { TaskCompletionBox } from "./TaskCompletedBox"; import React = require("react"); -import { Transform } from "../../util/Transform"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -72,31 +72,18 @@ export class DocumentLinksButton extends React.Component { setupMoveUpEvents(this, e, this.onLinkButtonMoved, emptyFunction, action((e, doubleTap) => { if (doubleTap) { - const rootDoc = this.props.View.rootDoc; - const docid = Doc.CurrentUserEmail + Doc.GetProto(rootDoc)[Id] + "-pivotish"; - DocServer.GetRefField(docid).then(async docx => { + const linkSource = this.props.View.rootDoc; + const docid = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + "-pivotish"; + DocServer.GetRefField(docid).then(docx => { const rootAlias = () => { - const rootAlias = Doc.MakeAlias(rootDoc); + const rootAlias = Doc.MakeAlias(linkSource); rootAlias.x = rootAlias.y = 0; return rootAlias; }; - let wid = rootDoc[WidthSym](); - const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([rootAlias()], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid); - const docs = await DocListCastAsync(Doc.GetProto(target).data); - if (!target.pivotFocusish) (Doc.GetProto(target).pivotFocusish = target); - DocListCast(rootDoc.links).forEach(link => { - const other = LinkManager.getOppositeAnchor(link, rootDoc); - const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other; - if (otherdoc && !docs?.some(d => Doc.AreProtosEqual(d, otherdoc))) { - const alias = Doc.MakeAlias(otherdoc); - alias.x = wid; - alias.y = 0; - alias._lockedPosition = false; - wid += otherdoc[WidthSym](); - Doc.AddDocToList(Doc.GetProto(target), "data", alias); - } - }); - LightboxView.SetLightboxDoc(target); + const linkCollection = ((docx instanceof Doc) && docx) || Docs.Create.StackingDocument([/*rootAlias()*/], { title: this.props.View.Document.title + "-pivot", _width: 500, _height: 500, }, docid); + linkCollection.linkSource = linkSource; + if (!linkCollection.reactionScript) linkCollection.reactionScript = ScriptField.MakeScript("updateLinkCollection(self)"); + LightboxView.SetLightboxDoc(linkCollection); }); } else DocumentLinksButton.LinkEditorDocView = this.props.View; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e7de87ea5..955698190 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -2,7 +2,7 @@ import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; +import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, DocListCastAsync, Field, Opt, StrListCast, WidthSym } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -1247,6 +1247,10 @@ export class DocumentView extends React.Component { return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).scale(1 / this.nativeScaling); } componentDidMount() { + this._disposers.reactionScript = reaction( + () => ScriptCast(this.rootDoc.reactionScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result, + output => output + ); this._disposers.height = reaction( () => NumCast(this.layoutDoc._height), action(height => { @@ -1298,4 +1302,24 @@ export class DocumentView extends React.Component { ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); else dv.switchViews(true, detailLayoutKeySuffix); +}); + +ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) { + const linkSource = Cast(linkCollection.linkSource, Doc, null); + const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data); + let wid = linkSource[WidthSym](); + const links = DocListCast(linkSource.links); + links.forEach(link => { + const other = LinkManager.getOppositeAnchor(link, linkSource); + const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other; + if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) { + const alias = Doc.MakeAlias(otherdoc); + alias.x = wid; + alias.y = 0; + alias._lockedPosition = false; + wid += otherdoc[WidthSym](); + Doc.AddDocToList(Doc.GetProto(linkCollection), "data", alias); + } + }); + return links; }); \ No newline at end of file diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 9d8825ad1..ff38e37dd 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -10,7 +10,7 @@ width: 100%; height: 100%; cursor: ew-resize; - background: yellow; + background: darkgray; } .webBox-background { width: 100%; -- cgit v1.2.3-70-g09d2 From 85c24ec4dcc39551e8c7de83d3482ec15472c030 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 31 Mar 2022 12:01:37 -0400 Subject: added autoLinks to formattedTextBoxes. Set auto link target by titling documents with a prefix '@'. --- src/client/util/CurrentUserUtils.ts | 11 +++++ src/client/views/DocumentDecorations.tsx | 6 +++ src/client/views/nodes/LinkDocPreview.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 48 ++++++++++++++-------- .../formattedText/FormattedTextBoxComment.tsx | 10 +++-- src/client/views/nodes/formattedText/marks_rts.ts | 27 ++++++++++++ 6 files changed, 83 insertions(+), 21 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 22ab4beb8..e51abf63b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1141,6 +1141,17 @@ export class CurrentUserUtils { // Sharing sidebar is where shared documents are contained static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { + if (doc.myPublishedDocs === undefined) { + let pubDocs = Docs.newAccount ? undefined : Cast((await doc.myPublishedDocs), Doc, null); + if (!pubDocs) { + pubDocs = new Doc(linkDatabaseId, true); + (pubDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail; + (pubDocs as Doc).author = Doc.CurrentUserEmail; + (pubDocs as Doc).data = new List([]); + (pubDocs as Doc)["acl-Public"] = SharingPermissions.Augment; + doc.myPublishedDocs = new PrefetchProxy(pubDocs); + } + } if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); if (!linkDocs) { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4c9f15fef..328b8ab88 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -86,7 +86,13 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P titleFieldKey === "title" && (d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-")); //@ts-ignore const titleField = (+this._accumulatedTitle === this._accumulatedTitle ? +this._accumulatedTitle : this._accumulatedTitle); + if (titleFieldKey === "title" && StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) { + Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", SelectionManager.Docs().lastElement()); + } Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true); + if (titleFieldKey === "title" && this._accumulatedTitle.startsWith("@")) { + Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", SelectionManager.Docs().lastElement()); + } if (d.rootDoc.syncLayoutFieldWithTitle) { const title = titleField.toString(); diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 2e29c0656..46736aa4e 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -83,7 +83,7 @@ export class LinkDocPreview extends React.Component { const anchorDoc = href.replace(Doc.localServerPath(), "").split("?")[0]; anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => { if (anchor instanceof Doc && DocListCast(anchor.links).length) { - this._linkDoc = DocListCast(anchor.links)[0]; + this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0]; this._linkSrc = anchor; const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c12c4acf0..ab2d84893 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -63,6 +63,7 @@ import React = require("react"); import { SidebarAnnos } from '../../SidebarAnnos'; import { Colors } from '../../global/globalEnums'; import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { LinkManager } from '../../../util/LinkManager'; const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { @@ -346,7 +347,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } + autoLink = () => { + if (this._editorView) { + const newAutoLinks = new Set(); + const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic"); + const f = this._editorView.state.selection.from; + const t = this._editorView.state.selection.to; + var tr = this._editorView.state.tr as any; + tr = tr.removeMark(0, tr.doc.content.size, this._editorView!.state.schema.marks.autoLinkAnchor); + DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); + tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); + this._editorView?.dispatch(tr); + oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink)).forEach(LinkManager.Instance.deleteLink); + } + } + updateTitle = () => { + const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic"); if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] && (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { @@ -357,26 +374,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - // needs a better API for taking in a set of words with target documents instead of just one target - hyperlinkTerms = (terms: string[], target: Doc) => { - if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { - const res1 = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - let tr = this._editorView.state.tr; - const flattened1: TextSelection[] = []; - res1.map(r => r.map(h => flattened1.push(h))); + // creates links between terms in a document and documents which have a matching Id + hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { + if (this._editorView && (this._editorView as any).docView) { + const flattened1 = this.findInNode(this._editorView, this._editorView.state.doc, StrCast(target.title).replace(/^@/, "")); + var alink: Doc | undefined; flattened1.forEach((flat, i) => { - const flattened: TextSelection[] = []; - const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - res.map(r => r.map(h => flattened.push(h))); + const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, StrCast(target.title).replace(/^@/, "")); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - const anchor = Docs.Create.TextanchorDocument(); - const alink = DocUtils.MakeLink({ doc: anchor }, { doc: target }, "automatic")!; - const allAnchors = [{ href: Doc.localServerPath(anchor), title: "a link", anchorId: anchor[Id] }]; - const link = this._editorView!.state.schema.marks.linkAnchor.create({ allAnchors, title: "auto link", location }); + alink = alink ?? (DocListCast(this.Document.links).find(link => + Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && + Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!); + newAutoLinks.add(alink); + const allAnchors = [{ href: Doc.localServerPath(this.props.Document), title: "a link", anchorId: this.props.Document[Id] }]; + const link = this._editorView!.state.schema.marks.autoLinkAnchor.create({ allAnchors, linkDoc: alink[Id], title: "auto link", location }); tr = tr.addMark(flattened[i].from, flattened[i].to, link); }); - this._editorView.dispatch(tr); } + return tr; } highlightSearchTerms = (terms: string[], backward: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { @@ -1256,7 +1271,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); let target = (e.target as any).parentElement; // hrefs are stored on the database of the node that wraps the hyerlink while (target && !target.dataset?.targethrefs) target = target.parentElement; - FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs); + FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); } (e.nativeEvent as any).formattedHandled = true; @@ -1399,6 +1414,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @action onBlur = (e: any) => { + this.autoLink(); FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined); if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 1fde6e5f0..3e673c0b2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -2,6 +2,7 @@ import { Mark, ResolvedPos } from "prosemirror-model"; import { EditorState } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../../../fields/Doc"; +import { DocServer } from "../../../DocServer"; import { LinkDocPreview } from "../LinkDocPreview"; import { FormattedTextBox } from "./FormattedTextBox"; import './FormattedTextBoxComment.scss'; @@ -9,7 +10,7 @@ import { schema } from "./schema_rts"; export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); } export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); } -export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.linkAnchor); } +export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); } export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { let before = 0, nbef = rpos.nodeBefore; while (nbef && finder(nbef.marks)) { @@ -82,14 +83,14 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.display = ""; } - static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "") { + static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "", linkDoc: string = "") { FormattedTextBoxComment.textBox = textBox; if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { - FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h)); + FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h), linkDoc); } } - static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[]) { + static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string) { const state = view.state; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { @@ -115,6 +116,7 @@ export class FormattedTextBoxComment { nbef && naft && LinkDocPreview.SetLinkInfo({ docProps: textBox.props, linkSrc: textBox.rootDoc, + linkDoc: linkDoc ? DocServer.GetCachedRefField(linkDoc) as Doc : undefined, location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))), hrefs, showHeader: true diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 6103a28d6..52ef06b7f 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -17,6 +17,33 @@ export const marks: { [index: string]: MarkSpec } = { return ["div", { className: "dummy" }, 0]; } }, + + + // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title` + // defaults to the empty string. Rendered and parsed as an `` + // element. + autoLinkAnchor: { + attrs: { + allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] }, + linkDoc: { default: "" }, + location: { default: null }, + title: { default: null }, + }, + inclusive: false, + parseDOM: [{ + tag: "a[href]", getAttrs(dom: any) { + return { + location: dom.getAttribute("location"), + title: dom.getAttribute("title") + }; + } + }], + toDOM(node: any) { + const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, ""); + const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, ""); + return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; + } + }, // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title` // defaults to the empty string. Rendered and parsed as an `` // element. -- cgit v1.2.3-70-g09d2 From dea5f9c74b62706b17958d95b154005af271cf59 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 31 Mar 2022 12:57:21 -0400 Subject: fixed initialization of myPublishedDocs to be a simple list. --- src/client/util/CurrentUserUtils.ts | 10 +--------- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 6 +++--- 2 files changed, 4 insertions(+), 12 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e51abf63b..f2094407d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1142,15 +1142,7 @@ export class CurrentUserUtils { // Sharing sidebar is where shared documents are contained static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.myPublishedDocs === undefined) { - let pubDocs = Docs.newAccount ? undefined : Cast((await doc.myPublishedDocs), Doc, null); - if (!pubDocs) { - pubDocs = new Doc(linkDatabaseId, true); - (pubDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail; - (pubDocs as Doc).author = Doc.CurrentUserEmail; - (pubDocs as Doc).data = new List([]); - (pubDocs as Doc)["acl-Public"] = SharingPermissions.Augment; - doc.myPublishedDocs = new PrefetchProxy(pubDocs); - } + doc.myPublishedDocs = new List(); } if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ab2d84893..6c4ca2d90 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -350,11 +350,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp autoLink = () => { if (this._editorView) { const newAutoLinks = new Set(); - const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic"); + const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === "automatic"); const f = this._editorView.state.selection.from; const t = this._editorView.state.selection.to; var tr = this._editorView.state.tr as any; - tr = tr.removeMark(0, tr.doc.content.size, this._editorView!.state.schema.marks.autoLinkAnchor); + const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor + tr = tr.removeMark(0, tr.doc.content.size, autoAnch); DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); @@ -363,7 +364,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } updateTitle = () => { - const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship = "automatic"); if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] && (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { -- cgit v1.2.3-70-g09d2 From 19366220aabeeb6fcdac6fa056b9b0e9585e8e6b Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Apr 2022 10:34:09 -0400 Subject: fixed up document focus to take a collectkon so that the treeView can focus on the matching alias when multiple aliases are shown in different collections. --- src/client/util/DocumentManager.ts | 13 ++++++++----- src/client/views/PropertiesDocContextSelector.tsx | 3 +++ src/client/views/StyleProvider.tsx | 8 +++++++- src/client/views/collections/TreeView.tsx | 2 +- 4 files changed, 19 insertions(+), 7 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0a00ab6e0..ad6d90bc3 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -5,6 +5,7 @@ import { Cast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; +import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { CollectionView } from '../views/collections/CollectionView'; import { LightboxView } from '../views/LightboxView'; import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView'; @@ -252,10 +253,11 @@ export class DocumentManager { } } -ScriptingGlobals.add(function DocFocusOrOpen(doc: any) { - const dv = DocumentManager.Instance.getDocumentView(doc); - if (dv && dv.props.Document === doc) { - dv.props.focus(doc, { willZoom: true }); +export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) { + const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc); + const dv = DocumentManager.Instance.getDocumentView(doc, (cv?.ComponentView as CollectionFreeFormView)?.props.CollectionView); + if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) { + dv.props.focus(dv.props.Document, { willZoom: true }); Doc.linkFollowHighlight(dv?.props.Document, false); } else { @@ -264,4 +266,5 @@ ScriptingGlobals.add(function DocFocusOrOpen(doc: any) { CollectionDockingView.AddSplit(showDoc === Doc.GetProto(showDoc) ? Doc.MakeAlias(showDoc) : showDoc, "right") && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc)); } -}); \ No newline at end of file +} +ScriptingGlobals.add(DocFocusOrOpen); \ No newline at end of file diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 4d803f992..015e0c8ee 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -4,6 +4,7 @@ import * as React from "react"; import { Doc, DocListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { Cast, NumCast, StrCast } from "../../fields/Types"; +import { DocFocusOrOpen } from "../util/DocumentManager"; import { CollectionDockingView } from "./collections/CollectionDockingView"; import { CollectionViewType } from "./collections/CollectionView"; import { DocumentView } from "./nodes/DocumentView"; @@ -32,12 +33,14 @@ export class PropertiesDocContextSelector extends React.Component { + if (!this.props.DocView) return; col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; if (col._viewType === CollectionViewType.Freeform) { col._panX = NumCast(target.x) + NumCast(target._width) / 2; col._panY = NumCast(target.y) + NumCast(target._height) / 2; } this.props.addDocTab(col, "toggle:right"); + setTimeout(() => DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100); } render() { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 2782574c5..649ee8394 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -10,6 +10,7 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; import { DashColor, lightOrDark } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { DocFocusOrOpen } from '../util/DocumentManager'; import { ColorScheme } from '../util/SettingsManager'; import { SnappingManager } from '../util/SnappingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; @@ -212,7 +213,12 @@ export function DashboardStyleProvider(doc: Opt, props: Opt - {DashboardToggleButton(doc, "hidden", "eye-slash", "eye")} + {DashboardToggleButton(doc, "hidden", "eye-slash", "eye", () => { + doc.hidden = doc.hidden ? undefined : true; + if (!doc.hidden) { + DocFocusOrOpen(doc, props?.ContainingCollectionDoc); + } + })} {DashboardToggleButton(doc, "lockedPosition", "lock", "unlock")} ; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index eedb353e3..ff5c4bab1 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -669,7 +669,7 @@ export class TreeView extends React.Component { ContentScaling={returnOne} />; - const buttons = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ":afterHeader" : "")); + const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ":afterHeader" : "")); return <>
Date: Mon, 4 Apr 2022 18:35:10 -0400 Subject: fixed autolink. fixed the pileup view to animate properly and activate without selection. added a 'dataTransition" prop to DocumentView to allow collections to animate changes in the size of documents. --- src/client/documents/Documents.ts | 18 ++++++++------- src/client/util/CurrentUserUtils.ts | 12 +++++----- .../views/collections/CollectionPileView.tsx | 27 ++++++++++++---------- src/client/views/collections/CollectionSubView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 19 +++++++++++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +++++++--- src/fields/Doc.ts | 1 - 9 files changed, 58 insertions(+), 36 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fdfd7bd31..bb60586eb 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -823,7 +823,7 @@ export namespace Docs { } export function PileDocument(documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: "visible", _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array, options: DocumentOptions, id?: string) { @@ -1344,7 +1344,7 @@ export namespace DocUtils { if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); } - export function pileup(docList: Doc[], x?: number, y?: number) { + export function pileup(docList: Doc[], x?: number, y?: number, create: boolean = true) { let w = 0, h = 0; runInAction(() => { docList.forEach(d => { @@ -1359,12 +1359,14 @@ export namespace DocUtils { d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection }); }); - const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, _overflow: "visible" }); - newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; - newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; - newCollection._width = newCollection._height = 110; - newCollection._jitterRotation = 10; - return newCollection; + if (create) { + const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, }); + newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; + newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; + newCollection._width = newCollection._height = 110; + newCollection._jitterRotation = 10; + return newCollection; + } } export function LeavePushpin(doc: Doc, annotationField: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f2094407d..af731ce9f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -292,10 +292,10 @@ export class CurrentUserUtils { if (doc["template-icon-view"] === undefined) { const iconView = Docs.Create.LabelDocument({ title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); // Docs.Create.TextDocument("", { - // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }) // }); // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); iconView.isTemplateDoc = makeTemplate(iconView); @@ -304,7 +304,7 @@ export class CurrentUserUtils { if (doc["template-icon-view-rtf"] === undefined) { const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); @@ -312,20 +312,20 @@ export class CurrentUserUtils { if (doc["template-icon-view-button"] === undefined) { const iconBtnView = Docs.Create.FontIconDocument({ title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30, - _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON); doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView); } if (doc["template-icon-view-img"] === undefined) { const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); } if (doc["template-icon-view-col"] === undefined) { - const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); + const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 0a336c544..0ca0a463e 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -35,6 +35,18 @@ export class CollectionPileView extends CollectionSubView() { layoutEngine = () => StrCast(this.Document._pileLayoutEngine); + @undoBatch + addPileDoc = (doc: Doc | Doc[]) => { + (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); + return this.props.addDocument?.(doc) || false; + } + + @undoBatch + removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { + (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d))); + return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; + } + // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { const isStarburst = this.layoutEngine() === "starburst"; @@ -47,14 +59,8 @@ export class CollectionPileView extends CollectionSubView() { { - (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); - return this.props.addDocument?.(doc) || false; - })} - moveDocument={undoBatch((doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d))); - return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; - })} /> + addDocument={this.addPileDoc} + moveDocument={this.removePileDoc} />
; } @@ -62,19 +68,16 @@ export class CollectionPileView extends CollectionSubView() { toggleStarburst = action(() => { if (this.layoutEngine() === 'starburst') { const defaultSize = 110; - this.layoutDoc._overflow = undefined; - this.childDocs.forEach(d => DocUtils.iconify(d)); this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize); this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize); - DocUtils.pileup(this.childDocs); + DocUtils.pileup(this.childDocs, undefined, undefined, false); this.layoutDoc._panX = 0; this.layoutDoc._panY = -10; this.props.Document._pileLayoutEngine = 'pass'; } else { const defaultSize = 25; - this.layoutDoc._overflow = 'visible'; !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500); !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); if (this.layoutEngine() === 'pass') { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 42e157396..d8f1287cd 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -453,7 +453,7 @@ export function CollectionSubView(moreProps?: X) { if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)); + addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..b2697cf08 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1152,6 +1152,7 @@ export class CollectionFreeFormView extends CollectionSubView this.props.removeDocument?.(d)); - const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2); + const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!; this.props.addDocument?.(newCollection); this.props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 979e86738..a9abf066e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -116,6 +116,7 @@ export interface DocumentViewSharedProps { PanelWidth: () => number; PanelHeight: () => number; docViewPath: () => DocumentView[]; + dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean); styleProvider: Opt; focus: DocFocusFunc; @@ -182,6 +183,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps { @observer export class DocumentViewInternal extends DocComponent() { public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. + _animateScaleTime = 300; // milliseconds; @observable _animateScalingTo = 0; @observable _mediaState = 0; @observable _pendingDoubleClick = false; @@ -1078,9 +1080,8 @@ export class DocumentViewInternal extends DocComponent {this.innards} @@ -1247,8 +1248,8 @@ export class DocumentView extends React.Component { setTimeout(action(() => { this.setCustomView(custom, view); this.docView && (this.docView._animateScalingTo = 1); // expand it - setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), 400); - }), 400); + setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), this.docView!._animateScaleTime - 10); + }), this.docView!._animateScaleTime - 10); }); startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource); @@ -1293,6 +1294,7 @@ export class DocumentView extends React.Component { {!this.props.Document || !this.props.PanelWidth() ? (null) : (
{ } } +export function deiconifyViewFunc(documentView: DocumentView) { + documentView.iconify(); + //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); +} +ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { + documentView.iconify(); +} +); + ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); else dv.switchViews(true, detailLayoutKeySuffix); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 75f0978a8..1f7e557d9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -359,18 +359,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); - oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink)).forEach(LinkManager.Instance.deleteLink); + oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); } } updateTitle = () => { + const title = StrCast(this.dataDoc.title) if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing - StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] && + (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] && (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { let node = this._editorView.state.doc; while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; - this.dataDoc.title = "-" + str.substr(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); + const prefix = str.startsWith("@") ? "" : "-"; + this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); + if (str.startsWith("@") && str.length > 1) { + Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc); + } } } @@ -909,6 +914,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } if (this._editorView && selected) { RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); + this.autoLink(); } }), { fireImmediately: true }); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 1edab16be..ebdbae344 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1414,7 +1414,6 @@ ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -ScriptingGlobals.add(function deiconifyView(doc: any) { Doc.deiconifyView(doc); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); }); ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; }); -- cgit v1.2.3-70-g09d2 From 51d7ce5f71465f2f578a08a998b2df353242ff4d Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 5 Apr 2022 13:54:26 -0400 Subject: fixes to allow dragging golden layout windows to be aborted with 'esc'. some code cleanup as well to avoid rebuilding golden layout when tabs are closed. Fixes for undo and goldenlayout --- src/client/goldenLayout.js | 32 ++++-- src/client/util/DragManager.ts | 25 ++--- src/client/views/DocumentDecorations.tsx | 7 +- src/client/views/GlobalKeyHandler.ts | 1 + .../views/collections/CollectionDockingView.tsx | 109 +++++++++++++-------- src/client/views/collections/TabDocView.tsx | 3 +- 6 files changed, 108 insertions(+), 69 deletions(-) (limited to 'src/client/util') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 896237e1d..295875ef6 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -366,6 +366,7 @@ this._nOriginalY = 0; this._bDragging = false; + this._bAborting = false; this._fMove = lm.utils.fnBind(this.onMouseMove, this); this._fUp = lm.utils.fnBind(this.onMouseUp, this); @@ -427,6 +428,24 @@ } }, + AbortDrag: function () { + if (this._timeout != null) { + clearTimeout(this._timeout); + this._eBody.removeClass('lm_dragging'); + this._eElement.removeClass('lm_dragging'); + this._oDocument.find('iframe').css('pointer-events', ''); + this._oDocument.unbind('mousemove touchmove', this._fMove); + this._oDocument.unbind('mouseup touchend', this._fUp); + + if (this._bDragging === true) { + this._bDragging = false; + this._bAborting = true; + this.emit('dragStop', { pageX: 0, pageY: 0 }, this._nOriginalX + this._nX); + this._bAborting = false; + } + } + }, + onMouseUp: function (oEvent) { if (this._timeout != null) { clearTimeout(this._timeout); @@ -2178,18 +2197,18 @@ */ _onDrop: function () { this._layoutManager.dropTargetIndicator.hide(); - + let abortedDrop = this._dragListener._bAborting; /* * Valid drop area found */ - if (this._area !== null) { + if (!abortedDrop && this._area !== null) { this._area.contentItem._$onDrop(this._contentItem, this._area); /** * No valid drop area available at present, but one has been found before. * Use it */ - } else if (this._lastValidArea !== null) { + } else if (!abortedDrop && this._lastValidArea !== null) { this._lastValidArea.contentItem._$onDrop(this._contentItem, this._lastValidArea); /** @@ -2197,7 +2216,7 @@ * content item to its original position if a original parent is provided. * (Which is not the case if the drag had been initiated by createDragSource) */ - } else if (this._originalParent) { + } else if (!abortedDrop && this._originalParent) { this._originalParent.addChild(this._contentItem); /** @@ -2212,7 +2231,7 @@ restoreScrollTops(this._contentItem.element) this.element.remove(); - this._layoutManager.emit('itemDropped', this._contentItem); + !abortedDrop && this._layoutManager.emit('itemDropped', this._contentItem); }, /** @@ -2963,7 +2982,7 @@ if (this.contentItem.parent.isMaximised === true) { this.contentItem.parent.toggleMaximise(); } - new lm.controls.DragProxy( + let proxy = new lm.controls.DragProxy( x, y, this._dragListener, @@ -2971,6 +2990,7 @@ this.contentItem, this.header.parent ); + this._layoutManager.emit('dragStart', proxy); }, /** diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0d6a77f71..411fc6d11 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,3 +1,4 @@ +import { extend } from "lodash"; import { action } from "mobx"; import { DateField } from "../../fields/DateField"; import { Doc, Field, Opt } from "../../fields/Doc"; @@ -56,12 +57,7 @@ export function SetupDrag( if (e.shiftKey) { e.persist(); const dragDoc = await docFunc(); - dragDoc && DragManager.StartWindowDrag?.({ - pageX: e.pageX, - pageY: e.pageY, - preventDefault: emptyFunction, - button: 0 - }, [dragDoc]); + dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]); } else { document.addEventListener("pointermove", onRowMove); document.addEventListener("pointerup", onRowUp); @@ -74,7 +70,8 @@ export function SetupDrag( export namespace DragManager { let dragDiv: HTMLDivElement; let dragLabel: HTMLDivElement; - export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined; + export let StartWindowDrag: Opt<((e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void)>; + export let CompleteWindowDrag: Opt<(aborted: boolean) => void>; export function Root() { const root = document.getElementById("root"); @@ -460,17 +457,13 @@ export namespace DragManager { } if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { dragData.dropAction = dragData.userDropAction || "same"; - if (dragData.dropAction === "move" || dragData.dropAction === "same") { - dragData.removeDocument?.(dragData.draggedDocuments[0]); - } AbortDrag(); await finishDrag?.(new DragCompleteEvent(true, dragData)); - DragManager.StartWindowDrag?.({ - pageX: e.pageX, - pageY: e.pageY, - preventDefault: emptyFunction, - button: 0 - }, dragData.droppedDocuments); + DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => { + if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) { + dragData.removeDocument?.(dragData.draggedDocuments[0]); + } + }); } const target = document.elementFromPoint(e.x, e.y); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8cd646935..d7ead713a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -151,12 +151,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P } onMaximizeDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, () => { - DragManager.StartWindowDrag?.({ - pageX: e.pageX, - pageY: e.pageY, - preventDefault: emptyFunction, - button: 0 - }, [SelectionManager.Views().slice(-1)[0].rootDoc]); + DragManager.StartWindowDrag?.(e, [SelectionManager.Views().slice(-1)[0].rootDoc]); return true; }, emptyFunction, this.onMaximizeClick, false, false); } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 1a4080d81..4a327a842 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -114,6 +114,7 @@ export class KeyManager { DocumentLinksButton.StartLinkView = undefined; InkStrokeProperties.Instance._controlButton = false; CurrentUserUtils.SelectedTool = InkTool.None; + DragManager.CompleteWindowDrag?.(true); var doDeselect = true; if (SnappingManager.GetIsDragging()) { DragManager.AbortDrag(); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 9e8374605..6931d9896 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -11,6 +11,7 @@ import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { inheritParentAcls } from '../../../fields/util'; +import { emptyFunction } from '../../../Utils'; import { DocServer } from "../../DocServer"; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -46,7 +47,7 @@ export class CollectionDockingView extends CollectionSubView() { private _reactionDisposer?: IReactionDisposer; private _lightboxReactionDisposer?: IReactionDisposer; private _containerRef = React.createRef(); - private _flush: UndoManager.Batch | undefined; + public _flush: UndoManager.Batch | undefined; private _ignoreStateChange = ""; public tabMap: Set = new Set(); public get initialized() { return this._goldenLayout !== null; } @@ -62,15 +63,37 @@ export class CollectionDockingView extends CollectionSubView() { DragManager.StartWindowDrag = this.StartOtherDrag; } - public StartOtherDrag = (e: any, dragDocs: Doc[]) => { - !this._flush && (this._flush = UndoManager.StartBatch("golden layout drag")); + /** + * Switches from dragging a document around a freeform canvas to dragging it as a tab to be docked. + * + * @param e fake mouse down event position data containing pageX and pageY coordinates + * @param dragDocs the documents to be dragged + * @param batch optionally an undo batch that has been started to use instead of starting a new batch + */ + public StartOtherDrag = (e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => { + this._flush = this._flush ?? UndoManager.StartBatch("golden layout drag"); const config = dragDocs.length === 1 ? CollectionDockingView.makeDocumentConfig(dragDocs[0]) : - { type: 'row', content: dragDocs.map((doc, i) => CollectionDockingView.makeDocumentConfig(doc)) }; + { type: 'row', content: dragDocs.map(doc => CollectionDockingView.makeDocumentConfig(doc)) }; const dragSource = this._goldenLayout.createDragSource(document.createElement("div"), config); - //dragSource._dragListener.on("dragStop", dragSource.destroy); - dragSource._dragListener.onMouseDown(e); + this.tabDragStart(dragSource, finishDrag); + dragSource._dragListener.onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }); } + tabItemDropped = () => DragManager.CompleteWindowDrag?.(false); + tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => { + DragManager.CompleteWindowDrag = (aborted: boolean) => { + if (aborted) { + proxy._dragListener.AbortDrag(); + if (this._flush) { + this._flush.cancel(); // cancel the undo change being logged + this._flush = undefined; + this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout) + } + DragManager.CompleteWindowDrag = undefined; + } + finishDrag?.(aborted); + } + } @undoBatch public CloseFullScreen = () => { this._goldenLayout._maximisedItem?.toggleMaximise(); @@ -106,7 +129,6 @@ export class CollectionDockingView extends CollectionSubView() { docconfig.callDownwards('_$init'); instance._goldenLayout._$maximiseItem(docconfig); instance._goldenLayout.emit('stateChanged'); - instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig()); instance.stateChanged(); return true; } @@ -255,7 +277,6 @@ export class CollectionDockingView extends CollectionSubView() { layoutChanged() { this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]); this._goldenLayout.emit('stateChanged'); - this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); this.stateChanged(); return true; } @@ -294,6 +315,9 @@ export class CollectionDockingView extends CollectionSubView() { } } this._goldenLayout.init(); + this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped); + this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart) + this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged); } } @@ -335,13 +359,12 @@ export class CollectionDockingView extends CollectionSubView() { @action onPointerUp = (e: MouseEvent): void => { window.removeEventListener("pointerup", this.onPointerUp); - if (this._flush) { - setTimeout(() => { - CollectionDockingView.Instance._ignoreStateChange = JSON.stringify(CollectionDockingView.Instance._goldenLayout.toConfig()); - this.stateChanged(); - this._flush?.end(); - this._flush = undefined; - }, 10); + const flush = this._flush; + this._flush = undefined; + if (flush) { + DragManager.CompleteWindowDrag = undefined; + if (!this.stateChanged()) flush.cancel(); + else flush.end(); } } @@ -352,9 +375,12 @@ export class CollectionDockingView extends CollectionSubView() { hitFlyout = (par.className === "dockingViewButtonSelector"); } if (!hitFlyout) { + const htmlTarget = e.target as HTMLElement; window.addEventListener("mouseup", this.onPointerUp); - if (!(e.target as HTMLElement).closest("*.lm_content") && ((e.target as HTMLElement).closest("*.lm_tab") || (e.target as HTMLElement).closest("*.lm_stack"))) { - this._flush = UndoManager.StartBatch("golden layout edit"); + if (!htmlTarget.closest("*.lm_content") && (htmlTarget.closest("*.lm_tab") || htmlTarget.closest("*.lm_stack"))) { + if (htmlTarget.className !== "lm_close_tab") { + this._flush = UndoManager.StartBatch("golden layout edit"); + } } } if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && @@ -388,38 +414,43 @@ export class CollectionDockingView extends CollectionSubView() { @action stateChanged = () => { + this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); const json = JSON.stringify(this._goldenLayout.toConfig()); const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); const docids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")); const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc); - - this.props.Document.dockingConfig = json; - setTimeout(async () => { - const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]); - const tabs = sublists && Cast(sublists[0], Doc, null); - // const other = sublists && Cast(sublists[1], Doc, null); - const tabdocs = await DocListCastAsync(tabs?.data); - // const otherdocs = await DocListCastAsync(other?.data); - if (tabs) { - tabs.data = new List(docs); - // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List(docs))); - } - // const otherSet = new Set(); - // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc)); - // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc)); - // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP); - // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List([...docs, ...vals]); - // if (other) { - // other.data = new List(vals); - // // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List(vals))); - // } - }, 0); + const changesMade = this.props.Document.dockingConfig !== json; + if (changesMade && !this._flush) { + this.props.Document.dockingConfig = json; + setTimeout(async () => { + const sublists = await DocListCastAsync(this.props.Document[this.props.fieldKey]); + const tabs = sublists && Cast(sublists[0], Doc, null); + // const other = sublists && Cast(sublists[1], Doc, null); + const tabdocs = await DocListCastAsync(tabs?.data); + // const otherdocs = await DocListCastAsync(other?.data); + if (tabs) { + tabs.data = new List(docs); + // DocListCast(tabs.aliases).forEach(tab => tab !== tabs && (tab.data = new List(docs))); + } + // const otherSet = new Set(); + // otherdocs?.filter(doc => !docs.includes(doc)).forEach(doc => otherSet.add(doc)); + // tabdocs?.filter(doc => !docs.includes(doc) && doc.type !== DocumentType.KVP).forEach(doc => otherSet.add(doc)); + // const vals = Array.from(otherSet.values()).filter(val => val instanceof Doc).map(d => d).filter(d => d.type !== DocumentType.KVP); + // this.props.Document[DataSym][this.props.fieldKey + "-all"] = new List([...docs, ...vals]); + // if (other) { + // other.data = new List(vals); + // // DocListCast(other.aliases).forEach(tab => tab !== other && (tab.data = new List(vals))); + // } + }, 0); + } + return changesMade; } tabDestroyed = (tab: any) => { this.tabMap.delete(tab); tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); + this.stateChanged(); } tabCreated = (tab: any) => { tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 8b5022593..ad5c0efb3 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -190,7 +190,7 @@ export class TabDocView extends React.Component { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true); SelectionManager.DeselectAll(); - tab.contentItem.remove(); + UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); }); } } @@ -277,7 +277,6 @@ export class TabDocView extends React.Component { private onActiveContentItemChanged(contentItem: any) { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; - (CollectionDockingView.Instance as any)._goldenLayout?.isInitialised && CollectionDockingView.Instance.stateChanged(); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } } -- cgit v1.2.3-70-g09d2