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 f61f6be6771415391d9e6733e4cdb1cde32e626d Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 22 Mar 2022 17:36:35 -0400 Subject: Stop drag in present mode if element isn't specifically selected to drag. --- src/client/util/DragManager.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c9c499fff..8d4b98d88 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -214,6 +214,10 @@ export namespace DragManager { options?: DragOptions, dropEvent?: () => any ) { + // stop an 'accidental' on-click drag that may have occured if the user is in presenting mode + // note: dragData.dropAction is only undefined when the element itself being dragged without being selected + if (Doc.UserDoc()?.isPresenting && dragData.dropAction === undefined) return false; + const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc); -- 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 From 4823e1c7ceb0e58cdb515e2bb013632d81767ae3 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 5 Apr 2022 20:03:36 -0400 Subject: added basic grouping when exiting pen mode --- src/Utils.ts | 1 + src/client/util/CurrentUserUtils.ts | 1 + src/client/views/GestureOverlay.tsx | 23 +++--- .../collectionFreeForm/CollectionFreeFormView.tsx | 67 +++++++++++++++-- .../collections/collectionFreeForm/MarqueeView.tsx | 3 + src/client/views/nodes/button/FontIconBox.tsx | 83 +++++++++++++++++++++- 6 files changed, 161 insertions(+), 17 deletions(-) (limited to 'src/client/util') diff --git a/src/Utils.ts b/src/Utils.ts index d0d891f77..6519b5d13 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -407,6 +407,7 @@ export function formatTime(time: number) { return (hours ? hours.toString() + ":" : "") + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0'); } +// x is furthest left, y is furthest top, r is furthest right, b is furthest bottom export function aggregateBounds(boundsList: { x: number, y: number, width?: number, height?: number }[], xpad: number, ypad: number) { const bounds = boundsList.map(b => ({ x: b.x, y: b.y, r: b.x + (b.width || 0), b: b.y + (b.height || 0) })).reduce((bounds, b) => ({ x: Math.min(b.x, bounds.x), y: Math.min(b.y, bounds.y), diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c7f293f2c..6cab8c100 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1011,6 +1011,7 @@ export class CurrentUserUtils { static inkTools(doc: Doc) { const tools: Button[] = [ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen", _readOnly_)' }, + { title: "Mode", toolTip: "Mode (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _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_)' }, diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 05e5b7d5f..87b006a93 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -617,22 +617,23 @@ export class GestureOverlay extends Touchable { newPoints.pop(); const controlPoints: { X: number, Y: number }[] = []; - // const bezierCurves = fitCurve(newPoints, 10); - // for (const curve of bezierCurves) { + const bezierCurves = fitCurve(newPoints, 10); + for (const curve of bezierCurves) { - // controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); - // controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); - // controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); - // controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); + controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); + controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); + controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); + controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); - // } - // const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + - // (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); - // if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; - // this._points = controlPoints; + } + const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); + if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; + this._points = controlPoints; this.dispatchGesture(GestureUtils.Gestures.Stroke); + } this._points = []; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..a694ca2b3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -3,7 +3,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { DateField } from "../../../../fields/DateField"; -import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; @@ -17,7 +17,7 @@ import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; +import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -97,6 +97,9 @@ export class CollectionFreeFormView extends CollectionSubView = new Map(); private _lastTap = 0; private _batch: UndoManager.Batch | undefined = undefined; + + // private isWritingMode: boolean = true; + // private writingModeDocs: Doc[] = []; private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } @@ -114,6 +117,7 @@ export class CollectionFreeFormView extends CollectionSubView(); @observable _marqueeRef = React.createRef(); + @observable _marqueeViewRef = React.createRef(); @observable _keyframeEditing = false; @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. @@ -431,6 +435,26 @@ export class CollectionFreeFormView extends CollectionSubView { + if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { + document.removeEventListener("pointerup", this.onPenUp); + const currentCol = DocListCast(this.rootDoc.currentInkDoc) + const rootDocList = DocListCast(this.rootDoc.data); + console.log("rootDocList", rootDocList[rootDocList.length - 1]); + console.log("currentCol", currentCol); + // if (!currentCol[0].data) { + // currentCol[0].data = []; + // } + // let docList = DocListCast(currentCol[0]); + + currentCol.push(rootDocList[rootDocList.length - 1]); + console.log(currentCol); + + this._batch?.end(); + } + } + @action onPointerDown = (e: React.PointerEvent): void => { this._downX = this._lastX = e.pageX; @@ -442,7 +466,32 @@ export class CollectionFreeFormView extends CollectionSubView(); @undoBatch onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { switch (ge.gesture) { @@ -494,7 +545,14 @@ export class CollectionFreeFormView extends CollectionSubView 0 ? undefined : this.nudge} addDocTab={this.addDocTab} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b10b0912f..eeb2b653b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -60,7 +60,9 @@ export class MarqueeView extends React.Component { + const selected = ffView.unprocessedDocs; + ffView._marqueeViewRef.current?.getCollection(ffView.unprocessedDocs, undefined, [], true); + // loop through selected an get the bound + const bounds: { x: number, y: number, width?: number, height?: number }[] = [] + + selected.map(action(d => { + const x = NumCast(d.x); + const y = NumCast(d.y); + const width = d[WidthSym](); + const height = d[HeightSym](); + bounds.push({x, y, width, height}); + })) + + const aggregBounds = aggregateBounds(bounds, 0, 0); + const marqViewRef = ffView._marqueeViewRef.current; + + // set the vals for bounds in marqueeView + if (marqViewRef) { + marqViewRef._downX = aggregBounds.x; + marqViewRef._downY = aggregBounds.y; + marqViewRef._lastX = aggregBounds.r; + marqViewRef._lastY = aggregBounds.b; + } + + selected.map(action(d => { + const dx = NumCast(d.x); + const dy = NumCast(d.y); + delete d.x; + delete d.y; + delete d.activeFrame; + delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + // TODO: nda - actually calc the bounds + // get the bounds + // d.x = dx - aggregBounds.x; + // d.y = dy - aggregBounds.y; + + // d.x = dx - aggregBounds.x ; + // d.y = dy; + if (marqViewRef?.Bounds) { + d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds?.width / 2; + d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; + } + console.log(d[DataSym], d.x, d.y) + return d; + })); + ffView.props.removeDocument?.(selected); + // TODO: nda - this is the code to actually get a new grouped collection + // const newCollection = ffView._marqueeViewRef.current?.getCollection(ffView.unprocessedDocs, undefined, [], true); + const newCollection = marqViewRef?.getCollection(selected, undefined, [], true); + console.log("newcoll:", newCollection?.[DataSym]); + + // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs + newCollection && ffView.props.addDocument?.(newCollection); + ffView.unprocessedDocs = []; + }); + + CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); + if (checkResult) { return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? Colors.MEDIUM_BLUE : "transparent"; @@ -716,6 +792,9 @@ ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boole } else if (tool) { // pen or eraser if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { Doc.UserDoc().activeInkTool = InkTool.None; + } else if (tool == "write") { + console.log("write mode selected - create groupDoc here!") + // } else { Doc.UserDoc().activeInkTool = tool; GestureOverlay.Instance.InkShape = ""; -- cgit v1.2.3-70-g09d2 From ef63a072a586f51e8fa51b4684987491287540b2 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Thu, 7 Apr 2022 17:21:01 -0400 Subject: added grouping and write mode vs pen mode --- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/GestureOverlay.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 12 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/MapBox/MapBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/nodes/button/FontIconBox.tsx | 112 ++++++++++----------- src/client/views/pdf/PDFViewer.tsx | 2 +- src/fields/InkField.ts | 3 +- webpack.config.js | 2 +- 14 files changed, 77 insertions(+), 79 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 6cab8c100..f08f13bbc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1010,8 +1010,8 @@ export class CurrentUserUtils { static inkTools(doc: Doc) { const tools: Button[] = [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("pen", _readOnly_)' }, - { title: "Mode", toolTip: "Mode (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _readOnly_)' }, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", click: 'setActiveInkTool("pen", _readOnly_)' }, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _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_)' }, diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 87b006a93..53b360e60 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -491,7 +491,10 @@ export class GestureOverlay extends Touchable { @action onPointerDown = (e: React.PointerEvent) => { - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { + CurrentUserUtils.SelectedTool = InkTool.Write; + } this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 9e8374605..c9f55a7bd 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -358,7 +358,7 @@ export class CollectionDockingView extends CollectionSubView() { } } if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && - ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { e.stopPropagation(); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a694ca2b3..40164313c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -468,6 +468,8 @@ export class CollectionFreeFormView extends CollectionSubView e.preventDefault()} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7092b335c..2e822bb6a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -517,7 +517,7 @@ export class DocumentViewInternal extends DocComponent { // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) - if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) { + if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { e.stopPropagation(); if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it @@ -550,7 +550,7 @@ export class DocumentViewInternal extends DocComponent { if (e.cancelBubble) return; - if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) return; + if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return; if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 0a4168698..a8da22b61 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -322,7 +322,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent; } marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index aa2130af5..9bf7c2e8c 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -471,7 +471,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 33fdc4935..a5b8967c4 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -549,7 +549,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 7ff47107e..54c49ecad 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -529,7 +529,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index fc0cc87ca..7f3a667a9 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -13,6 +13,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields import { WebField } from '../../../../fields/URLField'; import { aggregateBounds, Utils } from '../../../../Utils'; import { DocumentType } from '../../../documents/DocumentTypes'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from '../../../util/DocumentManager'; import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; import { SelectionManager } from '../../../util/SelectionManager'; @@ -717,64 +718,56 @@ ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boole // easy way without backing up to the server // - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - const selected = ffView.unprocessedDocs; - ffView._marqueeViewRef.current?.getCollection(ffView.unprocessedDocs, undefined, [], true); - // loop through selected an get the bound - const bounds: { x: number, y: number, width?: number, height?: number }[] = [] - - selected.map(action(d => { - const x = NumCast(d.x); - const y = NumCast(d.y); - const width = d[WidthSym](); - const height = d[HeightSym](); - bounds.push({x, y, width, height}); - })) - - const aggregBounds = aggregateBounds(bounds, 0, 0); - const marqViewRef = ffView._marqueeViewRef.current; - - // set the vals for bounds in marqueeView - if (marqViewRef) { - marqViewRef._downX = aggregBounds.x; - marqViewRef._downY = aggregBounds.y; - marqViewRef._lastX = aggregBounds.r; - marqViewRef._lastY = aggregBounds.b; - } - - selected.map(action(d => { - const dx = NumCast(d.x); - const dy = NumCast(d.y); - delete d.x; - delete d.y; - delete d.activeFrame; - delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - // TODO: nda - actually calc the bounds - // get the bounds - // d.x = dx - aggregBounds.x; - // d.y = dy - aggregBounds.y; + if (CurrentUserUtils.SelectedTool === InkTool.Write) { + CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { + const selected = ffView.unprocessedDocs; + // loop through selected an get the bound + const bounds: { x: number, y: number, width?: number, height?: number }[] = [] - // d.x = dx - aggregBounds.x ; - // d.y = dy; - if (marqViewRef?.Bounds) { - d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds?.width / 2; - d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; - } - console.log(d[DataSym], d.x, d.y) - return d; - })); - ffView.props.removeDocument?.(selected); - // TODO: nda - this is the code to actually get a new grouped collection - // const newCollection = ffView._marqueeViewRef.current?.getCollection(ffView.unprocessedDocs, undefined, [], true); - const newCollection = marqViewRef?.getCollection(selected, undefined, [], true); - console.log("newcoll:", newCollection?.[DataSym]); - - // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - newCollection && ffView.props.addDocument?.(newCollection); - ffView.unprocessedDocs = []; - }); - + selected.map(action(d => { + const x = NumCast(d.x); + const y = NumCast(d.y); + const width = d[WidthSym](); + const height = d[HeightSym](); + bounds.push({x, y, width, height}); + })) + + const aggregBounds = aggregateBounds(bounds, 0, 0); + const marqViewRef = ffView._marqueeViewRef.current; + + // set the vals for bounds in marqueeView + if (marqViewRef) { + marqViewRef._downX = aggregBounds.x; + marqViewRef._downY = aggregBounds.y; + marqViewRef._lastX = aggregBounds.r; + marqViewRef._lastY = aggregBounds.b; + } + + selected.map(action(d => { + const dx = NumCast(d.x); + const dy = NumCast(d.y); + delete d.x; + delete d.y; + delete d.activeFrame; + delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + // TODO: nda - actually calc the bounds + // calculate pos based on bounds + if (marqViewRef?.Bounds) { + d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2; + d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; + } + return d; + })); + ffView.props.removeDocument?.(selected); + // TODO: nda - this is the code to actually get a new grouped collection + const newCollection = marqViewRef?.getCollection(selected, undefined, [], true); + + // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs + newCollection && ffView.props.addDocument?.(newCollection); + ffView.unprocessedDocs = []; + }); + } CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); if (checkResult) { @@ -793,8 +786,9 @@ ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boole if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { Doc.UserDoc().activeInkTool = InkTool.None; } else if (tool == "write") { - console.log("write mode selected - create groupDoc here!") - // + console.log("write mode selected - create groupDoc here!", tool) + Doc.UserDoc().activeInkTool = tool; + GestureOverlay.Instance.InkShape = ""; } else { Doc.UserDoc().activeInkTool = tool; GestureOverlay.Instance.InkShape = ""; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 1856c5353..37fbd7a1d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -371,7 +371,7 @@ export class PDFViewer extends React.Component { if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) { this._setPreviewCursor?.(e.clientX, e.clientY, true, false); } - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { this.props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 31024e805..c34e8f93d 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -11,7 +11,8 @@ export enum InkTool { Pen = "pen", Highlighter = "highlighter", Eraser = "eraser", - Stamp = "stamp" + Stamp = "stamp", + Write = "write" } diff --git a/webpack.config.js b/webpack.config.js index 3fd00bcf3..5a954db19 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -49,7 +49,7 @@ module.exports = { repl: ["./src/debug/Repl.tsx", 'webpack-hot-middleware/client?reload=true'], test: ["./src/debug/Test.tsx", 'webpack-hot-middleware/client?reload=true'], inkControls: ["./src/mobile/InkControls.tsx", 'webpack-hot-middleware/client?reload=true'], - mobileInterface: ["./src/mobile/MobileMain.tsx", 'webpack-hot-middleware/client?reload=true'], + mobileInterface: ["./src/client/views/Main.tsx", 'webpack-hot-middleware/client?reload=true'], }, devtool: "source-map", output: { -- cgit v1.2.3-70-g09d2 From b3d6eaa3a0b126712eae25c1b91925d030a2d900 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 7 Apr 2022 18:06:40 -0400 Subject: added RecordingView --- package-lock.json | 13 + package.json | 2 + src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 16 +- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/FieldView.tsx | 1 + .../views/nodes/RecordingBox/ProgressBar.scss | 26 ++ .../views/nodes/RecordingBox/ProgressBar.tsx | 46 +++ .../views/nodes/RecordingBox/RecordingBox.tsx | 24 ++ .../views/nodes/RecordingBox/RecordingView.scss | 207 +++++++++++++ .../views/nodes/RecordingBox/RecordingView.tsx | 326 +++++++++++++++++++++ src/client/views/nodes/RecordingBox/index.ts | 2 + src/fields/URLField.ts | 1 + 14 files changed, 667 insertions(+), 5 deletions(-) create mode 100644 src/client/views/nodes/RecordingBox/ProgressBar.scss create mode 100644 src/client/views/nodes/RecordingBox/ProgressBar.tsx create mode 100644 src/client/views/nodes/RecordingBox/RecordingBox.tsx create mode 100644 src/client/views/nodes/RecordingBox/RecordingView.scss create mode 100644 src/client/views/nodes/RecordingBox/RecordingView.tsx create mode 100644 src/client/views/nodes/RecordingBox/index.ts (limited to 'src/client/util') diff --git a/package-lock.json b/package-lock.json index b1431e55b..d84e8a572 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2001,6 +2001,14 @@ "@types/react": "*" } }, + "@types/react-icons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz", + "integrity": "sha512-Vefs6LkLqF61vfV7AiAqls+vpR94q67gunhMueDznG+msAkrYgRxl7gYjNem/kZ+as2l2mNChmF1jRZzzQQtMg==", + "requires": { + "react-icons": "*" + } + }, "@types/react-measure": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@types/react-measure/-/react-measure-2.0.8.tgz", @@ -15764,6 +15772,11 @@ "react-resizable": "^1.9.0" } }, + "react-icons": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", + "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==" + }, "react-image-lightbox-with-rotate": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/react-image-lightbox-with-rotate/-/react-image-lightbox-with-rotate-5.1.1.tgz", diff --git a/package.json b/package.json index 2c3e641dd..35aa2ef92 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "@types/dom-speech-recognition": "0.0.1", "@types/formidable": "1.0.31", "@types/google-maps": "^3.2.3", + "@types/react-icons": "^3.0.0", "@types/react-reconciler": "^0.26.4", "@types/reveal": "^3.3.33", "@types/supercluster": "^7.1.0", @@ -253,6 +254,7 @@ "react-datepicker": "^3.8.0", "react-dom": "^16.14.0", "react-grid-layout": "^0.18.3", + "react-icons": "^4.3.1", "react-image-lightbox-with-rotate": "^5.1.1", "react-jsx-parser": "^1.29.0", "react-loading": "^2.0.3", diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 161dff6e0..ca942a38a 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -9,6 +9,7 @@ export enum DocumentType { KVP = "kvp", VID = "video", AUDIO = "audio", + REC = "recording", PDF = "pdf", INK = "inks", SCREENSHOT = "screenshot", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bb60586eb..e50172af5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -11,7 +11,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { AudioField, ImageField, MapField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; +import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; import { OmitKeys, Utils } from "../../Utils"; @@ -61,6 +61,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; +import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { @@ -401,6 +402,10 @@ export namespace Docs { layout: { view: AudioBox, dataField: defaultDataKey }, options: { _height: 100, backgroundColor: "lightGray", links: "@links(self)" } }], + [DocumentType.REC, { + layout: { view: VideoBox, dataField: defaultDataKey }, + options: { _height: 100, backgroundColor: "pink", links: "@links(self)" } + }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: defaultDataKey }, options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } @@ -468,7 +473,7 @@ export namespace Docs { options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: "@links(self)" }, }], [DocumentType.WEBCAM, { - layout: { view: DashWebRTCVideo, dataField: defaultDataKey }, + layout: { view: RecordingBox, dataField: defaultDataKey }, options: { links: "@links(self)" } }], [DocumentType.PRESELEMENT, { @@ -699,6 +704,10 @@ export namespace Docs { { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }); } + export function RecordingDocument(url: string, options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.REC), "", options); + } + export function SearchDocument(options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.SEARCH), new List([]), options); } @@ -1160,6 +1169,9 @@ export namespace DocUtils { } else if (field instanceof AudioField) { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; + } else if (field instanceof RecordingField) { + created = Docs.Create.RecordingDocument((field).url.href, resolved); + layout = RecordingBox.LayoutString; } else if (field instanceof InkField) { created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); layout = InkingStroke.LayoutString; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index af731ce9f..edb362a37 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -424,8 +424,8 @@ export class CurrentUserUtils { doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, title: "empty screenshot", _width: 400, _height: 200, system: true, cloneFieldFilter: new List(["system"]) }); } if (doc.emptyWall === undefined) { - doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List(["system"]) }); - (doc.emptyWall as Doc).videoWall = true; + doc.emptyWall = Docs.Create.WebCamDocument("", { _width: 400, _height: 200, title: "recording", system: true, cloneFieldFilter: new List(["system"]) }); + (doc.emptyWall as Doc).recording = true; } if (doc.emptyAudio === undefined) { doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List(["system"]) }); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 005133eb0..da9b448ec 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -41,6 +41,7 @@ import { WebBox } from "./WebBox"; import React = require("react"); import XRegExp = require("xregexp"); import { MapBox } from "./MapBox/MapBox"; +import { RecordingBox } from "./RecordingBox"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -225,7 +226,7 @@ export class DocumentContentsView extends React.Component void +} + +export function ProgressBar(props: ProgressBarProps) { + + const handleClick = (e: React.MouseEvent) => { + let progressbar = document.getElementById('progressbar')! + let bounds = progressbar!.getBoundingClientRect(); + let x = e.clientX - bounds.left; + let percent = x / progressbar.clientWidth * 100 + + for (let i = 0; i < props.marks.length; i++) { + let start = i == 0 ? 0 : props.marks[i-1]; + if (percent > start && percent < props.marks[i]) { + props.playSegment(i) + // console.log(i) + // console.log(percent) + // console.log(props.marks[i]) + break + } + } + } + + return( +
+
+ {props.marks.map((mark) => { + return
+ })} +
+ ) +} \ No newline at end of file diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx new file mode 100644 index 000000000..6d444d324 --- /dev/null +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -0,0 +1,24 @@ +import { observer } from "mobx-react"; +import * as React from "react"; +import { ViewBoxBaseComponent } from "../../DocComponent"; +import { FieldView } from "../FieldView"; +import { RecordingView } from './RecordingView'; + + +@observer +export class RecordingBox extends ViewBoxBaseComponent(){ + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); } + + private _ref: React.RefObject = React.createRef(); + + constructor(props: any) { + super(props); + } + + render() { + return
+ +
; + } +} diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss new file mode 100644 index 000000000..2eaf5468d --- /dev/null +++ b/src/client/views/nodes/RecordingBox/RecordingView.scss @@ -0,0 +1,207 @@ +video { + flex: 100%; + width: 100%; + min-height: 400px; + height: auto; + display: block; + background-color: black; +} + +button { margin: 0 .5rem } + +.recording-container { + height: 100%; + width: 100%; + display: flex; +} + +.video-wrapper { + max-width: 600px; + max-width: 700px; + position: relative; + display: flex; + justify-content: center; + overflow: hidden; + border-radius: 10px; +} + +.video-wrapper:hover .controls { + bottom: 30px; + transform: translateY(0%); + opacity: 100%; +} + +.controls { + display: flex; + align-items: center; + justify-content: space-evenly; + position: absolute; + padding: 14px; + width: 100%; + max-width: 500px; + flex-wrap: wrap; + background: rgba(255, 255, 255, 0.25); + box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1); + backdrop-filter: blur(4px); + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.18); + // transform: translateY(150%); + transition: all 0.3s ease-in-out; + // opacity: 0%; + bottom: 30px; + // bottom: -150px; +} + +.actions button { + background: none; + border: none; + outline: none; + cursor: pointer; +} + +.actions button i { + background-color: none; + color: white; + font-size: 30px; +} + +// input[type="range"] { +// -webkit-appearance: none !important; +// background: rgba(255, 255, 255, 0.2); +// border-radius: 20px; +// height: 4px; +// width: 350px; +// } + +// input[type="range"]::-webkit-slider-thumb { +// -webkit-appearance: none !important; +// cursor: pointer; +// height: 6px; +// } + +// input[type="range"]::-moz-range-progress { +// background: white; +// } + +.velocity { + appearance: none; + background: none; + color: white; + outline: none; + border: none; + text-align: center; + font-size: 16px; +} + +.mute-btn { + background: none; + border: none; + outline: none; + cursor: pointer; +} + +.mute-btn i { + background-color: none; + color: white; + font-size: 20px; +} + +.recording-sign { + height: 20px; + width: auto; + display: flex; + flex-direction: row; + position: absolute; + top: 10px; + right: 15px; + align-items: center; + justify-content: center; + + .timer { + font-size: 15px; + color: white; + margin: 0; + } + + .dot { + height: 15px; + width: 15px; + margin: 5px; + background-color: red; + border-radius: 50%; + display: inline-block; + } +} + +.controls-inner-container { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + +} + +.record-button-wrapper { + width: 35px; + height: 35px; + font-size: 0; + background-color: grey; + border: 0px; + border-radius: 35px; + margin: 10px; + + .record-button { + position: relative; + background-color: red; + border: 0px; + border-radius: 50%; + height: 28px; + width: 28px; + top: 50%; + left: 50%; + margin: -14px 0px 0px -14px; + + &:hover { + width: 30px; + height: 30px; + margin: -15px 0px 0px -15px; + } + } + + .stop-button{ + position: relative; + background-color: red; + border: 0px; + border-radius: 10%; + height: 18px; + width: 18px; + top: 50%; + left: 50%; + margin: -9px 0px 0px -9px; + + // &:hover { + // width: 40px; + // height: 40px + // } + } + +} + +.video-edit-wrapper { + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + position: absolute; + top: 0; + bottom: 0; + right: 50% - 15; + + .video-edit-buttons { + margin: 0 5px; + } + +} + + + diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx new file mode 100644 index 000000000..15f8c8626 --- /dev/null +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -0,0 +1,326 @@ +import * as React from 'react'; +import "./RecordingView.scss"; +import { ReactElement, useCallback, useEffect, useRef, useState } from "react"; +import { ProgressBar } from "./ProgressBar" +import { MdBackspace } from 'react-icons/md'; +import { FaCheckCircle } from 'react-icons/fa'; +import { IconContext } from "react-icons"; + + +enum RecordingStatus { + Recording, + Stopped, + Paused +} + +interface VideoSegment { + chunks: any[], + endTime: number +} + +const MAXTIME = 1000; + +export function RecordingView() { + + const [recording, setRecording] = useState(false); + const recordingTimerRef = useRef(0); + const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second + const [playing, setPlaying] = useState(false); + const [progress, setProgress] = useState(0); + const [speed, setSpeed] = useState(1); + const [muted, setMuted] = useState(false); + + const [videos, setVideos] = useState([]); + // const [videos, setVideos] = useState([]); + const [currentVid, setCurrentVid] = useState(0); + const recorder = useRef(null); + const videoElementRef = useRef(null); + + const [finished, setFinished] = useState(false) + + + + const DEFAULT_MEDIA_CONSTRAINTS = { + video: { + width: 1280, + height: 720, + }, + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 44100 + } + } + + useEffect(() => { + + if (finished) { + let allVideoChunks : any = [] + console.log(videos) + videos.forEach((vid) => { + console.log(vid.chunks) + allVideoChunks = allVideoChunks.concat(vid.chunks) + }) + + console.log(allVideoChunks) + + const blob = new Blob(allVideoChunks, { + type: 'video/webm' + }) + const blobUrl = URL.createObjectURL(blob) + + videoElementRef.current!.srcObject = null + videoElementRef.current!.src = blobUrl + videoElementRef.current!.muted = false + } + + + }, [finished]) + + useEffect(() => { + // check if the browser supports media devices on first load + if (!navigator.mediaDevices) { + console.log('This browser does not support getUserMedia.') + } + console.log('This device has the correct media devices.') + }, []) + + useEffect(() => { + // get access to the video element on every render + // videoElement = document.getElementById('video') as HTMLVideoElement; + videoElementRef.current = document.getElementById('video') as HTMLVideoElement; + }) + + // useEffect(() => { + // if (playing) { + // videoElement!.srcObject = null + // // videoElement!.src = videos[currentVid].url + // videoElement!.muted = false + // videoElement!.play() + // } else { + // videoElement!.pause(); + // } + // }, [playing, videoElement]); + + useEffect(() => { + let interval: any = null; + if (recording) { + interval = setInterval(() => { + setRecordingTimer(unit => unit + 1); + }, 10); + } else if (!recording && recordingTimer !== 0) { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [recording]) + + useEffect(() => { + setVideoProgressHelper(recordingTimer) + recordingTimerRef.current = recordingTimer; + }, [recordingTimer]) + + const setVideoProgressHelper = (progress: number) => { + const newProgress = (progress / MAXTIME) * 100; + setProgress(newProgress) + } + const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => { + const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints) + + videoElementRef.current!.src = "" + videoElementRef.current!.srcObject = stream + videoElementRef.current!.muted = true + + return stream + } + + const record = async () => { + const stream = await startShowingStream(); + recorder.current = new MediaRecorder(stream) + + // temporary chunks of video + let chunks: any = [] + recorder.current.ondataavailable = (event: any) => { + // store the video chunks as it is recording + console.log("data available") + if (event.data.size > 0) { + chunks.push(event.data) + } + } + + recorder.current.onstart = (event: any) => { + console.log("on start") + setRecording(true); + } + + recorder.current.onstop = () => { + // if we have a last portion + if (chunks.length > 1) { + // append the current portion to the video pieces + setVideos(videos => [...videos, {chunks: chunks, endTime: recordingTimerRef.current}]) + } + + // reset the temporary chunks + chunks = [] + setRecording(false); + setFinished(true); + } + + // recording paused + recorder.current.onpause = (event: any) => { + // append the current portion to the video pieces + console.log(chunks) + setVideos(videos => [...videos, {chunks: chunks, endTime: recordingTimerRef.current}]) + + // reset the temporary chunks + chunks = [] + setRecording(false); + } + + recorder.current.onresume = async (event: any) => { + console.log(event) + await startShowingStream(); + setRecording(true); + } + + recorder.current.start(200) + } + + + const stop = () => { + if (recorder.current) { + if (recorder.current.state !== "inactive") { + recorder.current.stop(); + // recorder.current.stream.getTracks().forEach((track: any) => track.stop()) + } + } + } + + const pause = () => { + if (recorder.current) { + if (recorder.current.state === "recording") { + recorder.current.pause(); + } + } + } + + const startOrResume = () => { + console.log('[RecordingView.tsx] startOrResume') + if (!recorder.current || recorder.current.state === "inactive") { + record(); + } else if (recorder.current.state === "paused") { + recorder.current.resume(); + } + } + + const playSegment = (idx: number) => { + console.log(idx) + let currentChunks = videos[idx].chunks + console.log(currentChunks) + + const blob = new Blob(currentChunks, { + type: 'video/webm' + }) + const blobUrl = URL.createObjectURL(blob) + console.log(blobUrl) + + videoElementRef.current!.srcObject = null + videoElementRef.current!.src = blobUrl + videoElementRef.current!.muted = false + } + + const clearPrevious = () => { + const numVideos = videos.length + setRecordingTimer(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) + setVideoProgressHelper(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) + setVideos(videos.filter((_, idx) => idx !== numVideos - 1)); + } + + // const handleVideoProgress = (event: any) => { + // const manualChange = Number(event.target.value); + // videoElement!.currentTime = (videoElement!.duration / 100) * manualChange; + // setProgress(manualChange) + // }; + + // const handleVideoSpeed = (event: any) => { + // const newSpeed = Number(event.target.value); + // videoElement!.playbackRate = speed; + // setSpeed(newSpeed) + // }; + + const handleOnTimeUpdate = () => { + if (playing) { + setVideoProgressHelper(videoElementRef.current!.currentTime) + } + }; + + const millisecondToMinuteSecond = (milliseconds: number) => { + const toTwoDigit = (digit: number) => { + return String(digit).length == 1 ? "0" + digit : digit + } + const minutes = Math.floor(( milliseconds % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); + return toTwoDigit(minutes) + " : " + toTwoDigit(seconds); + } + + + + + useEffect(() => { + console.log(videos.map((elt) => elt.endTime / MAXTIME * 100)) + console.log(videos) + }, [videos]) + + return ( +
+
+
+
) +} \ No newline at end of file diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts new file mode 100644 index 000000000..ff21eaed6 --- /dev/null +++ b/src/client/views/nodes/RecordingBox/index.ts @@ -0,0 +1,2 @@ +export * from './RecordingView' +export * from './RecordingBox' \ No newline at end of file diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 1d4bbaed0..3e7542e46 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -53,6 +53,7 @@ export abstract class URLField extends ObjectField { export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short.ogg"; @scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { } +@scriptingGlobal @Deserializable("recording") export class RecordingField extends URLField { } @scriptingGlobal @Deserializable("image") export class ImageField extends URLField { } @scriptingGlobal @Deserializable("video") export class VideoField extends URLField { } @scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } -- cgit v1.2.3-70-g09d2 From 6c7101d4f69dd79a83a48d04356748213c38a435 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 11 Apr 2022 13:31:33 -0400 Subject: making layout icons work better. --- src/client/documents/Documents.ts | 4 ++- src/client/util/CurrentUserUtils.ts | 15 +++++--- src/client/util/DragManager.ts | 6 ++-- src/client/views/DocumentDecorations.tsx | 20 ++++++++--- src/client/views/InkingStroke.tsx | 6 +++- src/client/views/StyleProvider.tsx | 7 ++-- .../views/collections/CollectionCarouselView.tsx | 4 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 40 ++++++++++++++++++++++ .../collectionFreeForm/MarqueeView.scss | 1 + src/client/views/nodes/DocumentView.tsx | 7 ++-- src/client/views/nodes/ImageBox.tsx | 6 ++-- src/client/views/nodes/LabelBigText.js | 33 +++++++++++++----- src/client/views/nodes/LabelBox.tsx | 5 +-- src/client/views/nodes/WebBox.tsx | 4 +-- src/fields/Doc.ts | 2 +- 15 files changed, 123 insertions(+), 37 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bb60586eb..652f8e7e0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -139,6 +139,8 @@ export class DocumentOptions { _itemIndex?: number; // which item index the carousel viewer is showing _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts _singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document) + _minFontSize?: number; // minimum font size for labelBoxes + _maxFontSize?: number; // maximum font size for labelBoxes _columnWidth?: number; _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden _fontSize?: string; @@ -441,7 +443,7 @@ export namespace Docs { }], [DocumentType.LABEL, { layout: { view: LabelBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } + options: { links: "@links(self)", _singleLine: true } }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index af731ce9f..bc4dbcb2e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -9,7 +9,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; +import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; @@ -292,7 +292,7 @@ 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(documentView)", { documentView: "any" }), system: true + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); // Docs.Create.TextDocument("", { // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }) @@ -304,7 +304,8 @@ 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(documentView)", { documentView: "any" }), system: true + _singleLine: false, _minFontSize: 18, _maxFontSize: 24, + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); @@ -319,14 +320,18 @@ export class CurrentUserUtils { } 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(documentView)", { documentView: "any" }), system: true + title: "data", _width: 50, isTemplateDoc: true, onClick: 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(documentView)", { documentView: "any" }), system: true }); + const iconColView = Docs.Create.ImageDocument("", { title: "icon", _width: 180 / 4, _height: 135 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); + const proto = iconColView.proto as Doc; + proto["icon-nativeWidth"] = 180 / 4; + proto["icon-nativeHeight"] = 135 / 4; + proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } if (doc["template-icons"] === undefined) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 411fc6d11..d6d04db9a 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -380,8 +380,8 @@ export namespace DragManager { } } const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / ele.offsetWidth; - const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX; + const scaleX = rect.width / (ele.offsetWidth || rect.width); + const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX; elesCont.left = Math.min(rect.left, elesCont.left); elesCont.top = Math.min(rect.top, elesCont.top); @@ -424,7 +424,7 @@ export namespace DragManager { const hideDragShowOriginalElements = (hide: boolean) => { dragLabel.style.display = hide ? "" : "none"; - !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); + //!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); eles.forEach(ele => ele.hidden = hide); }; options?.hideSource && hideDragShowOriginalElements(true); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d7ead713a..1487d5bb0 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -144,10 +144,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return true; } + _iconifyTimeout: NodeJS.Timeout | undefined; onCloseClick = () => { - const selected = SelectionManager.Views().slice(); - SelectionManager.DeselectAll(); - selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + const views = SelectionManager.Views().slice().filter(v => v); + const icons = this._iconifyTimeout ? views : views.filter(view => view.rootDoc.layoutKey === "layout_icon"); + const others = this._iconifyTimeout ? [] : views.filter(view => view.rootDoc.layoutKey !== "layout_icon"); + icons.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); + others.forEach(dv => dv.iconify()); + others.length && (this._iconifyTimeout = setTimeout(() => { + views.forEach(view => SelectionManager.DeselectView(view)); + this._iconifyTimeout = undefined; + }, 1000)); } onMaximizeDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, () => { @@ -171,7 +178,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P } else if (e.altKey) { // open same document in new tab CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right"); } else { - LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document)); + var openDoc = selectedDocs[0].props.Document; + if (openDoc.layoutKey === "layout_icon") { + openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc); + Doc.deiconifyView(openDoc); + } + LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document)); } } SelectionManager.DeselectAll(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 06671961d..5e589fdea 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -70,7 +70,11 @@ export class InkingStroke extends ViewBoxBaseComponent() { // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc) - screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1); + screenToLocal = () => { + console.log("Scaling = " + this.props.scaling?.()) + console.log("Stolocal = " + this.props.ScreenToLocalTransform().Scale) + return this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1); + } getAnchor = () => { console.log(document.activeElement); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 649ee8394..739670ecf 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -117,7 +117,10 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + "backgroundColor"], StrCast(doc?._backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : "")); + let docColor: Opt = + StrCast(doc?.[fieldKey + "backgroundColor"], + StrCast(doc?._backgroundColor, + StrCast(props?.Document.backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : ""))); switch (doc?.type) { case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break; case DocumentType.PRES: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; @@ -127,7 +130,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt
- { + if (this.clicks) { + console.log("UPDATE ICON"); + } + this.clicks++; + this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString(); + this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString(); + var htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); + this.props.docViewPath().lastElement().ContentDiv!.style.width = ""; + this.props.docViewPath().lastElement().ContentDiv!.style.height = ""; + const nativeWidth = this.layoutDoc[WidthSym](); + const nativeHeight = this.layoutDoc[HeightSym](); + + CreateImage( + "", + document.styleSheets, + htmlString?.replace(/"marqueeView"/g, '"marqueeView marqueeView2"'), + nativeWidth, + nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), + NumCast(this.layoutDoc._scrollTop) + ).then + ((data_url: any) => { + VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then( + returnedfilename => setTimeout(action(() => { + + this.dataDoc.icon = new ImageField(returnedfilename); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }), 500)); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + } + componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); @@ -1476,6 +1515,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 62510ce9d..41e4d6b6a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -10,6 +10,7 @@ user-select: none; } + .marqueeView:focus-within { overflow: hidden; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 103c3624d..185eafa4a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -80,6 +80,7 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { + updateIcon?: () => void; // updates the icon representation of the document getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, smooth: boolean) => Opt; // returns the duration of the focus setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document @@ -1118,6 +1119,7 @@ export class DocumentViewInternal extends DocComponent !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)} onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)} style={{ + display: "inline", borderRadius: this.borderRounding, pointerEvents: this.pointerEvents, outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px", @@ -1233,6 +1235,7 @@ export class DocumentView extends React.Component { } public iconify() { + this.ComponentView?.updateIcon?.(); const layoutKey = Cast(this.Document.layoutKey, "string", null); if (layoutKey !== "layout_icon") { this.switchViews(true, "icon"); @@ -1332,8 +1335,8 @@ export function deiconifyViewFunc(documentView: DocumentView) { } ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { documentView.iconify(); -} -); + documentView.select(false); +}); ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 17f95c1cc..fb9e86487 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -73,7 +73,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o", + ({ forceFull, scrSize, selected }) => this._curSuffix = this.fieldKey === "icon" ? "_m" : forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o", { fireImmediately: true, delay: 1000 }); this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { @@ -245,8 +245,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent options.maximumFontSize) { + if (fontSize < options.minimumFontSize) { + parentStyle.display = "flex"; + parentStyle.alignItems = "center"; + style.whiteSpace = "pre-wrap"; + style.textAlign = "center"; + style.visibility = ""; + style.fontSize = "18px"; + style.lineHeight = "20px"; + style.top = ""; + style.left = ""; + style.margin = ""; + return element; + } + if (options.maximumFontSize && fontSize > options.maximumFontSize) { fontSize = options.maximumFontSize; lineHeight = fontSize / options.fontSizeFactor; } diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 0015f0b71..c689d9f40 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; -import { Cast, StrCast } from '../../../fields/Types'; +import { Cast, StrCast, NumCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; @@ -100,7 +100,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro BigText(r, { rotateText: null, fontSizeFactor: 1, - maximumFontSize: null, + minimumFontSize: NumCast(this.layoutDoc._minFontSize), + maximumFontSize: NumCast(this.layoutDoc._maxFontSize), limitingDimension: "both", horizontalAlign: "center", verticalAlign: "center", diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index c12059943..0fd193977 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -130,8 +130,8 @@ export class WebBox extends ViewBoxAnnotatableComponent Date: Mon, 11 Apr 2022 16:06:15 -0400 Subject: lots of layout fixes to groups, labels, ink to support iconification better. simpliifed documentdecorations. fixed display artifacts related to things not showing up when dragging, or otherwise not getting a halo of nested freeform colletions. --- src/client/util/CurrentUserUtils.ts | 8 +- src/client/util/DragManager.ts | 2 +- src/client/views/DocumentDecorations.scss | 496 ++++++++++----------- src/client/views/DocumentDecorations.tsx | 10 +- src/client/views/InkingStroke.tsx | 6 +- src/client/views/StyleProvider.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 11 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +- src/client/views/nodes/DocumentIcon.tsx | 1 + src/client/views/nodes/DocumentView.scss | 3 + src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/LabelBigText.js | 1 + src/client/views/nodes/LabelBox.scss | 2 +- src/client/views/nodes/LabelBox.tsx | 31 +- 14 files changed, 275 insertions(+), 315 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index bc4dbcb2e..6d8e2d30c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -320,17 +320,17 @@ export class CurrentUserUtils { } 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, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + title: "data", _width: 150, isTemplateDoc: true, onClick: 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.ImageDocument("", { title: "icon", _width: 180 / 4, _height: 135 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + const iconColView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); const proto = iconColView.proto as Doc; - proto["icon-nativeWidth"] = 180 / 4; - proto["icon-nativeHeight"] = 135 / 4; + proto["icon-nativeWidth"] = 360 / 4; + proto["icon-nativeHeight"] = 270 / 4; proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d6d04db9a..94a09eac4 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -424,7 +424,7 @@ export namespace DragManager { const hideDragShowOriginalElements = (hide: boolean) => { dragLabel.style.display = hide ? "" : "none"; - //!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); + !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); eles.forEach(ele => ele.hidden = hide); }; options?.hideSource && hideDragShowOriginalElements(true); diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 82dca1287..35e37a2cd 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -4,8 +4,8 @@ $linkGap: 3px; .documentDecorations-Dark, .documentDecorations { - position: absolute; - z-index: 2000; + position: absolute; + z-index: 2000; } .documentDecorations-Dark { background: dimgray; @@ -17,11 +17,11 @@ $linkGap: 3px; left: 0; display: grid; grid-template-rows: 20px 8px 1fr 8px; - grid-template-columns: 8px 16px 1fr 8px 8px; + grid-template-columns: 8px 1fr 8px; pointer-events: none; .documentDecorations-centerCont { - grid-column: 3; + grid-column: 2; background: none; } @@ -41,6 +41,7 @@ $linkGap: 3px; opacity: 1; transform: translate(10px, 10px); grid-row: 4; + grid-column: 3 } .documentDecorations-topLeftResizer, @@ -73,20 +74,18 @@ $linkGap: 3px; .documentDecorations-topResizer, .documentDecorations-bottomResizer { - grid-column-start: 2; - grid-column-end: 5; + grid-column: 2; } .documentDecorations-bottomRightResizer, .documentDecorations-topRightResizer, .documentDecorations-rightResizer { - grid-column-start: 5; - grid-column-end: 7; + grid-column: 3; } .documentDecorations-rotation, .documentDecorations-borderRadius { - grid-column: 5; + grid-column: 3; grid-row: 4; border-radius: 100%; background: black; @@ -132,114 +131,113 @@ $linkGap: 3px; opacity: 1; } - .documentDecorations-topLeftResizer, - .documentDecorations-leftResizer, - .documentDecorations-bottomLeftResizer { - grid-column: 1; - } - - .documentDecorations-topResizer, - .documentDecorations-bottomResizer { - grid-column-start: 2; - grid-column-end: 5; - } - - .documentDecorations-bottomRightResizer, - .documentDecorations-topRightResizer, - .documentDecorations-rightResizer { - grid-column-start: 5; - grid-column-end: 7; - } - - .documentDecorations-rotation, - .documentDecorations-borderRadius { - grid-column: 5; - grid-row: 4; - border-radius: 100%; - background: black; - height: 8; - right: -12; - top: 12; - position: relative; - pointer-events: all; - cursor: nwse-resize; - - .borderRadiusTooltip { - width: 10px; - height: 10px; - position: absolute; - } - } - .documentDecorations-rotation { - background: transparent; - right: -15; - } - - .documentDecorations-topLeftResizer, - .documentDecorations-bottomRightResizer { - cursor: nwse-resize; - background: unset; - opacity: 1; - } + .documentDecorations-topLeftResizer, + .documentDecorations-leftResizer, + .documentDecorations-bottomLeftResizer { + grid-column: 1; + } - .documentDecorations-topLeftResizer { - border-left: 2px solid; - border-top: solid 2px; - } + .documentDecorations-topResizer, + .documentDecorations-bottomResizer { + grid-column: 2; + } - .documentDecorations-bottomRightResizer { - border-right: 2px solid; - border-bottom: solid 2px; - } + .documentDecorations-bottomRightResizer, + .documentDecorations-topRightResizer, + .documentDecorations-rightResizer { + grid-column: 3 + } - .documentDecorations-topLeftResizer:hover, - .documentDecorations-bottomRightResizer:hover { - opacity: 1; - } + .documentDecorations-rotation, + .documentDecorations-borderRadius { + grid-column: 3; + grid-row: 4; + border-radius: 100%; + background: black; + height: 8; + right: -12; + top: 12; + position: relative; + pointer-events: all; + cursor: nwse-resize; - .documentDecorations-bottomRightResizer { - grid-row: 4; - } + .borderRadiusTooltip { + width: 10px; + height: 10px; + position: absolute; + } + } + .documentDecorations-rotation { + background: transparent; + right: -15; + } - .documentDecorations-topRightResizer, - .documentDecorations-bottomLeftResizer { - cursor: nesw-resize; - background: unset; - opacity: 1; - } + .documentDecorations-topLeftResizer, + .documentDecorations-bottomRightResizer { + cursor: nwse-resize; + background: unset; + opacity: 1; + } + + .documentDecorations-topLeftResizer { + border-left: 2px solid; + border-top: solid 2px; + } + + .documentDecorations-bottomRightResizer { + border-right: 2px solid; + border-bottom: solid 2px; + } + + .documentDecorations-topLeftResizer:hover, + .documentDecorations-bottomRightResizer:hover { + opacity: 1; + } + + .documentDecorations-bottomRightResizer { + grid-row: 4; + } + + .documentDecorations-topRightResizer, + .documentDecorations-bottomLeftResizer { + cursor: nesw-resize; + background: unset; + opacity: 1; + } - .documentDecorations-topRightResizer { - border-right: 2px solid; - border-top: 2px solid; - } + .documentDecorations-topRightResizer { + border-right: 2px solid; + border-top: 2px solid; + } - .documentDecorations-bottomLeftResizer { - border-left: 2px solid; - border-bottom: 2px solid; - } + .documentDecorations-bottomLeftResizer { + border-left: 2px solid; + border-bottom: 2px solid; + } - .documentDecorations-topRightResizer:hover, - .documentDecorations-bottomLeftResizer:hover { +.documentDecorations-topRightResizer:hover, +.documentDecorations-bottomLeftResizer:hover { cursor: nesw-resize; background: black; opacity: 1; - } +} - .documentDecorations-topResizer, - .documentDecorations-bottomResizer { +.documentDecorations-topResizer, +.documentDecorations-bottomResizer { cursor: ns-resize; - } +} - .documentDecorations-title-Dark, - .documentDecorations-title { +.documentDecorations-title-Dark, +.documentDecorations-title { opacity: 1; - grid-column-start: 2; - grid-column-end: 4; + width: 100%; + grid-column: 2; pointer-events: auto; overflow: hidden; text-align: center; display: flex; - margin-left: 5px; + padding-left: 5px; + padding-right: 12px; height: 20px; position: absolute; border-radius: 8px; @@ -247,7 +245,7 @@ $linkGap: 3px; .documentDecorations-titleSpan, .documentDecorations-titleSpan-Dark { - width: 100%; + width: calc(100% - 17px); // = padding-left + padding-right border-radius: 8px; background: #ffffffa0; position: absolute; @@ -263,105 +261,61 @@ $linkGap: 3px; background: black; } - .documentDecorations-contextMenu { - width: 25px; - height: calc(100% + 8px); // 8px for the height of the top resizer bar - grid-column-start: 2; - grid-column-end: 2; - pointer-events: all; - padding-left: 5px; - cursor: pointer; - } - - .documentDecorations-titleBackground { - background: #ffffffcf; - border-radius: 8px; - width: 100%; - height: 100%; - position: absolute; - } - - .documentDecorations-title { - opacity: 1; - grid-column-start: 2; - grid-column-end: 4; - pointer-events: auto; - overflow: hidden; - text-align: center; - display: flex; - margin-left: 5px; - height: 20px; - position: absolute; - .documentDecorations-titleSpan { - width: 100%; - border-radius: 8px; - background: #ffffffcf; - position: absolute; - display: inline-block; - cursor: move; - } - } - - .focus-visible { - margin-left: 0px; - } -} + .documentDecorations-titleBackground { + background: #ffffffcf; + border-radius: 8px; + width: 100%; + height: 100%; + position: absolute; + } -.documentDecorations-iconifyButton { - opacity: 1; - grid-column-start: 4; - grid-column-end: 4; - pointer-events: all; - right: 0; - cursor: pointer; - position: absolute; - width: 20px; + .focus-visible { + margin-left: 0px; + } } .documentDecorations-openButton { - display: flex; - align-items: center; - opacity: 1; - grid-column-start: 5; - grid-column-end: 5; - pointer-events: all; - cursor: pointer; + display: flex; + align-items: center; + opacity: 1; + grid-column-start: 3; + pointer-events: all; + cursor: pointer; } .documentDecorations-closeButton { - display: flex; - align-items: center; - opacity: 1; - grid-column-start: 1; - grid-column-end: 3; - pointer-events: all; - cursor: pointer; - - > svg { - margin: 0; - } + display: flex; + align-items: center; + opacity: 1; + grid-column: 1; + pointer-events: all; + cursor: pointer; + + > svg { + margin: 0; + } } .documentDecorations-background { - background: lightblue; - position: absolute; - opacity: 0.1; + background: lightblue; + position: absolute; + opacity: 0.1; } .linkFlyout { - grid-column: 2/4; + grid-column: 2/4; } .linkButton-empty:hover { - background: $medium-gray; - transform: scale(1.05); - cursor: pointer; + background: $medium-gray; + transform: scale(1.05); + cursor: pointer; } .linkButton-nonempty:hover { - background: $medium-gray; - transform: scale(1.05); - cursor: pointer; + background: $medium-gray; + transform: scale(1.05); + cursor: pointer; } .link-button-container { @@ -379,132 +333,132 @@ $linkGap: 3px; } .linkButtonWrapper { - pointer-events: auto; - padding-right: 5px; - width: 25px; + pointer-events: auto; + padding-right: 5px; + width: 25px; } .linkButton-linker { - height: 20px; - width: 20px; - text-align: center; - border-radius: 50%; - pointer-events: auto; - color: $dark-gray; - border: $dark-gray 1px solid; + height: 20px; + width: 20px; + text-align: center; + border-radius: 50%; + pointer-events: auto; + color: $dark-gray; + border: $dark-gray 1px solid; } .linkButton-linker:hover { - cursor: pointer; - transform: scale(1.05); + cursor: pointer; + transform: scale(1.05); } .linkButton-empty, .linkButton-nonempty { - height: 20px; - width: 20px; - border-radius: 50%; - opacity: 0.9; - pointer-events: auto; - background-color: $dark-gray; - color: $white; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 75%; - transition: transform 0.2s; - text-align: center; - display: flex; - justify-content: center; - align-items: center; - - &:hover { - background: $medium-gray; - transform: scale(1.05); - cursor: pointer; - } + height: 20px; + width: 20px; + border-radius: 50%; + opacity: 0.9; + pointer-events: auto; + background-color: $dark-gray; + color: $white; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + + &:hover { + background: $medium-gray; + transform: scale(1.05); + cursor: pointer; + } } .templating-menu { - position: absolute; - pointer-events: auto; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 75%; - transition: transform 0.2s; - text-align: center; - display: flex; - justify-content: center; - align-items: center; + position: absolute; + pointer-events: auto; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; } .documentdecorations-icon { - margin: 0px; + margin: 0px; } .templating-button, .docDecs-tagButton { - width: 20px; - height: 20px; - border-radius: 50%; - opacity: 0.9; - font-size: 14; - background-color: $dark-gray; - color: $white; - text-align: center; - cursor: pointer; - - &:hover { - background: $medium-gray; - transform: scale(1.05); - } + width: 20px; + height: 20px; + border-radius: 50%; + opacity: 0.9; + font-size: 14; + background-color: $dark-gray; + color: $white; + text-align: center; + cursor: pointer; + + &:hover { + background: $medium-gray; + transform: scale(1.05); + } } #template-list { - position: absolute; - top: 25px; - left: 0px; - width: max-content; - font-family: $sans-serif; - font-size: 12px; - background-color: $light-gray; - padding: 2px 12px; - list-style: none; - - .templateToggle, - .chromeToggle { - text-align: left; - } - - input { - margin-right: 10px; - } + position: absolute; + top: 25px; + left: 0px; + width: max-content; + font-family: $sans-serif; + font-size: 12px; + background-color: $light-gray; + padding: 2px 12px; + list-style: none; + + .templateToggle, + .chromeToggle { + text-align: left; + } + + input { + margin-right: 10px; + } } @-moz-keyframes spin { - 100% { - -moz-transform: rotate(360deg); - } + 100% { + -moz-transform: rotate(360deg); + } } @-webkit-keyframes spin { - 100% { - -webkit-transform: rotate(360deg); - } + 100% { + -webkit-transform: rotate(360deg); + } } @keyframes spin { - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } } @keyframes shadow-pulse { - 0% { - box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8); - } + 0% { + box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8); + } - 100% { - box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); - } + 100% { + box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); + } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 1487d5bb0..353843b8d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -449,7 +449,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } - const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles; + const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc.isGroup; const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle; const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton); const canDelete = SelectionManager.Views().some(docView => { @@ -466,16 +466,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P ); const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); - const titleArea = hideTitle ?
: + const titleArea = hideTitle ?
: this._edtingTitle ? this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> : -
+
{`${this.selectionTitle}`}
; @@ -515,8 +515,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} {hideResizers ? (null) : <> - {SelectionManager.Views().length !== 1 || hideTitle ? (null) : - topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)}
e.preventDefault()} />
e.preventDefault()} />
e.preventDefault()} /> diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 5e589fdea..06671961d 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -70,11 +70,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc) - screenToLocal = () => { - console.log("Scaling = " + this.props.scaling?.()) - console.log("Stolocal = " + this.props.ScreenToLocalTransform().Scale) - return this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1); - } + screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1); getAnchor = () => { console.log(document.activeElement); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 739670ecf..eb6551fc8 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -98,7 +98,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || - doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; + doc?.type === DocumentType.RTF || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY; let docColor: Opt = @@ -143,7 +143,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt pair.layout); - docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + const docs = this.childLayoutPairs.map(pair => pair.layout).slice(); + docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1; if (zlast - docs.length > 100) { for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; @@ -1414,12 +1414,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.clicks) { - console.log("UPDATE ICON"); - } - this.clicks++; this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString(); this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString(); var htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); @@ -1431,7 +1426,7 @@ export class CollectionFreeFormView extends CollectionSubView {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} - {!this.props.isSelected() || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : + {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : + Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} /> } {audioView}
; @@ -1111,7 +1111,7 @@ export class DocumentViewInternal extends DocComponent !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)} onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)} style={{ - display: "inline", borderRadius: this.borderRounding, pointerEvents: this.pointerEvents, outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px", @@ -1244,6 +1243,7 @@ export class DocumentView extends React.Component { const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null); this.switchViews(deiconifyLayout ? true : false, deiconifyLayout); this.Document.deiconifyLayout = undefined; + this.props.bringToFront(this.rootDoc); } } @undoBatch diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js index 78fe2839e..02c36c4bc 100644 --- a/src/client/views/nodes/LabelBigText.js +++ b/src/client/views/nodes/LabelBigText.js @@ -156,6 +156,7 @@ export default function BigText(element, options) { width: element.offsetWidth, //Note: This is slightly larger than the jQuery version height: element.offsetHeight, }; + if (!box.width || !box.height) return element; if (options.rotateText !== null) { diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index 6a0d651d2..42e158584 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -4,7 +4,7 @@ border-radius: inherit; display: flex; flex-direction: column; - position: absolute; + position: relative; } .labelBox-mainButton { diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index c689d9f40..3405a7635 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -30,6 +30,11 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro componentDidMount() { this.props.setContentView?.(this); + console.log("MOUNTING") + } + + componentDidUpdate() { + console.log("UPDATING") } getTitle() { @@ -73,6 +78,19 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro @observable _mouseOver = false; @computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; } + fitTextToBox = (r: any) => { + BigText(r, { + rotateText: null, + fontSizeFactor: 1, + minimumFontSize: NumCast(this.layoutDoc._minFontSize), + maximumFontSize: NumCast(this.layoutDoc._maxFontSize), + limitingDimension: "both", + horizontalAlign: "center", + verticalAlign: "center", + textAlign: "center", + whiteSpace: "nowrap" + }) + } // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); @@ -97,17 +115,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro }} > { if (r) { - BigText(r, { - rotateText: null, - fontSizeFactor: 1, - minimumFontSize: NumCast(this.layoutDoc._minFontSize), - maximumFontSize: NumCast(this.layoutDoc._maxFontSize), - limitingDimension: "both", - horizontalAlign: "center", - verticalAlign: "center", - textAlign: "center", - whiteSpace: "nowrap" - }); + if (!r.offsetWidth || !r.offsetHeight) setTimeout(() => this.fitTextToBox(r)); + else this.fitTextToBox(r); } }}>{label.startsWith("#") ? (null) : label}
-- cgit v1.2.3-70-g09d2 From ec4cf9ea0439701fde3aa49e462b054998c657f1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 11 Apr 2022 17:57:37 -0400 Subject: got pdf's to generate icons when iconifying. --- src/client/util/CurrentUserUtils.ts | 20 ++++++++++- src/client/views/DocumentDecorations.scss | 5 +-- src/client/views/nodes/PDFBox.tsx | 57 +++++++++++++++++++++++++++++-- src/client/views/nodes/VideoBox.scss | 1 + src/client/views/nodes/VideoBox.tsx | 20 +++++++---- src/client/views/pdf/PDFViewer.tsx | 2 +- 6 files changed, 92 insertions(+), 13 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 6d8e2d30c..0e392cc85 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -334,9 +334,27 @@ export class CurrentUserUtils { proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } + if (doc["template-icon-view-video"] === undefined) { + const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + iconVidView.isTemplateDoc = makeTemplate(iconVidView, true, "icon_" + DocumentType.VID); + const proto = iconVidView.proto as Doc; + proto["icon-nativeWidth"] = 360 / 4; + proto["icon-nativeHeight"] = 270 / 4; + proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); + doc["template-icon-view-video"] = new PrefetchProxy(iconVidView); + } + if (doc["template-icon-view-pdf"] === undefined) { + const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF); + const proto = iconPdfView.proto as Doc; + proto["icon-nativeWidth"] = 360 / 4; + proto["icon-nativeHeight"] = 270 / 4; + proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); + doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView); + } if (doc["template-icons"] === undefined) { doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); + doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-video"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); } else { const templateIconsDoc = Cast(doc["template-icons"], Doc, null); const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 35e37a2cd..7432d45bf 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -232,12 +232,13 @@ $linkGap: 3px; opacity: 1; width: 100%; grid-column: 2; + grid-column-end: 2; pointer-events: auto; overflow: hidden; text-align: center; display: flex; - padding-left: 5px; - padding-right: 12px; + padding-left: 2px; + padding-right: 2px; height: 20px; position: absolute; border-radius: 8px; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index a2e7d2aa3..3f1771e68 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -2,10 +2,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; +import { CreateImage } from "../nodes/WebBoxRenderer"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types'; -import { PdfField } from "../../../fields/URLField"; +import { ImageField, PdfField } from "../../../fields/URLField"; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; @@ -21,6 +22,8 @@ import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; import "./PDFBox.scss"; import React = require("react"); +import { Id } from '../../../fields/FieldSymbols'; +import { VideoBox } from './VideoBox'; @observer export class PDFBox extends ViewBoxAnnotatableComponent() { @@ -52,6 +55,56 @@ export class PDFBox extends ViewBoxAnnotatableComponent { + if (oldDiv.childNodes) { + for (let i = 0; i < oldDiv.childNodes.length; i++) { + this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); + } + } + if (oldDiv instanceof HTMLCanvasElement) { + const canvas = oldDiv as HTMLCanvasElement; + var img = document.createElement('img'); // create a Image Element + img.src = canvas.toDataURL(); //image source + img.style.width = canvas.style.width; + img.style.height = canvas.style.height; + const newCan = newDiv as HTMLCanvasElement; + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(img); + } + } + + updateIcon = () => { + const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; + newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); + newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + this.replaceCanvases(docViewContent, newDiv) + var htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); + const nativeWidth = this.layoutDoc[WidthSym](); + const nativeHeight = this.layoutDoc[HeightSym](); + + CreateImage( + "", + document.styleSheets, + htmlString?.replace(/docView-hack/g, 'documentView-hack'), + nativeWidth, + nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), + NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"]) + ).then + ((data_url: any) => { + VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then( + returnedfilename => setTimeout(action(() => { + this.dataDoc.icon = new ImageField(returnedfilename); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }), 500)); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + } + componentWillUnmount() { this._selectReactionDisposer?.(); } componentDidMount() { this.props.setContentView?.(this); diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 3cf10a033..f47a71469 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -81,6 +81,7 @@ align-items: center; justify-content: center; display: flex; + width: 100%; visibility: none; opacity: 0; background-color: $dark-gray; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ca8dc8515..b7b8ce064 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,10 +4,10 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from "mobx-react"; import { basename } from "path"; import * as rp from 'request-promise'; -import { Doc, DocListCast } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, WidthSym } from "../../../fields/Doc"; import { InkTool } from "../../../fields/InkField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { AudioField, VideoField } from "../../../fields/URLField"; +import { AudioField, ImageField, VideoField } from "../../../fields/URLField"; import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; @@ -220,16 +220,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent void) => { const width = NumCast(this.layoutDoc._width); const canvas = document.createElement('canvas'); canvas.width = 640; canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1); const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { - // ctx.rect(0, 0, canvas.width, canvas.height); - // ctx.fillStyle = "blue"; - // ctx.fill(); this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height); } @@ -259,10 +256,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent - returnedFilename && this.createRealSummaryLink(returnedFilename, downX, downY)); + returnedFilename && (cb ?? this.createRealSummaryLink)(returnedFilename, downX, downY)); } } + updateIcon = () => { + const makeIcon = (returnedfilename: string) => { + this.dataDoc.icon = new ImageField(returnedfilename); + this.dataDoc["icon-nativeWidth"] = this.layoutDoc[WidthSym](); + this.dataDoc["icon-nativeHeight"] = this.layoutDoc[HeightSym](); + } + this.Snapshot(undefined, undefined, makeIcon); + } + // creates link for snapshot createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => { const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 651b0401b..9aaa6e90f 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -74,7 +74,7 @@ export class PDFViewer extends React.Component { private _annotationLayer: React.RefObject = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewer: React.RefObject = React.createRef(); - private _mainCont: React.RefObject = React.createRef(); + _mainCont: React.RefObject = React.createRef(); private _selectionText: string = ""; private _downX: number = 0; private _downY: number = 0; -- cgit v1.2.3-70-g09d2 From 0f8b556ac88632d31db4218bcea2e7a559deb6e4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 11 Apr 2022 20:11:42 -0400 Subject: fixed sendtoback menu and functionality. fixed mixedblendmode for groups. fixed orange highlighting when docs are toggled hidden. fixed placement of linking button. --- src/client/util/DocumentManager.ts | 5 ++++- src/client/views/StyleProvider.tsx | 11 ++++++----- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 10 ++++------ src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 ++-- 5 files changed, 17 insertions(+), 15 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ad6d90bc3..6076de055 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -169,7 +169,10 @@ export class DocumentManager { const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; const docView = getFirstDocView(targetDoc, originatingDoc); const wasHidden = targetDoc.hidden; // - if (wasHidden) runInAction(() => targetDoc.hidden = false); // if the target is hidden, un-hide it here. + if (wasHidden) runInAction(() => { + targetDoc.hidden = false; + docView?.props.bringToFront(targetDoc); + }); // if the target is hidden, un-hide it here. const focusAndFinish = (didFocus: boolean) => { if (originatingDoc?.isPushpin) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index eb6551fc8..37ef96782 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -143,13 +143,14 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : - Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE)))); + Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE)) + )); break; //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index ba34d846e..277d6dc53 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -166,11 +166,9 @@ export class CollectionFreeFormDocumentView extends DocComponent {this.props.renderCutoffProvider(this.props.Document) ? diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 6d96a9ab6..46537df7e 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -278,7 +278,7 @@ export class DocumentLinksButton extends React.Component +
{ (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 423a5edbf..9ec7b9a58 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -752,7 +752,7 @@ export class DocumentViewInternal extends DocComponent SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" }); zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" }); } - !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" }); + !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" }); !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" }); @@ -1119,6 +1119,7 @@ export class DocumentViewInternal extends DocComponent !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)} onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)} style={{ + display: this.hidden ? "inline" : undefined, borderRadius: this.borderRounding, pointerEvents: this.pointerEvents, outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px", @@ -1322,7 +1323,6 @@ export class DocumentView extends React.Component { ContentScaling={this.ContentScale} ScreenToLocalTransform={this.screenToLocalTransform} focus={this.props.focus || emptyFunction} - bringToFront={emptyFunction} ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} />
)}
); -- cgit v1.2.3-70-g09d2 From 7cd890392f79f1783af2bdb0fe86fa6a49db849a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 11 Apr 2022 23:17:07 -0400 Subject: fixes to header to be transparent for icons. layout fixes for doc decoratoins title row. groups are not semi transnparent anymore to avoid mixBlendMode confusion --- src/client/util/CurrentUserUtils.ts | 13 +++++-------- src/client/views/DocumentDecorations.scss | 9 +++++---- src/client/views/DocumentDecorations.tsx | 3 +-- src/client/views/StyleProvider.tsx | 2 +- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 5 +++-- src/client/views/nodes/DocumentView.tsx | 6 ++++-- 6 files changed, 19 insertions(+), 19 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 0e392cc85..bf722157a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -303,7 +303,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"), + title: "icon_" + DocumentType.RTF, _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), _singleLine: false, _minFontSize: 18, _maxFontSize: 24, _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); @@ -320,13 +320,13 @@ export class CurrentUserUtils { } if (doc["template-icon-view-img"] === undefined) { const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 150, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + title: "data", _width: 150, isTemplateDoc: true, _showTitle: "title", onClick: 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.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + const iconColView = Docs.Create.ImageDocument("", { title: "icon", _showTitle: "title", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); const proto = iconColView.proto as Doc; proto["icon-nativeWidth"] = 360 / 4; @@ -335,7 +335,7 @@ export class CurrentUserUtils { doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } if (doc["template-icon-view-video"] === undefined) { - const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconVidView.isTemplateDoc = makeTemplate(iconVidView, true, "icon_" + DocumentType.VID); const proto = iconVidView.proto as Doc; proto["icon-nativeWidth"] = 360 / 4; @@ -344,7 +344,7 @@ export class CurrentUserUtils { doc["template-icon-view-video"] = new PrefetchProxy(iconVidView); } if (doc["template-icon-view-pdf"] === undefined) { - const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF); const proto = iconPdfView.proto as Doc; proto["icon-nativeWidth"] = 360 / 4; @@ -1344,9 +1344,6 @@ export class CurrentUserUtils { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } - if (doc.activeCollectionBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default. - doc.activeCollectionBackground = undefined; - } return doc; } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 7432d45bf..481b90249 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -230,15 +230,15 @@ $linkGap: 3px; .documentDecorations-title-Dark, .documentDecorations-title { opacity: 1; - width: 100%; + width: calc(100% - 8px); // = margin-left + margin-right grid-column: 2; grid-column-end: 2; pointer-events: auto; overflow: hidden; text-align: center; display: flex; - padding-left: 2px; - padding-right: 2px; + margin-left: 6px; // closeButton width (14) - leftColumn width (8) + margin-right: 2px; height: 20px; position: absolute; border-radius: 8px; @@ -246,7 +246,7 @@ $linkGap: 3px; .documentDecorations-titleSpan, .documentDecorations-titleSpan-Dark { - width: calc(100% - 17px); // = padding-left + padding-right + width: 100% ; border-radius: 8px; background: #ffffffa0; position: absolute; @@ -290,6 +290,7 @@ $linkGap: 3px; opacity: 1; grid-column: 1; pointer-events: all; + width: 14px; cursor: pointer; > svg { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 353843b8d..9d9505c08 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -469,13 +469,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P const titleArea = hideTitle ?
: this._edtingTitle ? this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> : -
+
{`${this.selectionTitle}`}
; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 37ef96782..2ce78f64f 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -146,7 +146,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE)) diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 277d6dc53..7fb2c235a 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -18,6 +18,8 @@ import "./CollectionFreeFormDocumentView.scss"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); import { DocumentType } from "../../documents/DocumentTypes"; +import { collectionSchema } from "../../../fields/documentSchemas"; +import { CollectionViewType } from "../collections/CollectionView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; @@ -167,8 +169,7 @@ export class CollectionFreeFormDocumentView extends DocComponent
; + return
{!this._retryThumb || !this.thumbShown() ? (null) : @@ -1030,7 +1031,8 @@ export class DocumentViewInternal extends DocComponent
; const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc); - const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"); + const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, + Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"); const titleView = !showTitle ? (null) :
Date: Tue, 12 Apr 2022 10:54:28 -0400 Subject: cleaned up errors/warnings. fixed dragging to turn off pointer events on whatever's being dragged. --- src/client/util/DocumentManager.ts | 10 ++++++---- src/client/util/DragManager.ts | 2 +- src/client/views/StyleProvider.tsx | 4 +++- src/client/views/collections/CollectionDockingView.tsx | 4 ++-- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 4 ++-- src/client/views/nodes/LabelBox.tsx | 7 +------ src/client/views/nodes/PDFBox.tsx | 6 +++--- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 10 files changed, 21 insertions(+), 22 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 6076de055..1d603e18f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -169,10 +169,12 @@ export class DocumentManager { const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; const docView = getFirstDocView(targetDoc, originatingDoc); const wasHidden = targetDoc.hidden; // - if (wasHidden) runInAction(() => { - targetDoc.hidden = false; - docView?.props.bringToFront(targetDoc); - }); // if the target is hidden, un-hide it here. + if (wasHidden) { + runInAction(() => { + targetDoc.hidden = false; + docView?.props.bringToFront(targetDoc); + }); // if the target is hidden, un-hide it here. + } const focusAndFinish = (didFocus: boolean) => { if (originatingDoc?.isPushpin) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 94a09eac4..13e5940d6 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -405,7 +405,7 @@ export namespace DragManager { Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); } [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => - ele.hasAttribute("style") && ((ele as any).style.pointerEvents = "none")); + (ele as any).style && ((ele as any).style.pointerEvents = "none")); dragDiv.appendChild(dragElement); if (dragElement !== ele) { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 2ce78f64f..9803f9782 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -149,7 +149,9 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : - Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE)) + Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? + Colors.BLACK : + "linear-gradient(#065fff, #85c1f9)")) )); break; //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 6931d9896..e95371aaa 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -92,7 +92,7 @@ export class CollectionDockingView extends CollectionSubView() { DragManager.CompleteWindowDrag = undefined; } finishDrag?.(aborted); - } + }; } @undoBatch public CloseFullScreen = () => { @@ -316,7 +316,7 @@ 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('dragStart', this.tabDragStart); this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 04beed541..9b0acffdf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1417,7 +1417,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString(); this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString(); - var htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); + const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); this.props.docViewPath().lastElement().ContentDiv!.style.width = ""; this.props.docViewPath().lastElement().ContentDiv!.style.height = ""; const nativeWidth = this.layoutDoc[WidthSym](); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 7fb2c235a..3e35039e3 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -168,8 +168,8 @@ export class CollectionFreeFormDocumentView extends DocComponent m + ":").join(" ") + ")") render() { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3f1771e68..c73823a2b 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -63,7 +63,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - const title = StrCast(this.dataDoc.title) + const title = StrCast(this.dataDoc.title); if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] && (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { -- cgit v1.2.3-70-g09d2 From 594f585b74bf6ecec3795a4b780da619c66f341d Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Apr 2022 10:57:08 -0400 Subject: added images to treeView bullets for docs with icons. fixed following pushpin links to prototypes to toggle delegates when shown. gave groups a default padding. fixed creating icons to not create unnecessary template docs. fixed toggling isPushpin. made summaries simple links instead of templates. --- src/client/documents/Documents.ts | 38 ++++++++++++---------- src/client/util/DocumentManager.ts | 5 +-- src/client/views/DocumentDecorations.tsx | 5 ++- src/client/views/StyleProvider.tsx | 13 ++++++-- src/client/views/collections/TreeView.scss | 2 +- src/client/views/collections/TreeView.tsx | 5 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 9 ++--- src/client/views/nodes/DocumentView.tsx | 8 +++-- src/client/views/nodes/LinkDocPreview.tsx | 9 ++++- 9 files changed, 59 insertions(+), 35 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 652f8e7e0..465cdf3ec 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -223,6 +223,7 @@ export class DocumentOptions { dockingConfig?: string; annotationOn?: Doc; isPushpin?: boolean; + isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document // BACKGROUND GRID @@ -807,7 +808,7 @@ export namespace Docs { } export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { - const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Freeform }, id); + const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id); documents.map(d => d.context = inst); return inst; } @@ -1317,22 +1318,25 @@ export namespace DocUtils { const _height = NumCast(doc._height); const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; - let fieldTemplate: Opt; - if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { - fieldTemplate = Docs.Create.TextDocument("", options); - } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); - } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - } - const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - - fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); - docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + if (docLayoutTemplate) { + Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); + } else { + let fieldTemplate: Opt; + if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { + fieldTemplate = Docs.Create.TextDocument("", options); + } else if (doc.data instanceof PdfField) { + fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); + } else if (doc.data instanceof VideoField) { + fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof AudioField) { + fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof ImageField) { + fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + } + const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); + fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); + docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + } } export function makeCustomView(doc: Doc, custom: boolean, layout: string) { Doc.setNativeView(doc); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 1d603e18f..fb50ecb4a 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -176,12 +176,13 @@ export class DocumentManager { }); // if the target is hidden, un-hide it here. } const focusAndFinish = (didFocus: boolean) => { + const finalTargetDoc = docView?.Document ?? targetDoc; if (originatingDoc?.isPushpin) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal - targetDoc.hidden = !targetDoc.hidden; + finalTargetDoc.hidden = !finalTargetDoc.hidden; } } else { - targetDoc.hidden && (targetDoc.hidden = undefined); + finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); !noSelect && docView?.select(false); } finished?.(); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 062ca1181..a8d91e0a2 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -453,6 +453,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin); }); } + @computed get hasIcons() { + return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon"); + } render() { const bounds = this.Bounds; @@ -516,7 +519,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> - {!canDelete ?
: topBtn("close", "times", undefined, this.onCloseClick, "Close")} + {!canDelete ?
: topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, this.onCloseClick, "Close")} {titleArea} {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} {hideResizers ? (null) : diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 9803f9782..dba290b46 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -3,10 +3,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; import { action, runInAction } from 'mobx'; +import { extname } from 'path'; import { Doc, Opt, StrListCast } from "../../fields/Doc"; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; -import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, ImageCast, NumCast, StrCast } from "../../fields/Types"; import { DashColor, lightOrDark } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; @@ -88,7 +89,15 @@ export function DefaultStyleProvider(doc: Opt, props: Opt props?.styleProvider?.(doc, props, StyleProp.ShowTitle); const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + ((Math.abs(x * y) * 9301 + 49297) % 233280 / 233280) * (max - min); switch (property.split(":")[0]) { - case StyleProp.TreeViewIcon: return Doc.toIcon(doc, isOpen); + case StyleProp.TreeViewIcon: + const icon = Cast(doc?.layout_icon, Doc, null) ? Doc.expandTemplateLayout(Cast(doc?.layout_icon, Doc, null), doc) : undefined; + if (icon && ImageCast(doc?.icon, ImageCast(doc?.data))) { + const img = ImageCast(doc?.icon, ImageCast(doc?.data)); + const ext = extname(img.url.href); + const url = doc?.icon ? img.url.href : img.url.href.replace(ext, "_s" + ext); + return ; + } + return Doc.toIcon(doc, isOpen); case StyleProp.DocContents: return undefined; case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey"; case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null)); diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 2e33d3564..7c75810d3 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -47,7 +47,7 @@ width: $TREE_BULLET_WIDTH; color: $medium-gray; margin-top: 3px; - transform: scale(1.3, 1.3); + // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder. border: #80808030 1px solid; border-radius: 4px; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index ff5c4bab1..c2d0983da 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -31,6 +31,7 @@ import { CollectionTreeView } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); +import { IconProp } from '@fortawesome/fontawesome-svg-core'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -471,7 +472,7 @@ export class TreeView extends React.Component { onClick={this.bulletClick} style={this.props.treeView.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) } : { color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), - opacity: checked === "unchecked" ? undefined : 0.4 + opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4 }}> {this.props.treeView.outlineMode ? !(this.doc.text as RichTextField)?.Text ? (null) : @@ -484,7 +485,7 @@ export class TreeView extends React.Component { checked === "unchecked" ? "square" : !this.treeViewOpen ? "caret-right" : "caret-down"} />
- {this.onCheckedClick ? (null) : } + {this.onCheckedClick ? (null) : typeof iconType === "string" ? : iconType}
}
; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b8f687489..5405ab500 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -519,13 +519,8 @@ export class MarqueeView extends React.Component(selected); - Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations"); - summary._backgroundColor = "#e2ad32"; - portal.layoutKey = "layout_portal"; - portal.title = "document collection"; + const summary = Docs.Create.TextDocument("", { _backgroundColor: "#e2ad32", x: this.Bounds.left, y: this.Bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" }); + const portal = Docs.Create.FreeformDocument(selected, { isGroup: true, backgroundColor: "transparent" }); DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", ""); this.props.addLiveTextDocument(summary); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 195f6c9e5..8bf9348c7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -594,8 +594,12 @@ export class DocumentViewInternal extends DocComponent, zoom: boolean, setPushpin: boolean): void => { this.Document.ignoreClick = false; - this.Document._isLinkButton = !this.Document._isLinkButton; - setPushpin && (this.Document.isPushpin = this.Document._isLinkButton); + if (setPushpin) { + this.Document.isPushpin = !this.Document.isPushpin; + this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton; + } else { + this.Document._isLinkButton = !this.Document._isLinkButton; + } if (this.Document._isLinkButton && !this.onClickHandler) { this.Document.followLinkZoom = zoom; this.Document.followLinkLocation = location; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index d5f3f3b4e..eca2e3cfd 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -15,6 +15,7 @@ import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; import './LinkDocPreview.scss'; import React = require("react"); import { DocumentType } from '../../documents/DocumentTypes'; +import { DragManager } from '../../util/DragManager'; interface LinkDocPreviewProps { linkDoc?: Doc; @@ -137,7 +138,13 @@ export class LinkDocPreview extends React.Component { @computed get previewHeader() { return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
-
+
{ + DragManager.StartDocumentDrag([this._infoRef.current!], + new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); + e.stopPropagation(); + LinkDocPreview.Clear(); + }}> {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}

{StrCast(this._linkDoc.description)}

-- cgit v1.2.3-70-g09d2 From f4446ed5f08a483abaeab3551b6e62d0a41915c8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Apr 2022 10:11:43 -0400 Subject: fixed pushpin behavior when there are multiple links to the pushpin (they all toggle). changed lightboxview to allow stepping into links when doc has no contents. --- src/client/util/LinkManager.ts | 12 +++++++----- src/client/views/LightboxView.tsx | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index df2c02a8d..0f3da0548 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -221,7 +221,9 @@ export class LinkManager { const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs); - const followLinks = linkDocList.length ? (sourceDoc.isPushpin ? linkDocList : [linkDocList[0]]) : []; + const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1); + var count = 0; + const allFinished = () => ++count === followLinks.length && finished?.(); followLinks.forEach(async linkDoc => { if (linkDoc) { const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 : @@ -232,20 +234,20 @@ export class LinkManager { const tour = DocListCast(target[fieldKey]).reverse(); LightboxView.SetLightboxDoc(currentContext, undefined, tour); setTimeout(LightboxView.Next); - finished?.(); + allFinished(); } else { const containerAnnoDoc = Cast(target.annotationOn, Doc, null); const containerDoc = containerAnnoDoc || target; const containerDocContext = Cast(containerDoc?.context, Doc, null); const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext; const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished); + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, allFinished); } } else { - finished?.(); + allFinished(); } } else { - finished?.(); + allFinished(); } }); } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index ec3bf6c18..b39b5020c 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -36,7 +36,7 @@ export class LightboxView extends React.Component { @observable private static _tourMap: Opt = []; // list of all tours available from the current target private static _savedState: Opt<{ panX: Opt, panY: Opt, scale: Opt, scrollTop: Opt }>; private static _history: Opt<{ doc: Doc, target?: Doc }[]> = []; - private static _future: Opt = []; + @observable private static _future: Opt = []; private static _docView: Opt; static path: { doc: Opt, target: Opt, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt, saved: Opt<{ panX: Opt, panY: Opt, scale: Opt, scrollTop: Opt }> }[] = []; @action public static SetLightboxDoc(doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc) { @@ -195,7 +195,9 @@ export class LightboxView extends React.Component { const coll = LightboxView._docTarget; if (coll) { const fieldKey = Doc.LayoutFieldKey(coll); - LightboxView.SetLightboxDoc(coll, undefined, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]); + const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]; + const links = DocListCast(coll.links).map(link => LinkManager.getOppositeAnchor(link, coll)).filter(doc => doc).map(doc => doc!); + LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links); TabDocView.PinDoc(coll, { hidePresBox: true }); } } -- cgit v1.2.3-70-g09d2 From 74ef5f7a8c60dc68a1262077f7502d0535377b88 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Apr 2022 11:49:28 -0400 Subject: fixed following links to documents in iconified collections/text to deiconify. fixed ctrl-x to cut (not iconify). changed lightboxview to add to future docs when stepping into. added placeholder for indicating how many documents are left in lightbox future --- src/client/util/DocumentManager.ts | 14 +++++++++++++- src/client/views/DocumentDecorations.tsx | 8 ++++---- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/LightboxView.tsx | 11 ++++------- src/client/views/nodes/DocumentView.tsx | 15 +++++++++------ src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- 6 files changed, 33 insertions(+), 21 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index fb50ecb4a..607a3d6bf 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -197,7 +197,14 @@ export class DocumentManager { (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView; if (!docView && annoContainerView) { - annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below + if (annoContainerView.props.Document.layoutKey === "layout_icon") { + annoContainerView.iconify(() => this.jumpToDocument( + targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, + finished, originalTarget, noSelect, presZoom)); + return; + } else { + annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below + } } if (focusView) { !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox @@ -217,6 +224,11 @@ export class DocumentManager { targetDocContextView.props.focus(targetDocContextView.rootDoc, { willZoom, afterFocus: async () => { targetDocContext._viewTransition = undefined; + if (targetDocContext.layoutKey === "layout_icon") { + targetDocContextView.iconify(() => this.jumpToDocument( + targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, + finished, originalTarget, noSelect, presZoom)); + } return ViewAdjustment.doNothing; } }); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index a8d91e0a2..78a8a4c7e 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -145,11 +145,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P } _iconifyTimeout: NodeJS.Timeout | undefined; - onCloseClick = () => { + onCloseClick = (force = false) => { if (this.canDelete) { const views = SelectionManager.Views().slice().filter(v => v); - const icons = this._iconifyTimeout ? views : views.filter(view => view.rootDoc.layoutKey === "layout_icon"); - const others = this._iconifyTimeout ? [] : views.filter(view => view.rootDoc.layoutKey !== "layout_icon"); + const icons = this._iconifyTimeout || force ? views : views.filter(view => view.rootDoc.layoutKey === "layout_icon"); + const others = this._iconifyTimeout || force ? [] : views.filter(view => view.rootDoc.layoutKey !== "layout_icon"); icons.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); others.forEach(dv => dv.iconify()); others.length && (this._iconifyTimeout = setTimeout(() => { @@ -519,7 +519,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> - {!canDelete ?
: topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, this.onCloseClick, "Close")} + {!canDelete ?
: topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, e => this.onCloseClick(), "Close")} {titleArea} {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} {hideResizers ? (null) : diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 91e0503b3..e3031d2c5 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -275,7 +275,7 @@ export class KeyManager { const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":"); SelectionManager.Views().length && navigator.clipboard.writeText(text); - DocumentDecorations.Instance.onCloseClick(); + DocumentDecorations.Instance.onCloseClick(true); stopPropagation = false; preventDefault = false; } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index b39b5020c..59ed0dc92 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -63,7 +63,7 @@ export class LightboxView extends React.Component { } } if (future) { - this._future = future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length); + this._future = [...(this._future ?? []), ...(this.LightboxDoc ? [this.LightboxDoc] : []), ...future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length),]; } this._doc = doc; this._layoutTemplate = layoutTemplate; @@ -118,7 +118,7 @@ export class LightboxView extends React.Component { addDocTab = LightboxView.AddDocTab; @action public static Next() { const doc = LightboxView._doc!; - const target = LightboxView._docTarget = LightboxView._future?.pop(); + const target = LightboxView._docTarget = this._future?.pop(); const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (targetDocView && target) { const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement(); @@ -163,11 +163,8 @@ export class LightboxView extends React.Component { const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); if (docView) { LightboxView._docTarget = target; - const focusSpeed = 1000; - doc._viewTransition = `transform ${focusSpeed}ms`; if (!target) docView.ComponentView?.shrinkWrap?.(); else docView.focus(target, { willZoom: true, scale: 0.9 }); - setTimeout(() => doc._viewTransition = undefined, focusSpeed); } else { LightboxView.SetLightboxDoc(doc, target); @@ -232,7 +229,7 @@ export class LightboxView extends React.Component { const doc = LightboxView._doc; const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); - else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true }); + //else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button })); })} Document={LightboxView.LightboxDoc} @@ -272,7 +269,7 @@ export class LightboxView extends React.Component { () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => { e.stopPropagation(); LightboxView.Next(); - })} + }, this.future()?.length.toString())}
{ diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0e22b1b3f..4380cb0bc 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -452,7 +452,7 @@ export class DocumentViewInternal extends DocComponent this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here + const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => @@ -1243,15 +1243,15 @@ export class DocumentView extends React.Component { return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) }; } - public iconify() { + public iconify(finished?: () => void) { this.ComponentView?.updateIcon?.(); const layoutKey = Cast(this.Document.layoutKey, "string", null); if (layoutKey !== "layout_icon") { - this.switchViews(true, "icon"); + this.switchViews(true, "icon", finished); if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") this.Document.deiconifyLayout = layoutKey.replace("layout_", ""); } else { const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null); - this.switchViews(deiconifyLayout ? true : false, deiconifyLayout); + this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finished); this.Document.deiconifyLayout = undefined; this.props.bringToFront(this.rootDoc); } @@ -1262,12 +1262,15 @@ export class DocumentView extends React.Component { Doc.setNativeView(this.props.Document); custom && DocUtils.makeCustomViewClicked(this.props.Document, Docs.Create.StackingDocument, layout, undefined); } - switchViews = action((custom: boolean, view: string) => { + switchViews = action((custom: boolean, view: string, finished?: () => void) => { this.docView && (this.docView._animateScalingTo = 0.1); // shrink doc setTimeout(action(() => { this.setCustomView(custom, view); this.docView && (this.docView._animateScalingTo = 1); // expand it - setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), this.docView!._animateScaleTime - 10); + setTimeout(action(() => { + this.docView && (this.docView._animateScalingTo = 0); + finished?.(); + }), this.docView!._animateScaleTime - 10); }), this.docView!._animateScaleTime - 10); }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b6713d878..ac291801b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -758,7 +758,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); if (sel.from !== sel.to) { - const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), unrendered: true }); + const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); const href = targetHref ?? Doc.localServerPath(anchor); if (anchor !== anchorDoc) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { @@ -859,7 +859,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { autoHeight && this.props.setHeight?.(marginsHeight + Math.max(sidebarHeight, textHeight)); }, { fireImmediately: true }); - this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks + this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); this._cachedLinks = newLinks; -- cgit v1.2.3-70-g09d2 From 605137223d8afa0eaf8818de5b556601fca8441f Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Thu, 14 Apr 2022 17:23:49 -0400 Subject: Create smooth record and playback of tracking. --- src/client/util/DragManager.ts | 2 +- src/client/views/MainView.scss | 5 ++++ src/client/views/MainView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 32 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 8d4b98d88..db9986a41 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -216,7 +216,7 @@ export namespace DragManager { ) { // stop an 'accidental' on-click drag that may have occured if the user is in presenting mode // note: dragData.dropAction is only undefined when the element itself being dragged without being selected - if (Doc.UserDoc()?.isPresenting && dragData.dropAction === undefined) return false; + if (Doc.UserDoc()?.presentationMode === 'recording' && dragData.dropAction === undefined) return false; const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 15cd2c144..3799b6b13 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -29,6 +29,11 @@ left: calc(100% + 5px); z-index: 1; pointer-events: none; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 10px; } .mainView-snapLines { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8c0795881..b98c80068 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -528,6 +528,7 @@ export class MainView extends React.Component { searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> + {['watching', 'recording'].includes(String(this.userDoc?.presentationMode) ?? '') ?
{this.userDoc?.presentationMode}
: <>}
; } @computed get snapLines() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..75855f5d9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -50,6 +50,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import e = require("connect-flash"); export const panZoomSchema = createSchema({ _panX: "number", @@ -117,6 +118,8 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); } @@ -956,8 +959,37 @@ export class CollectionFreeFormView extends CollectionSubView { + // need the first for subtraction + let first = null; + + this.storedMovements.forEach(movement => { + if (first === null) first = movement.time; + + // set the pan to what was stored + setTimeout(() => { + this.Document._panX = movement.panX; + this.Document._panY = movement.panY; + }, movement.time - first) + }) + + // for now, clear the movements + this.storedMovements = [] + } + @action setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { + // if not presenting, just retrace the movements + if (Doc.UserDoc()?.presentationMode === 'watching') { + this.followMovements() + return; + } + + if (Doc.UserDoc()?.presentationMode === 'recording') { + // store as many movments as possible + this.storedMovements.push({time: new Date().getTime(), panX, panY}) + } + if (!this.isAnnotationOverlay && clamp) { // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); -- cgit v1.2.3-70-g09d2 From 605ab32150c5b2b9d84eb817d3086796b72964df Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Thu, 14 Apr 2022 17:46:15 -0400 Subject: fix: fixed panning with finger interaction --- src/client/util/InteractionUtils.tsx | 2 ++ src/client/views/GestureOverlay.tsx | 13 +++++++------ .../collectionFreeForm/CollectionFreeFormView.tsx | 2 ++ 3 files changed, 11 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 3e051dec8..675dff00b 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -286,6 +286,8 @@ export namespace InteractionUtils { // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); + case TOUCHTYPE: + return e.pointerType === TOUCHTYPE; default: return e.pointerType === type; } } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index f034e06ad..75a757280 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -85,7 +85,6 @@ export class GestureOverlay extends Touchable { const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches); const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches); const nt: (React.Touch | Touch)[] = Array.from(e.touches); - console.log("getNewTouches"); this._hands.forEach((hand) => { for (let i = 0; i < e.targetTouches.length; i++) { const pt = e.targetTouches.item(i); @@ -112,7 +111,6 @@ export class GestureOverlay extends Touchable { } onReactTouchStart = (te: React.TouchEvent) => { - console.log("react touch start"); document.removeEventListener("touchmove", this.onReactHoldTouchMove); document.removeEventListener("touchend", this.onReactHoldTouchEnd); if (RadialMenu.Instance?._display === true) { @@ -131,6 +129,7 @@ export class GestureOverlay extends Touchable { // pen is also a touch, but with a radius of 0.5 (at least with the surface pens) // and this seems to be the only way of differentiating pen and touch on touch events if (pt.radiusX > 1 && pt.radiusY > 1) { + Doc.UserDoc().activeInkTool = InkTool.None; this.prevPoints.set(pt.identifier, pt); } } @@ -209,7 +208,6 @@ export class GestureOverlay extends Touchable { } onReactTouchMove = (e: TouchEvent) => { - console.log("react touch move"); const nts: any = this.getNewTouches(e); this._holdTimer && clearTimeout(this._holdTimer); this._holdTimer = undefined; @@ -500,6 +498,10 @@ export class GestureOverlay extends Touchable { @action onPointerDown = (e: React.PointerEvent) => { + if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { + CurrentUserUtils.SelectedTool = InkTool.None; + return; + } if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { CurrentUserUtils.SelectedTool = InkTool.Write; @@ -631,7 +633,8 @@ export class GestureOverlay extends Touchable { } // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document - if (!actionPerformed) { + console.log("dispatch gesture", CurrentUserUtils.SelectedTool); + if (!actionPerformed && CurrentUserUtils.SelectedTool !== InkTool.None) { const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); newPoints.pop(); console.log("getting to bezier math"); @@ -650,11 +653,9 @@ export class GestureOverlay extends Touchable { (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; this._points = controlPoints; - this.dispatchGesture(GestureUtils.Gestures.Stroke); // TODO: nda - check inks to group here checkInksToGroup(); - } this._points = []; this._timeStamps = []; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 908f08f88..c9465e7a4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -704,6 +704,8 @@ export class CollectionFreeFormView extends CollectionSubView { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { + console.log("is touch"); + Doc.UserDoc().activeInkTool = InkTool.None; if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { -- cgit v1.2.3-70-g09d2 From 969ab00c44859b932884bb32fef1b1e8b11d5448 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 24 Apr 2022 15:01:00 -0400 Subject: from last --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/collections/CollectionSubView.tsx | 33 +++++++++++----------- src/client/views/collections/TreeView.tsx | 12 ++++---- 3 files changed, 22 insertions(+), 25 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index bf722157a..c7309d15e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -389,7 +389,7 @@ export class CurrentUserUtils { const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, - backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) + backgroundColor: "white", system: true, cloneFieldFilter: new List(["system"]) }); Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); FormattedTextBox.SelectOnLoad = textDoc[Id]; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 0eb94c394..5bdd0c0ac 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,32 +1,23 @@ -import { action, computed, observable } from "mobx"; -import ReactLoading from 'react-loading'; -import * as rp from 'request-promise'; +import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; import CursorField from "../../../fields/CursorField"; -import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; +import { Doc, Opt, Field, DocListCast, AclPrivate, StrListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; -import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; +import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; -import { returnFalse, Utils } from "../../../Utils"; +import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils"; import { DocServer } from "../../DocServer"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Networking } from "../../Network"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { DragManager, dropActionType } from "../../util/DragManager"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; import { InteractionUtils } from "../../util/InteractionUtils"; -import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; -import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; -import { OverlayView } from "../OverlayView"; -import { CollectionView, CollectionViewProps, CollectionViewType } from "./CollectionView"; import React = require("react"); +import ReactLoading from 'react-loading'; +import * as rp from 'request-promise'; +import { Networking } from "../../Network"; export interface SubCollectionViewProps extends CollectionViewProps { @@ -481,4 +472,12 @@ export function CollectionSubView(moreProps?: X) { return CollectionSubView; } - +import { DragManager, dropActionType } from "../../util/DragManager"; +import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox"; +import { CollectionView, CollectionViewType, CollectionViewProps } from "./CollectionView"; +import { SelectionManager } from "../../util/SelectionManager"; +import { OverlayView } from "../OverlayView"; +import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; \ No newline at end of file diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 61c7ff78e..6213850d8 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,7 +1,8 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym, StrListCast } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; @@ -9,7 +10,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils, returnOne } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; @@ -21,17 +22,14 @@ import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from "../EditableView"; import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; -import { DocumentView, DocumentViewProps, StyleProviderFunc, DocumentViewInternal } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { KeyValueBox } from '../nodes/KeyValueBox'; -import { SliderBox } from '../nodes/SliderBox'; -import { StyleProp, testDocProps } from '../StyleProvider'; +import { StyleProp } from '../StyleProvider'; import { CollectionTreeView } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); -import { IconProp } from '@fortawesome/fontawesome-svg-core'; export interface TreeViewProps { treeView: CollectionTreeView; -- cgit v1.2.3-70-g09d2 From 40ccb702d1dddf0ce9f15e047feb321353f01580 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Apr 2022 14:55:04 -0400 Subject: fixed following pushpin links to text selections to toggle the text box (since markers don't have a hidden flag for toggling). fixed pdf next/prev page and # pages. fixed pdf selections to not include
's which apparently have the wrong bounds. --- src/client/util/DocumentManager.ts | 11 ++++++----- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 10 ++++++++-- src/client/views/pdf/PDFViewer.tsx | 8 +++----- 4 files changed, 18 insertions(+), 13 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 607a3d6bf..cc3196baa 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -168,15 +168,17 @@ export class DocumentManager { originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; const docView = getFirstDocView(targetDoc, originatingDoc); - const wasHidden = targetDoc.hidden; // + const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); + const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field + const wasHidden = resolvedTarget.hidden; if (wasHidden) { runInAction(() => { - targetDoc.hidden = false; - docView?.props.bringToFront(targetDoc); + resolvedTarget.hidden = false; + docView?.props.bringToFront(resolvedTarget); }); // if the target is hidden, un-hide it here. } const focusAndFinish = (didFocus: boolean) => { - const finalTargetDoc = docView?.Document ?? targetDoc; + const finalTargetDoc = docView?.Document ?? resolvedTarget; if (originatingDoc?.isPushpin) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal finalTargetDoc.hidden = !finalTargetDoc.hidden; @@ -188,7 +190,6 @@ export class DocumentManager { finished?.(); return false; }; - const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const annoContainerView = annotatedDoc && getFirstDocView(annotatedDoc); const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9d738180b..88000ecbd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -460,7 +460,7 @@ export class DocumentViewInternal extends DocComponent this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here - const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; + const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0)) diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 23749d479..b556b0ef1 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -157,8 +157,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent this._pdfViewer?.prevAnnotation(); public nextAnnotation = () => this._pdfViewer?.nextAnnotation(); - public backPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) - 1; return true; }; - public forwardPage = () => { this.Document._curPage = (NumCast(this.Document._curPage) || 1) + 1; return true; }; + public backPage = () => { + this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1); + return true; + }; + public forwardPage = () => { + this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1); + return true; + }; public gotoPage = (p: number) => this.Document._curPage = p; @undoBatch diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 92a69c02b..b58966173 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -167,7 +167,7 @@ export class PDFViewer extends React.Component { }); if (i === this.props.pdf.numPages - 1) { this.props.loaded?.(page.view[page0or180 ? 2 : 3] - page.view[page0or180 ? 0 : 1], - page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], i); + page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], this.props.pdf.numPages); } })))); this.props.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; @@ -299,9 +299,7 @@ export class PDFViewer extends React.Component { @action gotoPage = (p: number) => { - if (this._pdfViewer?._setDocumentViewerElement?.offsetParent) { - this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); - } + this._pdfViewer?.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); } @action @@ -419,7 +417,7 @@ export class PDFViewer extends React.Component { const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); - if (rect && rect.width !== this._mainCont.current.clientWidth) { + if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) { const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; const annoBox = document.createElement("div"); annoBox.className = "marqueeAnnotator-annotationBox"; -- cgit v1.2.3-70-g09d2 From 2192b6cd994e71812d335a9902ef4e4ae296314b Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Apr 2022 23:33:19 -0400 Subject: fixed following links (jumpToDocument) to work properly with markers and hidden flags in nested arrangements. pushpin behaviors had been broken for scrolling to a location within a text document and hiding the document if no scroll was needed. unhiding hidden images/pdfs wasn't working when following a pushpin to an annotation on them. --- src/client/util/DocumentManager.ts | 26 +++++++++++----------- src/client/views/nodes/DocumentView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 7 ++++-- 3 files changed, 19 insertions(+), 16 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index cc3196baa..9febd4302 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -167,15 +167,15 @@ export class DocumentManager { ): Promise => { originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; - const docView = getFirstDocView(targetDoc, originatingDoc); + var docView = getFirstDocView(targetDoc, originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field - const wasHidden = resolvedTarget.hidden; + var wasHidden = resolvedTarget.hidden; if (wasHidden) { runInAction(() => { - resolvedTarget.hidden = false; + resolvedTarget.hidden = false; // if the target is hidden, un-hide it here. docView?.props.bringToFront(resolvedTarget); - }); // if the target is hidden, un-hide it here. + }); } const focusAndFinish = (didFocus: boolean) => { const finalTargetDoc = docView?.Document ?? resolvedTarget; @@ -188,16 +188,15 @@ export class DocumentManager { !noSelect && docView?.select(false); } finished?.(); - return false; }; - const annoContainerView = annotatedDoc && getFirstDocView(annotatedDoc); + const annoContainerView = (!wasHidden || resolvedTarget !== annotatedDoc) && annotatedDoc && getFirstDocView(annotatedDoc); const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined; const targetDocContext = contextDoc || annotatedDoc; const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) || (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView; - if (!docView && annoContainerView) { + if ((!docView && targetDoc.type !== DocumentType.MARKER) && annoContainerView) { if (annoContainerView.props.Document.layoutKey === "layout_icon") { annoContainerView.iconify(() => this.jumpToDocument( targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, @@ -221,6 +220,8 @@ export class DocumentManager { createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); } else { // otherwise try to get a view of the context of the target if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. + wasHidden = wasHidden || targetDocContextView.rootDoc.hidden; + targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden targetDocContext._viewTransition = "transform 500ms"; targetDocContextView.props.focus(targetDocContextView.rootDoc, { willZoom, afterFocus: async () => { @@ -240,24 +241,24 @@ export class DocumentManager { finished?.(); } else { // no timecode means we need to find the context view and focus on our target const findView = (delay: number) => { - const retryDocView = getFirstDocView(targetDoc); // test again for the target view snce we presumably created the context above by focusing on it - if (retryDocView) { // we found the target in the context + const retryDocView = getFirstDocView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { // we found the target in the context. Doc.linkFollowHighlight(retryDocView.rootDoc); - retryDocView.props.focus(targetDoc, { + retryDocView.focus(targetDoc, { willZoom, afterFocus: (didFocus: boolean) => new Promise(res => { !noSelect && focusAndFinish(didFocus); res(ViewAdjustment.doNothing); }) }); // focus on the target in the context - } else if (delay > 1500) { + } else if (delay > 1000) { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target } } else { - setTimeout(() => findView(delay + 250), 250); + setTimeout(() => findView(delay + 200), 200); } }; findView(0); @@ -270,7 +271,6 @@ export class DocumentManager { } } } - } export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) { const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 88000ecbd..9d738180b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -460,7 +460,7 @@ export class DocumentViewInternal extends DocComponent this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); const focusSpeed = this._componentView?.scrollFocus?.(anchor, !options?.instant || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here - const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing; + const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0)) diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 41e2fc266..756d6c2d6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -813,6 +813,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }; let start = 0; + this._didScroll = false; // assume we don't need to scroll. if we do, this will get set to true in handleScrollToSelextion when we dispatch the setSelection below if (this._editorView && textAnchorId) { const editor = this._editorView; const ret = findAnchorFrag(editor.state.doc.content, editor); @@ -832,7 +833,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - return this._focusSpeed; + return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed } // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. @@ -1128,6 +1129,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } }); } + _didScroll = false; setupEditor(config: any, fieldKey: string) { const curText = Cast(this.dataDoc[this.props.fieldKey], RichTextField, null) || StrCast(this.dataDoc[this.props.fieldKey]); const rtfField = Cast((!curText && this.layoutDoc[this.props.fieldKey]) || this.dataDoc[fieldKey], RichTextField); @@ -1142,7 +1144,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const scrollRef = self._scrollRef.current; const topOff = docPos.top < viewRect.top ? docPos.top - viewRect.top : undefined; const botOff = docPos.bottom > viewRect.bottom ? docPos.bottom - viewRect.bottom : undefined; - if ((topOff || botOff) && scrollRef) { + if (((topOff && Math.abs(Math.trunc(topOff)) > 0) || (botOff && Math.abs(Math.trunc(botOff)) > 0)) && scrollRef) { const shift = Math.min(topOff ?? Number.MAX_VALUE, botOff ?? Number.MAX_VALUE); const scrollPos = scrollRef.scrollTop + shift * self.props.ScreenToLocalTransform().Scale; if (this._focusSpeed !== undefined) { @@ -1150,6 +1152,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } else { scrollRef.scrollTo({ top: scrollPos }); } + this._didScroll = true; } return true; }, -- cgit v1.2.3-70-g09d2 From cf0f4d9fe144d05e8604d5224066b0e86879a3de Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Apr 2022 00:08:36 -0400 Subject: fixes for pushpin links to anchors on pdfviews --- src/client/util/DocumentManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 9febd4302..412d5a169 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -178,7 +178,7 @@ export class DocumentManager { }); } const focusAndFinish = (didFocus: boolean) => { - const finalTargetDoc = docView?.Document ?? resolvedTarget; + const finalTargetDoc = resolvedTarget; if (originatingDoc?.isPushpin) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal finalTargetDoc.hidden = !finalTargetDoc.hidden; @@ -261,7 +261,7 @@ export class DocumentManager { setTimeout(() => findView(delay + 200), 200); } }; - findView(0); + setTimeout(() => findView(0), 0); } } else { // there's no context view so we need to create one first and try again when that finishes const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget); -- cgit v1.2.3-70-g09d2 From 778432158e5f0bcdcb2e68ff6872a5c9335ef3af Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Apr 2022 10:49:32 -0400 Subject: made sharing menu a developer feature. made link relationships support non-symmetry --- src/client/util/LinkManager.ts | 9 ++++++--- src/client/views/PropertiesView.tsx | 2 +- src/client/views/nodes/DocumentLinksButton.tsx | 11 +++++++++-- src/client/views/nodes/LinkDocPreview.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 ++++++---- 5 files changed, 23 insertions(+), 11 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 0f3da0548..7ba7a1d4c 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -29,6 +29,7 @@ export class LinkManager { public static currentLink: Opt; public static get Instance() { return LinkManager._instance; } public static addLinkDB = (linkDb: any) => LinkManager.userLinkDBs.push(linkDb); + public static AutoKeywords = "keywords:Usages"; static links: Doc[] = []; constructor() { LinkManager._instance = this; @@ -149,9 +150,11 @@ export class LinkManager { public getRelatedGroupedLinks(anchor: Doc): Map> { const anchorGroups = new Map>(); this.relatedLinker(anchor).forEach(link => { - if (!link.linkRelationship || link?.linkRelationship !== "-ungrouped-") { - const group = anchorGroups.get(StrCast(link.linkRelationship)); - anchorGroups.set(StrCast(link.linkRelationship), group ? [...group, link] : [link]); + if (link.linkRelationship && link.linkRelationship !== "-ungrouped-") { + const relation = StrCast(link.linkRelationship); + const anchorRelation = relation.indexOf(":") ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation; + const group = anchorGroups.get(anchorRelation); + anchorGroups.set(anchorRelation, group ? [...group, link] : [link]); } else { // if link is in no groups then put it in default group const group = anchorGroups.get("*"); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index b63395c76..21c688421 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1362,7 +1362,7 @@ export class PropertiesView extends React.Component { {this.fieldsSubMenu} - {this.sharingSubMenu} + {isNovice ? null : this.sharingSubMenu} {isNovice ? null : this.filtersSubMenu} diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 9b79db319..d2e6e8579 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -17,6 +17,7 @@ import { DocumentView } from "./DocumentView"; import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { TaskCompletionBox } from "./TaskCompletedBox"; import React = require("react"); +import { DocumentType } from "../../documents/DocumentTypes"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -158,7 +159,9 @@ export class DocumentLinksButton extends React.Component +
{ (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 28915674d..375434933 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -85,7 +85,7 @@ export class LinkDocPreview extends React.Component { anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => { if (anchor instanceof Doc && DocListCast(anchor.links).length) { this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0]; - const automaticLink = this._linkDoc.linkRelationship === "automatic"; + const automaticLink = this._linkDoc.linkRelationship === LinkManager.AutoKeywords; if (automaticLink) { // automatic links specify the target in the link info, not the source const linkTarget = anchor; this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 756d6c2d6..29117794e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -349,7 +349,7 @@ 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 === LinkManager.AutoKeywords); const f = this._editorView.state.selection.from; const t = this._editorView.state.selection.to; var tr = this._editorView.state.tr as any; @@ -382,14 +382,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const flattened1 = this.findInNode(editorView, editorView.state.doc, StrCast(target.title).replace(/^@/, "")); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { - const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, StrCast(target.title).replace(/^@/, "")); + const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; 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")!); + Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, + LinkManager.AutoKeywords)!); newAutoLinks.add(alink); const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); const sel = flattened[i]; -- cgit v1.2.3-70-g09d2 From 59a22f3a007b201e68ac2f3e1d62e0ca5e66488a Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Apr 2022 12:13:44 -0400 Subject: fixed feedback dragging link button. fixed warnings. added some assymetric linkRelationships. --- src/client/documents/Documents.ts | 3 ++- src/client/util/DocumentManager.ts | 2 +- src/client/util/LinkManager.ts | 2 +- src/client/views/SidebarAnnos.tsx | 2 +- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../views/collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/linking/LinkMenu.tsx | 4 +--- src/client/views/nodes/DocumentLinksButton.scss | 2 ++ src/client/views/nodes/DocumentLinksButton.tsx | 10 +++++----- src/client/views/nodes/DocumentView.tsx | 4 ++-- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 4 ++-- src/client/views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/search/SearchBox.tsx | 2 +- src/server/ApiManagers/UploadManager.ts | 4 ++-- 15 files changed, 24 insertions(+), 23 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0bb873667..087d32a60 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1094,10 +1094,11 @@ export namespace DocUtils { export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) { broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1); return DocUtils.ActiveRecordings.map(audio => - DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording link", "recording timeline")); + DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline")); } export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { + if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? "Commentary:Comments On" : "link"; const sv = DocumentManager.Instance.getDocumentView(source.doc); if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return; if (target.doc === Doc.UserDoc()) return undefined; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 412d5a169..b6c28d2fe 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -167,7 +167,7 @@ export class DocumentManager { ): Promise => { originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; - var docView = getFirstDocView(targetDoc, originatingDoc); + const docView = getFirstDocView(targetDoc, originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field var wasHidden = resolvedTarget.hidden; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 7ba7a1d4c..9445533dc 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -152,7 +152,7 @@ export class LinkManager { this.relatedLinker(anchor).forEach(link => { if (link.linkRelationship && link.linkRelationship !== "-ungrouped-") { const relation = StrCast(link.linkRelationship); - const anchorRelation = relation.indexOf(":") ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation; + const anchorRelation = relation.indexOf(":") !== -1 ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation; const group = anchorGroups.get(anchorRelation); anchorGroups.set(anchorRelation, group ? [...group, link] : [link]); } else { diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 43a02d029..04c0565ea 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -61,7 +61,7 @@ export class SidebarAnnos extends React.Component { FormattedTextBox.SelectOnLoad = target[Id]; FormattedTextBox.DontSelectInitialText = true; this.allMetadata.map(tag => target[tag] = tag); - DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup"); + DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline comment:comment on"); this.addDocument(target); this._stackRef.current?.focusDocument(target); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f0b3d70a0..3f72052ae 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -278,7 +278,7 @@ export class CollectionFreeFormView extends CollectionSubView
: -
+
{this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node -
DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}> @@ -256,7 +255,8 @@ export class DocumentLinksButton extends React.Component +
: diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9d738180b..8570f6fff 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -663,7 +663,7 @@ export class DocumentViewInternal extends DocComponent d.anchor1 === this.props.Document); if (!portalLink) { const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" }); - DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to"); + DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from"); } this.Document.followLinkLocation = "inPlace"; this.Document.followLinkZoom = true; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index aadad5ffa..5982d4d66 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -152,7 +152,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1); return true; - }; + } public forwardPage = () => { this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1); return true; - }; + } public gotoPage = (p: number) => this.Document._curPage = p; @undoBatch diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 00c03875b..427e05edb 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -275,7 +275,7 @@ export class RichTextRules { this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); } const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid); - DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to", undefined); + DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to:portal from", undefined); const fstate = this.TextBox.EditorView?.state; if (fstate && selection) { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 5fe2a5ab1..9257cb75e 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -112,7 +112,7 @@ export class SearchBox extends ViewBoxBaseComponent() { if (this.props.linkFrom) { const linkFrom = this.props.linkFrom(); if (linkFrom) { - DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }, "Link"); + DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }); } } }); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 5e0cd67cc..83a2bc79b 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -14,7 +14,7 @@ import { normalize } from "path"; import RouteSubscriber from "../RouteSubscriber"; const imageDataUri = require('image-data-uri'); import { SolrManager } from "./SearchManager"; -let fs = require('fs'); +const fs = require('fs'); export enum Directory { parsed_files = "parsed_files", @@ -265,7 +265,7 @@ export default class UploadManager extends ApiManager { } if (deleteFiles) { const path = serverPathToFile(Directory.images, ""); - let regex = new RegExp(`${deleteFiles}.*`); + const regex = new RegExp(`${deleteFiles}.*`); fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f)); } return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => { -- cgit v1.2.3-70-g09d2 From 4f6b6306803437851959e9ad214fd61a14207a20 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Thu, 28 Apr 2022 16:34:31 -0400 Subject: added basic code for subgrouping transcribed text --- src/client/util/InteractionUtils.tsx | 1 + src/client/views/GestureOverlay.tsx | 2 +- src/client/views/InkTranscription.tsx | 71 +++++++++++++++++++++- src/client/views/LightboxView.tsx | 3 +- src/client/views/Touchable.tsx | 2 + .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 + src/client/views/nodes/button/FontIconBox.tsx | 56 ++--------------- 8 files changed, 86 insertions(+), 55 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 675dff00b..e2c5fab64 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -357,6 +357,7 @@ export namespace InteractionUtils { } export function IsDragging(oldTouches: Map, newTouches: React.Touch[], leniency: number): boolean { + console.log("getting here"); for (const touch of newTouches) { if (touch) { const oldTouch = oldTouches.get(touch.identifier); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 5f0311d38..e689cd43c 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -212,7 +212,7 @@ export class GestureOverlay extends Touchable { const nts: any = this.getNewTouches(e); this._holdTimer && clearTimeout(this._holdTimer); this._holdTimer = undefined; - + document.dispatchEvent( new CustomEvent>("dashOnTouchMove", { diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index ea5d5a1ec..3b464db10 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { Doc } from '../../fields/Doc'; import { InkData, InkField } from "../../fields/InkField"; import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { aggregateBounds } from '../../Utils'; import { DocumentType } from "../documents/DocumentTypes"; import './InkTranscription.scss'; @@ -15,12 +16,18 @@ export class InkTranscription extends React.Component { @observable _mathRef: any; @observable _textRegister: any; @observable _textRef: any; + private lastJiix: any; private currGroup?: Doc; + private wordsBoundMapping: any; + private inkDocs: Doc[]; + private ffView: any; constructor(props: Readonly<{}>) { super(props); InkTranscription.Instance = this; + this.wordsBoundMapping = new Map(); + this.inkDocs = new Array(); } componentWillUnmount() { @@ -98,8 +105,10 @@ export class InkTranscription extends React.Component { return this._textRef = r; } - transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => { + transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean, ffView?: any) => { if (!groupDoc) return; + this.inkDocs = inkDocs; + this.ffView = ffView; const validInks = inkDocs.filter(s => s.type === DocumentType.INK); @@ -137,8 +146,20 @@ export class InkTranscription extends React.Component { }; } + mmToPixel = (mm: number) => { + return ((96 * mm) / 25.4); + } + + calcBounds = (coords: any) => { + // find max and min x values and subtract + const max = Math.max(...coords); + const min = Math.min(...coords); + return max - min; + } + exportInk = (e: any, ref: any) => { const exports = e.detail.exports; + console.log(e); if (exports) { if (exports['application/x-latex']) { const latex = exports['application/x-latex']; @@ -152,12 +173,60 @@ export class InkTranscription extends React.Component { ref.editor.clear(); } else if (exports['text/plain']) { + if (exports['application/vnd.myscript.jiix']) { + this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']); + // console.log("lastJiix:", this.lastJiix); + // TODO: nda - go through all the words and look at x,y values and figure out the selection + this.lastJiix.words.forEach((word: any) => { + // map each word to a new map + this.wordsBoundMapping[word] = new Map(); + if (word.items) { + word.items.map((item: any) => { + this.wordsBoundMapping.set(word, {xList: item.X, yList: item.Y, width: this.calcBounds(item.X), height: this.calcBounds(item.Y)}); + }) + } + }) + // make groups for each of the words + // loop through this.wordsBoundMapping + + this.wordsBoundMapping.forEach((value: any, key: any) => { + const xList = value.xList; + const yList = value.yList; + const width = value.width; + const height = value.height; + const bounds: { x: number, y: number, width?: number, height?: number }[] = [] + // loop through xList + for (let i = 0; i < xList.length; i++) { + const x = xList[i]; + const y = yList[i]; + const newBounds = { x: x, y: y }; + bounds.push(newBounds); + } + const aggregBounds = aggregateBounds(bounds, 0, 0); + + const marqViewRef = this.ffView?._marqueeViewRef.current; + + // set the vals for bounds in marqueeView + if (marqViewRef) { + marqViewRef._downX = aggregBounds.x; + marqViewRef._downY = aggregBounds.y; + marqViewRef._lastX = aggregBounds.r; + marqViewRef._lastY = aggregBounds.b; + } + + }); + + // console.log(this.wordsBoundMapping); + } const text = exports['text/plain']; console.log(text); if (this.currGroup) { + console.log("curr grouping"); this.currGroup.text = text; this.currGroup.title = text; + console.log(this.lastJiix); + console.log(this.currGroup); } ref.editor.clear(); diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index ec3bf6c18..c8f6b5f0b 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -5,7 +5,7 @@ import "normalize.css"; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, Utils } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; @@ -17,6 +17,7 @@ import "./LightboxView.scss"; import { DocumentView } from './nodes/DocumentView'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; import { CollectionMenu } from './collections/CollectionMenu'; +import { utils } from 'mocha'; interface LightboxViewProps { PanelWidth: number; diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 789756a78..c712e7ed3 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -127,11 +127,13 @@ export abstract class Touchable extends React.Component { } handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { + console.log("getting to handle1PointersMove"); e.stopPropagation(); e.preventDefault(); } handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { + console.log("getting to handle2PointersMove"); e.stopPropagation(); e.preventDefault(); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 023953658..7c11c252a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -702,9 +702,9 @@ export class CollectionFreeFormView extends CollectionSubView { + console.log("this is onPointerMove"); if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - console.log("is touch"); Doc.UserDoc().activeInkTool = InkTool.None; if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { @@ -825,6 +825,7 @@ export class CollectionFreeFormView extends CollectionSubView) => { + console.log("getting here yeet"); if (!e.cancelBubble) { const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); if (myTouches[0]) { @@ -836,6 +837,7 @@ export class CollectionFreeFormView extends CollectionSubView only want to do this when collection is selected' this.pan(myTouches[0]); } } diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 7e6ca4248..f9d2b9917 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -21,6 +21,8 @@ import { LinkDescriptionPopup } from "./LinkDescriptionPopup"; import { TaskCompletionBox } from "./TaskCompletedBox"; import React = require("react"); import { Transform } from "../../util/Transform"; +import { InkTool } from "../../../fields/InkField"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index fc358f106..85130e69e 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -757,13 +757,17 @@ export function createInkGroup(inksToGroup?: Doc[]) { ffView.props.removeDocument?.(selected); // TODO: nda - this is the code to actually get a new grouped collection const newCollection = marqViewRef?.getCollection(selected, undefined, [], true); + if (newCollection) { + newCollection.height = newCollection[HeightSym](); + newCollection.width = newCollection[WidthSym](); + } // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs newCollection && ffView.props.addDocument?.(newCollection); // TODO: nda - will probably need to go through and only remove the unprocessed selected docs ffView.unprocessedDocs = []; - InkTranscription.Instance.transcribeInk(newCollection, selected, false); + InkTranscription.Instance.transcribeInk(newCollection, selected, false, ffView); }); } CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); @@ -778,56 +782,6 @@ export function createInkGroup(inksToGroup?: Doc[]) { ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) { createInkGroup(); - // if (CurrentUserUtils.SelectedTool === InkTool.Write) { - // CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // const selected = ffView.unprocessedDocs; - // // loop through selected an get the bound - // const bounds: { x: number, y: number, width?: number, height?: number }[] = [] - - // selected.map(action(d => { - // const x = NumCast(d.x); - // const y = NumCast(d.y); - // const width = d[WidthSym](); - // const height = d[HeightSym](); - // bounds.push({x, y, width, height}); - // })) - - // const aggregBounds = aggregateBounds(bounds, 0, 0); - // const marqViewRef = ffView._marqueeViewRef.current; - - // // set the vals for bounds in marqueeView - // if (marqViewRef) { - // marqViewRef._downX = aggregBounds.x; - // marqViewRef._downY = aggregBounds.y; - // marqViewRef._lastX = aggregBounds.r; - // marqViewRef._lastY = aggregBounds.b; - // } - - // selected.map(action(d => { - // const dx = NumCast(d.x); - // const dy = NumCast(d.y); - // delete d.x; - // delete d.y; - // delete d.activeFrame; - // delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - // delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - // // calculate pos based on bounds - // if (marqViewRef?.Bounds) { - // d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2; - // d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; - // } - // return d; - // })); - // ffView.props.removeDocument?.(selected); - // // TODO: nda - this is the code to actually get a new grouped collection - // const newCollection = marqViewRef?.getCollection(selected, undefined, [], true); - - // // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - // newCollection && ffView.props.addDocument?.(newCollection); - // ffView.unprocessedDocs = []; - // }); - // } - // CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); if (checkResult) { return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? -- cgit v1.2.3-70-g09d2 From 7b08cb4fd86a7e7048e14c05ab980f5b008d18d8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Apr 2022 13:57:42 -0400 Subject: cleaned up creation of icon templates. fixed labelBox to support padding and to work with multiple lines and min font sizes. --- src/client/util/CurrentUserUtils.ts | 141 +++++++++++++++------------------ src/client/util/DropConverter.ts | 5 ++ src/client/views/nodes/LabelBigText.js | 7 +- src/client/views/nodes/LabelBox.tsx | 46 ++++++++--- 4 files changed, 105 insertions(+), 94 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c7309d15e..7948aa70b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -9,7 +9,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; -import { ImageField, nullAudio } from "../../fields/URLField"; +import { nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; @@ -22,11 +22,10 @@ import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; -import { LabelBox } from "../views/nodes/LabelBox"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; import { DragManager } from "./DragManager"; -import { makeTemplate } from "./DropConverter"; +import { makeTemplate, MakeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; import { ScriptingGlobals } from "./ScriptingGlobals"; @@ -289,82 +288,68 @@ export class CurrentUserUtils { // setup templates for different document types when they are iconified from Document Decorations static setupDefaultIconTemplates(doc: Doc) { - 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, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - }); - // Docs.Create.TextDocument("", { - // 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); - doc["template-icon-view"] = new PrefetchProxy(iconView); - } - if (doc["template-icon-view-rtf"] === undefined) { - const iconRtfView = Docs.Create.LabelDocument({ - title: "icon_" + DocumentType.RTF, _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), - _singleLine: false, _minFontSize: 18, _maxFontSize: 24, - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - }); - iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); - doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); - } - 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(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: 150, isTemplateDoc: true, _showTitle: "title", onClick: 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.ImageDocument("", { title: "icon", _showTitle: "title", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); - iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); - const proto = iconColView.proto as Doc; - proto["icon-nativeWidth"] = 360 / 4; - proto["icon-nativeHeight"] = 270 / 4; - proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); - doc["template-icon-view-col"] = new PrefetchProxy(iconColView); - } - if (doc["template-icon-view-video"] === undefined) { - const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); - iconVidView.isTemplateDoc = makeTemplate(iconVidView, true, "icon_" + DocumentType.VID); - const proto = iconVidView.proto as Doc; - proto["icon-nativeWidth"] = 360 / 4; - proto["icon-nativeHeight"] = 270 / 4; - proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); - doc["template-icon-view-video"] = new PrefetchProxy(iconVidView); - } - if (doc["template-icon-view-pdf"] === undefined) { - const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); - iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF); - const proto = iconPdfView.proto as Doc; - proto["icon-nativeWidth"] = 360 / 4; - proto["icon-nativeHeight"] = 270 / 4; - proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); - doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView); - } - if (doc["template-icons"] === undefined) { - doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-video"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); - } else { - const templateIconsDoc = Cast(doc["template-icons"], Doc, null); - const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; - DocListCastAsync(templateIconsDoc.data).then(async curIcons => { - curIcons && await Promise.all(curIcons); - requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype)); - }); + const templateIconsDoc = Cast(doc["template-icons"], Doc, null); + + const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc, finalize: (icon: Doc) => Doc = (doc: Doc) => doc) => { + const iconFieldName = type ? "icon_" + type : "icon"; + if (templateIconsDoc?.[iconFieldName] === undefined) { + const template = finalize(MakeTemplate(iconTemplate(), true, iconFieldName, templateField)); + if (templateIconsDoc) { + templateIconsDoc[iconFieldName] = new PrefetchProxy(template); + Doc.AddDocToList(templateIconsDoc, "data", template); + } + return template; + } + return templateIconsDoc[iconFieldName] as Doc; + }; + const finalizeIconTemplate = (icon: Doc) => { + const iconProto = Doc.GetProto(icon); + iconProto["icon-nativeWidth"] = iconProto.width; + iconProto["icon-nativeHeight"] = iconProto.height; + return icon; + }; + const iconList = [ + makeIconTemplate(undefined, "title", () => Docs.Create.LabelDocument({ + textTransform: "unset", letterSpacing: "unset", _backgroundColor: "dimgray", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, + onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + })), + makeIconTemplate(DocumentType.AUDIO, "title", () => Docs.Create.LabelDocument({ + textTransform: "unset", letterSpacing: "unset", _backgroundColor: "lightgreen", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, + onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + })), + makeIconTemplate(DocumentType.RTF, "text", () => Docs.Create.LabelDocument({ + _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, + onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + })), + makeIconTemplate(DocumentType.PDF, "title", () => Docs.Create.LabelDocument({ + textTransform: "unset", letterSpacing: "unset", _backgroundColor: "pink", _singleLine: false, _minFontSize: 10, _maxFontSize: 24, borderRounding: "5px", + _width: 75, _height: 125, _xPadding: 10, _yPadding: 10, + onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + })), + makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ + _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, + onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + })), + makeIconTemplate(DocumentType.IMG, "data", () => Docs.Create.ImageDocument("", { + _width: 150, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + })), + makeIconTemplate(DocumentType.COL, "icon", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { + _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + }), finalizeIconTemplate), + makeIconTemplate(DocumentType.VID, "icon", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { + _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + }), finalizeIconTemplate), + // makeIconTemplate(DocumentType.PDF, "data", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { + // _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + // }), setIconProtoFields) + ]; + if (!templateIconsDoc) { + doc["template-icons"] = Docs.Create.TreeDocument(iconList, { title: "icon templates", _height: 75, system: true }); + iconList.map(ntype => (doc["template-icons"] as Doc)[StrCast(ntype.title)] = new PrefetchProxy(ntype)); } - return doc["template-icons"] as Doc; } static creatorBtnDescriptors(doc: Doc): { diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 082b6d8bd..076afd3a0 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -10,6 +10,11 @@ import { ImageField } from "../../fields/URLField"; import { ScriptingGlobals } from "./ScriptingGlobals"; import { listSpec } from "../../fields/Schema"; +export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt = undefined, templateField: string = "") { + if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated + doc.isTemplateDoc = makeTemplate(doc, first, rename); + return doc; +} // // converts 'doc' into a template that can be used to render other documents. // the title of doc is used to determine which field is being templated, so diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js index 02c36c4bc..6a212d9ea 100644 --- a/src/client/views/nodes/LabelBigText.js +++ b/src/client/views/nodes/LabelBigText.js @@ -193,11 +193,12 @@ export default function BigText(element, options) { if (fontSize < options.minimumFontSize) { parentStyle.display = "flex"; parentStyle.alignItems = "center"; - style.whiteSpace = "pre-wrap"; style.textAlign = "center"; style.visibility = ""; - style.fontSize = "18px"; - style.lineHeight = "20px"; + style.fontSize = options.minimumFontSize + "px"; + style.lineHeight = ""; + style.overflow = "hidden"; + style.textOverflow = "ellipsis"; style.top = ""; style.left = ""; style.margin = ""; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index d539ca9b8..dfff59005 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -27,14 +27,19 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro return `<${fieldType.name} fieldKey={'${fieldStr}'} label={'${label}'} {...props} />`; //e.g., "" } private dropDisposer?: DragManager.DragDropDisposer; - + private _timeout: any; componentDidMount() { this.props.setContentView?.(this); } + componentWillUnMount() { + this._timeout && clearTimeout(this._timeout); + } getTitle() { - return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label : - typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); + return this.rootDoc["title-custom"] ? StrCast(this.rootDoc.title) : + this.props.label ? this.props.label : + typeof this.rootDoc[this.fieldKey] === "string" ? StrCast(this.rootDoc[this.fieldKey]) : + StrCast(this.rootDoc.title); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -73,8 +78,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro @observable _mouseOver = false; @computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; } - fitTextToBox = (r: any) => { - BigText(r, { + fitTextToBox = (r: any): any => { + const params = { rotateText: null, fontSizeFactor: 1, minimumFontSize: NumCast(this.layoutDoc._minFontSize), @@ -83,11 +88,25 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro horizontalAlign: "center", verticalAlign: "center", textAlign: "center", - whiteSpace: "nowrap" - }); + whiteSpace: this.layoutDoc._singleLine ? "nowrap" : "pre-wrap" + }; + this._timeout = undefined; + if (!r) return params; + if (!r.offsetHeight || !r.offsetWidth) return this._timeout = setTimeout(() => this.fitTextToBox(r)); + const parent = r.parentNode; + const parentStyle = parent.style; + parentStyle.display = ""; + parentStyle.alignItems = ""; + r.setAttribute("style", ""); + r.style.width = this.layoutDoc._singleLine ? "" : "100%"; + + r.style.textOverflow = "ellipsis"; + r.style.overflow = "hidden"; + BigText(r, params); } // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { + this.fitTextToBox(null);// this causes mobx to trigger re-render when data changes const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... @@ -104,16 +123,17 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit", letterSpacing: StrCast(this.layoutDoc.letterSpacing), textTransform: StrCast(this.layoutDoc.textTransform) as any, + paddingLeft: NumCast(this.rootDoc._xPadding), + paddingRight: NumCast(this.rootDoc._xPadding), + paddingTop: NumCast(this.rootDoc._yPadding), + paddingBottom: NumCast(this.rootDoc._yPadding), width: this.props.PanelWidth(), height: this.props.PanelHeight(), whiteSpace: this.layoutDoc._singleLine ? "pre" : "pre-wrap" }} > - { - if (r) { - if (!r.offsetWidth || !r.offsetHeight) setTimeout(() => this.fitTextToBox(r)); - else this.fitTextToBox(r); - } - }}>{label.startsWith("#") ? (null) : label} + this.fitTextToBox(r))}> + {label.startsWith("#") ? (null) : label} +
{!missingParams?.length ? (null) : missingParams.map(m =>
{m}
)} -- cgit v1.2.3-70-g09d2 From 32a7dfc6eae2def8d1b792edcb550316948f6ab0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Apr 2022 16:22:46 -0400 Subject: more cleanup --- src/client/documents/Documents.ts | 2 + src/client/util/CurrentUserUtils.ts | 80 +++++++++++++------------------------ 2 files changed, 30 insertions(+), 52 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 741868a99..dd3e13cfd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -156,6 +156,8 @@ export class DocumentOptions { _timelineLabel?: boolean; // whether the document exists on a timeline "_carousel-caption-xMargin"?: number; "_carousel-caption-yMargin"?: number; + "icon-nativeWidth"?: number; + "icon-nativeHeight"?: number; x?: number; y?: number; z?: number; // whether document is in overlay (1) or not (0 or undefined) diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7948aa70b..57313cecc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -290,65 +290,41 @@ export class CurrentUserUtils { static setupDefaultIconTemplates(doc: Doc) { const templateIconsDoc = Cast(doc["template-icons"], Doc, null); - const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc, finalize: (icon: Doc) => Doc = (doc: Doc) => doc) => { - const iconFieldName = type ? "icon_" + type : "icon"; - if (templateIconsDoc?.[iconFieldName] === undefined) { - const template = finalize(MakeTemplate(iconTemplate(), true, iconFieldName, templateField)); + const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => { + const iconFieldName = "icon" + (type ? "_" + type : ""); + if (!templateIconsDoc?.[iconFieldName]) { + const template = MakeTemplate(iconTemplate(), true, iconFieldName, templateField); if (templateIconsDoc) { templateIconsDoc[iconFieldName] = new PrefetchProxy(template); Doc.AddDocToList(templateIconsDoc, "data", template); + } else { + return template; } - return template; } - return templateIconsDoc[iconFieldName] as Doc; - }; - const finalizeIconTemplate = (icon: Doc) => { - const iconProto = Doc.GetProto(icon); - iconProto["icon-nativeWidth"] = iconProto.width; - iconProto["icon-nativeHeight"] = iconProto.height; - return icon; }; - const iconList = [ - makeIconTemplate(undefined, "title", () => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _backgroundColor: "dimgray", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.AUDIO, "title", () => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _backgroundColor: "lightgreen", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.RTF, "text", () => Docs.Create.LabelDocument({ - _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.PDF, "title", () => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _backgroundColor: "pink", _singleLine: false, _minFontSize: 10, _maxFontSize: 24, borderRounding: "5px", - _width: 75, _height: 125, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ - _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, - onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.IMG, "data", () => Docs.Create.ImageDocument("", { - _width: 150, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.COL, "icon", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { - _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - }), finalizeIconTemplate), - makeIconTemplate(DocumentType.VID, "icon", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { - _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - }), finalizeIconTemplate), - // makeIconTemplate(DocumentType.PDF, "data", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { - // _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - // }), setIconProtoFields) - ]; + const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); + const labelBox = (extra: object) => Docs.Create.LabelDocument({ + textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, system: true, onClick: deiconifyScript(), ...extra, + }); + const imageBox = (url: string, extra: object) => Docs.Create.ImageDocument(url, { + "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, + _showTitle: "title", system: true, onClick: deiconifyScript(), ...extra + }); + const newIconsList = [ + makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), + makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), + makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), + makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), + makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), + makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })), + // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {}), finalizeImageTemplate) + ].filter(d => d).map(d => d!); if (!templateIconsDoc) { - doc["template-icons"] = Docs.Create.TreeDocument(iconList, { title: "icon templates", _height: 75, system: true }); - iconList.map(ntype => (doc["template-icons"] as Doc)[StrCast(ntype.title)] = new PrefetchProxy(ntype)); + doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true }); + newIconsList.map(d => (doc["template-icons"] as Doc)[StrCast(d.title)] = new PrefetchProxy(d)); } } -- cgit v1.2.3-70-g09d2 From 45062251a0ee91c5ece36141730a75dde03814dd Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Apr 2022 16:35:05 -0400 Subject: fixed creating collection icons to capture pfs (and other things with canvases) --- src/client/util/CurrentUserUtils.ts | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 30 ++++++++++++++++++---- src/client/views/nodes/PDFBox.tsx | 1 + 3 files changed, 27 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 57313cecc..03c63e737 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -320,7 +320,7 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })), - // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {}), finalizeImageTemplate) + // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) ].filter(d => d).map(d => d!); if (!templateIconsDoc) { doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3f72052ae..452a6bfcf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1422,12 +1422,32 @@ export class CollectionFreeFormView extends CollectionSubView { + if (oldDiv.childNodes) { + for (let i = 0; i < oldDiv.childNodes.length; i++) { + this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); + } + } + if (oldDiv instanceof HTMLCanvasElement) { + const canvas = oldDiv; + const img = document.createElement('img'); // create a Image Element + img.src = canvas.toDataURL(); //image source + img.style.width = canvas.style.width; + img.style.height = canvas.style.height; + const newCan = newDiv as HTMLCanvasElement; + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(img); + } + } + updateIcon = () => { - this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString(); - this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString(); - const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); - this.props.docViewPath().lastElement().ContentDiv!.style.width = ""; - this.props.docViewPath().lastElement().ContentDiv!.style.height = ""; + const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; + newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); + newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + this.replaceCanvases(docViewContent, newDiv); + const htmlString = this._mainCont && new XMLSerializer().serializeToString(newDiv); const nativeWidth = this.layoutDoc[WidthSym](); const nativeHeight = this.layoutDoc[HeightSym](); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 668c6c8fc..f386c7bbd 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -290,6 +290,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { const funcs: ContextMenuProps[] = []; funcs.push({ description: "Copy path", event: () => this.pdfUrl && Utils.CopyText(Utils.prepend("") + this.pdfUrl.url.pathname), icon: "expand-arrows-alt" }); + funcs.push({ description: "update icon", event: () => this.pdfUrl && this.updateIcon(), icon: "expand-arrows-alt" }); //funcs.push({ description: "Toggle Sidebar ", event: () => this.toggleSidebar(), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } -- cgit v1.2.3-70-g09d2 From a6aec8759298fba41b486a973796662056f47da0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Apr 2022 17:05:10 -0400 Subject: changed the way labelBox splits words to allow .&- added icon for webBox --- src/client/util/CurrentUserUtils.ts | 1 + .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/DocumentView.scss | 6 ++++-- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/WebBoxRenderer.js | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 03c63e737..9720343e1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -315,6 +315,7 @@ export class CurrentUserUtils { makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), + makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 452a6bfcf..5d1e1892c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1454,7 +1454,7 @@ export class CollectionFreeFormView extends CollectionSubView this.fitTextToBox(r))}> - {label.startsWith("#") ? (null) : label} + {label.startsWith("#") ? (null) : label.replace(/([\.\-&])/g, "$1 ")}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index f386c7bbd..69662d53a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -87,7 +87,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent/g, ""), width, height, scroll); + const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(//g, ""), width, height, scroll); return val; } -- cgit v1.2.3-70-g09d2 From 1eb2c362b020b3cbe446bbc1585108129fda6977 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 01:11:54 -0400 Subject: Get storing pres data working, but is choppy due to mobx usage. --- src/client/apis/recording/recordingApi.ts | 300 -------------------- src/client/util/RecordingApi.ts | 303 +++++++++++++++++++++ src/client/views/Main.tsx | 2 + .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 14 +- .../views/nodes/RecordingBox/RecordingView.tsx | 26 +- src/client/views/nodes/VideoBox.tsx | 14 + 7 files changed, 348 insertions(+), 316 deletions(-) delete mode 100644 src/client/apis/recording/recordingApi.ts create mode 100644 src/client/util/RecordingApi.ts (limited to 'src/client/util') diff --git a/src/client/apis/recording/recordingApi.ts b/src/client/apis/recording/recordingApi.ts deleted file mode 100644 index b57e675b7..000000000 --- a/src/client/apis/recording/recordingApi.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { CollectionFreeFormView } from "../../views/collections/collectionFreeForm"; -import React, { useState } from "react"; -import { IReactionDisposer, observable, reaction } from "mobx"; -import { NumCast } from "../../../fields/Types"; - -type Movement = { - time: number, - panX: number, - panY: number, -} - -type Presentation = { - movements: Array - meta: Object, - startDate: Date | null, -} - -export class RecordingApi { - - private static NULL_PRESENTATION: Presentation = { - movements: [], - meta: {}, - startDate: null, - } - - // instance variables - private currentPresentation: Presentation; - private isRecording: boolean; - private absoluteStart: number; - - - // create static instance and getter for global use - @observable static _instance: RecordingApi; - public static get instance(): RecordingApi { return RecordingApi._instance } - public constructor() { - // init the global instance - RecordingApi._instance = this; - - // init the instance variables - this.currentPresentation = RecordingApi.NULL_PRESENTATION - this.isRecording = false; - this.absoluteStart = -1; - - // used for tracking movements in the view frame - this.disposeFunc = null; - - // for now, set playFFView - this.playFFView = null; - this.timers = null; - } - - // little helper :) - private get isInitPresenation(): boolean { - return this.currentPresentation.startDate === null - } - - public start = (view: CollectionFreeFormView, meta?: Object): Error | undefined => { - // check if already init a presentation - if (!this.isInitPresenation) { - console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') - return new Error('[recordingApi.ts] start()') - } - - // (1a) get start date for presenation - const startDate = new Date() - // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime() - - // (2) assign meta content if it exists - this.currentPresentation.meta = meta || {} - // (3) assign start date to currentPresenation - this.currentPresentation.startDate = startDate - // (4) set isRecording true to allow trackMovements - this.isRecording = true - } - - public clear = (): Error | undefined => { - // TODO: maybe archive the data? - if (this.isRecording) { - console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() or finish() first') - return new Error('[recordingApi.ts] clear()') - } - - // clear presenation data - this.currentPresentation = RecordingApi.NULL_PRESENTATION - // clear isRecording - this.isRecording = false - // clear absoluteStart - this.absoluteStart = -1 - - // clear the disposeFunc - this.disposeFunc = null - } - - public pause = (): Error | undefined => { - if (this.currentPresentation.startDate === null) { - console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - return new Error('[recordingApi.ts] pause(): no presenation') - } - // don't allow track movments - this.isRecording = false - - // set relativeStart to the pausedTimestamp - const timestamp = new Date().getTime() - this.absoluteStart = timestamp - } - - public resume = () => { - const timestamp = new Date().getTime() - const startTimestamp = this.currentPresentation.startDate?.getTime() - if (!startTimestamp) { - console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') - return new Error('[recordingApi.ts] pause()') - } - - // update absoluteStart to bridge the paused time - const absoluteTimePaused = timestamp - this.absoluteStart - this.absoluteStart = absoluteTimePaused - } - - public finish = (): Error | Presentation => { - if (this.isInitPresenation) { - console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first') - return new Error('[recordingApi.ts] finish()') - } - - // return a copy of the the presentation data - return { ...this.currentPresentation } - } - - private trackMovements = (panX: number, panY: number): Error | undefined => { - // ensure we are recording - if (!this.isRecording) { - console.error('[recordingApi.ts] pause() failed: recording is paused()') - return new Error('[recordingApi.ts] pause()') - } - - // get the relative time - const timestamp = new Date().getTime() - const relativeTime = timestamp - this.absoluteStart - - // make new movement struct - const movement: Movement = { time: relativeTime, panX, panY } - - // add that movement to the current presentation data's movement array - this.currentPresentation.movements.push(movement) - } - - // instance variable for the FFView - private disposeFunc: IReactionDisposer | null; - - // set the FFView that will be used in a reaction to track the movements - public setRecordingFFView = (view: CollectionFreeFormView): void => { - // set the view to the current view - if (view === null) return; - - //this.recordingFFView = view; - // set the reaction to track the movements - this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }), - (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y) - ) - - // for now, set the most recent recordingFFView to the playFFView - this.playFFView = view; - } - - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; - } - - // TODO: extract this into different class with pause and resume recording - private playFFView: CollectionFreeFormView | null; - private timers: Timer[] | null; - - public followMovements = (presentation: Presentation): undefined | Error => { - if (presentation.startDate === null || this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - } - - const document = this.playFFView.Document - const { movements } = presentation - this.timers = movements.map(movement => { - const { panX, panY, time } = movement - return new Timer(() => { - document._panX = panX; - document._panY = panY; - // TODO: consider cleaning this array to null or some state - }, time) - }) - } - - public pauseMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] pauseMovements() failed: no view') - } - // TODO: set userdoc presentMode to browsing - this.timers?.forEach(timer => timer.pause()) - } - - public resumeMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] resumeMovements() failed: no view') - } - this.timers?.forEach(timer => timer.resume()) - } - - // public followMovements = (presentation: Presentation): undefined | Error => { - // if (presentation.startDate === null || this.playFFView === null) { - // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - // } - - // const document = this.playFFView.Document - // const { movements } = presentation - // movements.forEach(movement => { - // const { panX, panY, time } = movement - // // set the pan to what was stored - // setTimeout(() => { - // document._panX = panX; - // document._panY = panY; - // }, time) - // }) - // } - // Unfinished code for tracing multiple free form views - // export let pres: Map = new Map() - - // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { - // pres.set(ffView, - // reaction(() => ({ x: ffView.panX, y: ffView.panY }), - // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) - // ) - // } - - // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { - // const disposer = pres.get(ffView); - // disposer?.(); - // pres.delete(ffView) - // } -} - -/** Represents the `setTimeout` with an ability to perform pause/resume actions - * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout - */ -export class Timer { - private _start: Date; - private _remaining: number; - private _durationTimeoutId?: NodeJS.Timeout; - private _callback: (...args: any[]) => void; - private _done = false; - get done () { - return this._done; - } - - public constructor(callback: (...args: any[]) => void, ms = 0) { - this._callback = () => { - callback(); - this._done = true; - }; - this._remaining = ms; - this.resume(); - } - - /** pauses the timer */ - public pause(): Timer { - if (this._durationTimeoutId && !this._done) { - this._clearTimeoutRef(); - this._remaining -= new Date().getTime() - this._start.getTime(); - } - return this; - } - - /** resumes the timer */ - public resume(): Timer { - if (!this._durationTimeoutId && !this._done) { - this._start = new Date; - this._durationTimeoutId = setTimeout(this._callback, this._remaining); - } - return this; - } - - /** - * clears the timeout and marks it as done. - * - * After called, the timeout will not resume - */ - public clearTimeout() { - this._clearTimeoutRef(); - this._done = true; - } - - private _clearTimeoutRef() { - if (this._durationTimeoutId) { - clearTimeout(this._durationTimeoutId); - this._durationTimeoutId = undefined; - } - } - -} \ No newline at end of file diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts new file mode 100644 index 000000000..c4f76282c --- /dev/null +++ b/src/client/util/RecordingApi.ts @@ -0,0 +1,303 @@ +import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; +import { IReactionDisposer, observable, reaction } from "mobx"; +import { NumCast } from "../../fields/Types"; + +type Movement = { + time: number, + panX: number, + panY: number, +} + +type Presentation = { + movements: Array + meta: Object, + startDate: Date | null, +} + +export class RecordingApi { + + private static NULL_PRESENTATION: Presentation = { + movements: [], + meta: {}, + startDate: null, + } + + // instance variables + private currentPresentation: Presentation; + private isRecording: boolean; + private absoluteStart: number; + + + // create static instance and getter for global use + @observable static _instance: RecordingApi; + public static get Instance(): RecordingApi { return RecordingApi._instance } + public constructor() { + // init the global instance + RecordingApi._instance = this; + + // init the instance variables + this.currentPresentation = RecordingApi.NULL_PRESENTATION + this.isRecording = false; + this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; + + // for now, set playFFView + this.playFFView = null; + this.timers = null; + } + + // little helper :) + private get isInitPresenation(): boolean { + return this.currentPresentation.startDate === null + } + + public start = (meta?: Object): Error | undefined => { + // check if already init a presentation + if (!this.isInitPresenation) { + console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') + return new Error('[recordingApi.ts] start()') + } + + // (1a) get start date for presenation + const startDate = new Date() + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime() + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {} + // (3) assign start date to currentPresenation + this.currentPresentation.startDate = startDate + // (4) set isRecording true to allow trackMovements + this.isRecording = true + } + + public clear = (): Error | Presentation => { + // TODO: maybe archive the data? + if (this.isRecording) { + console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first') + return new Error('[recordingApi.ts] clear()') + } + + const presCopy = { ...this.currentPresentation } + + // clear presenation data + this.currentPresentation = RecordingApi.NULL_PRESENTATION + // clear isRecording + this.isRecording = false + // clear absoluteStart + this.absoluteStart = -1 + // clear the disposeFunc + this.disposeFunc = null + + return presCopy; + } + + public pause = (): Error | undefined => { + if (this.currentPresentation.startDate === null) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + return new Error('[recordingApi.ts] pause(): no presenation') + } + // don't allow track movments + this.isRecording = false + + // set relativeStart to the pausedTimestamp + const timestamp = new Date().getTime() + this.absoluteStart = timestamp + } + + public resume = () => { + const timestamp = new Date().getTime() + const startTimestamp = this.currentPresentation.startDate?.getTime() + if (!startTimestamp) { + console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') + return new Error('[recordingApi.ts] pause()') + } + + // update absoluteStart to bridge the paused time + const absoluteTimePaused = timestamp - this.absoluteStart + this.absoluteStart = absoluteTimePaused + } + + // public finish = (): Error | Presentation => { + // if (this.isInitPresenation) { + // console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first') + // return new Error('[recordingApi.ts] finish()') + // } + + // // return a copy of the the presentation data + // return { ...this.currentPresentation } + // } + + private trackMovements = (panX: number, panY: number): Error | undefined => { + // ensure we are recording + if (!this.isRecording) { + console.error('[recordingApi.ts] pause() failed: recording is paused()') + return new Error('[recordingApi.ts] pause()') + } + + // get the relative time + const timestamp = new Date().getTime() + const relativeTime = timestamp - this.absoluteStart + + // make new movement struct + const movement: Movement = { time: relativeTime, panX, panY } + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements.push(movement) + } + + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === null || view === this.playFFView) return; + + //this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }), + (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y) + ) + + // for now, set the most recent recordingFFView to the playFFView + this.playFFView = view; + } + + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; + } + + // TODO: extract this into different class with pause and resume recording + private playFFView: CollectionFreeFormView | null; + private timers: Timer[] | null; + + // public followMovements = (presentation: Presentation): undefined | Error => { + // console.log(presentation) + // if (presentation.startDate === null || this.playFFView === null) { + // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + // } + + // const document = this.playFFView.Document + // const { movements } = presentation + // this.timers = movements.map(movement => { + // const { panX, panY, time } = movement + // return new Timer(() => { + // document._panX = panX; + // document._panY = panY; + // // TODO: consider cleaning this array to null or some state + // }, time) + // }) + // } + + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view') + } + // TODO: set userdoc presentMode to browsing + this.timers?.forEach(timer => timer.pause()) + } + + public resumeMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] resumeMovements() failed: no view') + } + this.timers?.forEach(timer => timer.resume()) + } + + public followMovements = (presentation: Presentation): undefined | Error => { + if (presentation.startDate === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + } + + const document = this.playFFView.Document + const { movements } = presentation + movements.forEach(movement => { + const { panX, panY, time } = movement + // set the pan to what was stored + setTimeout(() => { + document._panX = panX; + document._panY = panY; + }, time) + }) + } + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } +} + +/** Represents the `setTimeout` with an ability to perform pause/resume actions + * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout + */ +export class Timer { + private _start: Date; + private _remaining: number; + private _durationTimeoutId?: NodeJS.Timeout; + private _callback: (...args: any[]) => void; + private _done = false; + get done () { + return this._done; + } + + public constructor(callback: (...args: any[]) => void, ms = 0) { + this._callback = () => { + callback(); + this._done = true; + }; + this._remaining = ms; + this.resume(); + } + + /** pauses the timer */ + public pause(): Timer { + if (this._durationTimeoutId && !this._done) { + this._clearTimeoutRef(); + this._remaining -= new Date().getTime() - this._start.getTime(); + } + return this; + } + + /** resumes the timer */ + public resume(): Timer { + if (!this._durationTimeoutId && !this._done) { + this._start = new Date; + this._durationTimeoutId = setTimeout(this._callback, this._remaining); + } + return this; + } + + /** + * clears the timeout and marks it as done. + * + * After called, the timeout will not resume + */ + public clearTimeout() { + this._clearTimeoutRef(); + this._done = true; + } + + private _clearTimeoutRef() { + if (this._durationTimeoutId) { + clearTimeout(this._durationTimeoutId); + this._durationTimeoutId = undefined; + } + } + +} \ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 8560ccb29..517fe097c 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -8,6 +8,7 @@ import { AssignAllExtensions } from "../../extensions/General/Extensions"; import { Docs } from "../documents/Documents"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { LinkManager } from "../util/LinkManager"; +import { RecordingApi } from "../util/RecordingApi"; import { CollectionView } from "./collections/CollectionView"; import { MainView } from "./MainView"; @@ -36,5 +37,6 @@ AssignAllExtensions(); const expires = "expires=" + d.toUTCString(); document.cookie = `loadtime=${loading};${expires};path=/`; new LinkManager(); + new RecordingApi; ReactDOM.render(, document.getElementById('root')); })(); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 35bd9cf79..f74e526b6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -54,7 +54,7 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import e = require("connect-flash"); -import { RecordingApi } from "../../../apis/recording/RecordingApi"; +import { RecordingApi } from "../../../util/RecordingApi"; export const panZoomSchema = createSchema({ _panX: "number", @@ -965,6 +965,9 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(doc => doc instanceof Doc); diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 86358e838..1b17476f7 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -1,7 +1,7 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { AudioField, VideoField } from "../../../../fields/URLField"; +import { VideoField } from "../../../../fields/URLField"; import { Upload } from "../../../../server/SharedMediaTypes"; import { ViewBoxBaseComponent } from "../../DocComponent"; import { FieldView } from "../FieldView"; @@ -9,6 +9,8 @@ import { VideoBox } from "../VideoBox"; import { RecordingView } from './RecordingView'; import { DocumentType } from "../../../documents/DocumentTypes"; +import { RecordingApi } from "../../../util/RecordingApi"; + @observer export class RecordingBox extends ViewBoxBaseComponent() { @@ -31,17 +33,19 @@ export class RecordingBox extends ViewBoxBaseComponent() { @action setResult = (info: Upload.FileInformation) => { - console.log("Setting result to " + info) + // console.log("Setting result to " + info) this.result = info - console.log(this.result.accessPaths.agnostic.client) + // console.log(this.result.accessPaths.agnostic.client) this.dataDoc.type = DocumentType.VID; - console.log(this.videoDuration) + // console.log(this.videoDuration) this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration; this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey); // this.dataDoc.nativeWidth = this.dataDoc.nativeHeight = undefined; // this.layoutDoc._fitWidth = undefined; - this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.agnostic.client); + this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.agnostic.client); + // stringify the presenation and store it + this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(RecordingApi.Instance.clear()); } render() { diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index d99492095..870ef87d7 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -8,7 +8,7 @@ import { IconContext } from "react-icons"; import { Networking } from '../../../Network'; import { Upload } from '../../../../server/SharedMediaTypes'; -import { RecordingApi } from '../../../apis/recording/RecordingApi'; +import { RecordingApi } from '../../../util/RecordingApi'; enum RecordingStatus { @@ -57,11 +57,11 @@ export function RecordingView(props: IRecordingViewProps) { height: 720, }, // audio: true, - // audio: { - // echoCancellation: true, - // noiseSuppression: true, - // sampleRate: 44100 - // } + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 44100 + } } useEffect(() => { @@ -159,7 +159,10 @@ export function RecordingView(props: IRecordingViewProps) { // } videoRecorder.current.onstart = (event: any) => { - setRecording(true); + setRecording(true); + // TODO: update names + // RecordingApi.Instance.clear(); + RecordingApi.Instance.start(); } videoRecorder.current.onstop = () => { @@ -172,7 +175,8 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] setRecording(false); - setFinished(true); + setFinished(true); + RecordingApi.Instance.pause(); } // recording paused @@ -182,12 +186,14 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] - setRecording(false); + setRecording(false); + RecordingApi.Instance.pause(); } videoRecorder.current.onresume = async (event: any) => { await startShowingStream(); - setRecording(true); + setRecording(true); + RecordingApi.Instance.resume(); } videoRecorder.current.start(200) diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index e57cb1abe..7364a64d9 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -28,6 +28,7 @@ import { AnchorMenu } from "../pdf/AnchorMenu"; import { StyleProp } from "../StyleProvider"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; +import { RecordingApi } from "../../util/RecordingApi"; const path = require('path'); @@ -117,6 +118,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + // if presentation isn't null, call followmovements on the recording api + if (this.presentation) { + const err = RecordingApi.Instance.followMovements(this.presentation); + if (err) console.log(err); + } + + this._playing = true; const eleTime = this.player?.currentTime || 0; if (this.timeline) { -- cgit v1.2.3-70-g09d2 From 91746b4e9c570a7579e3396e2e31c73d8fa9915e Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 01:32:27 -0400 Subject: updated to subtracting timestamps for better smoothness --- src/client/util/RecordingApi.ts | 27 +++++++++++----------- .../views/nodes/RecordingBox/RecordingView.tsx | 2 ++ 2 files changed, 15 insertions(+), 14 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index c4f76282c..ab7642a90 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -11,7 +11,7 @@ type Movement = { type Presentation = { movements: Array meta: Object, - startDate: Date | null, + startTime: number | null, } export class RecordingApi { @@ -19,7 +19,7 @@ export class RecordingApi { private static NULL_PRESENTATION: Presentation = { movements: [], meta: {}, - startDate: null, + startTime: null, } // instance variables @@ -50,7 +50,7 @@ export class RecordingApi { // little helper :) private get isInitPresenation(): boolean { - return this.currentPresentation.startDate === null + return this.currentPresentation.startTime === null } public start = (meta?: Object): Error | undefined => { @@ -63,12 +63,12 @@ export class RecordingApi { // (1a) get start date for presenation const startDate = new Date() // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime() + // this.absoluteStart = startDate.getTime() // (2) assign meta content if it exists this.currentPresentation.meta = meta || {} // (3) assign start date to currentPresenation - this.currentPresentation.startDate = startDate + this.currentPresentation.startTime = startDate.getTime() // (4) set isRecording true to allow trackMovements this.isRecording = true } @@ -89,15 +89,15 @@ export class RecordingApi { // clear absoluteStart this.absoluteStart = -1 // clear the disposeFunc - this.disposeFunc = null + this.removeRecordingFFView() return presCopy; } public pause = (): Error | undefined => { - if (this.currentPresentation.startDate === null) { + if (this.isInitPresenation) { console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - return new Error('[recordingApi.ts] pause(): no presenation') + return new Error('[recordingApi.ts] pause(): no presentation') } // don't allow track movments this.isRecording = false @@ -109,7 +109,7 @@ export class RecordingApi { public resume = () => { const timestamp = new Date().getTime() - const startTimestamp = this.currentPresentation.startDate?.getTime() + const startTimestamp = this.currentPresentation.startTime if (!startTimestamp) { console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') return new Error('[recordingApi.ts] pause()') @@ -137,12 +137,11 @@ export class RecordingApi { return new Error('[recordingApi.ts] pause()') } - // get the relative time + // get the time const timestamp = new Date().getTime() - const relativeTime = timestamp - this.absoluteStart // make new movement struct - const movement: Movement = { time: relativeTime, panX, panY } + const movement: Movement = { time: timestamp, panX, panY } // add that movement to the current presentation data's movement array this.currentPresentation.movements.push(movement) @@ -211,7 +210,7 @@ export class RecordingApi { } public followMovements = (presentation: Presentation): undefined | Error => { - if (presentation.startDate === null || this.playFFView === null) { + if (presentation.startTime === null || this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } @@ -223,7 +222,7 @@ export class RecordingApi { setTimeout(() => { document._panX = panX; document._panY = panY; - }, time) + }, time - presentation.startTime) }) } // Unfinished code for tracing multiple free form views diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 870ef87d7..51cc44941 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -176,6 +176,7 @@ export function RecordingView(props: IRecordingViewProps) { videoChunks = [] setRecording(false); setFinished(true); + console.log("finished recording") RecordingApi.Instance.pause(); } @@ -187,6 +188,7 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] setRecording(false); + console.log("paused recording") RecordingApi.Instance.pause(); } -- cgit v1.2.3-70-g09d2 From 4dc4b0939d4e4afbc9f6db999ff80d434ef4ccc6 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 02:00:55 -0400 Subject: Added the presentation mode back in. However, pres isn't being recreated smoothly after being stored in the db. --- src/client/util/RecordingApi.ts | 21 +++++++++++++++++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 ++++-- .../views/nodes/RecordingBox/RecordingView.tsx | 3 --- 3 files changed, 21 insertions(+), 9 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index ab7642a90..b53cba79d 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -1,6 +1,7 @@ import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; +import { Doc } from "../../fields/Doc"; type Movement = { time: number, @@ -42,6 +43,7 @@ export class RecordingApi { // used for tracking movements in the view frame this.disposeFunc = null; + this.recordingFFView = null; // for now, set playFFView this.playFFView = null; @@ -60,6 +62,9 @@ export class RecordingApi { return new Error('[recordingApi.ts] start()') } + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording' + // (1a) get start date for presenation const startDate = new Date() // (1b) set start timestamp to absolute timestamp @@ -80,6 +85,9 @@ export class RecordingApi { return new Error('[recordingApi.ts] clear()') } + // update the presentation mode + Doc.UserDoc().presentationMode = 'none' + const presCopy = { ...this.currentPresentation } // clear presenation data @@ -133,8 +141,8 @@ export class RecordingApi { private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { - console.error('[recordingApi.ts] pause() failed: recording is paused()') - return new Error('[recordingApi.ts] pause()') + console.error('[recordingApi.ts] trackMovements() failed: recording is paused()') + return new Error('[recordingApi.ts] trackMovements()') } // get the time @@ -149,11 +157,12 @@ export class RecordingApi { // instance variable for the FFView private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; // set the FFView that will be used in a reaction to track the movements public setRecordingFFView = (view: CollectionFreeFormView): void => { // set the view to the current view - if (view === null || view === this.playFFView) return; + if (view === this.recordingFFView || view === null) return; //this.recordingFFView = view; // set the reaction to track the movements @@ -163,7 +172,7 @@ export class RecordingApi { ) // for now, set the most recent recordingFFView to the playFFView - this.playFFView = view; + this.recordingFFView = view; } // call on dispose function to stop tracking movements @@ -194,6 +203,10 @@ export class RecordingApi { // }) // } + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view + } + public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f74e526b6..8bcf6f46f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -965,8 +965,10 @@ export class CollectionFreeFormView extends CollectionSubView { setRecording(true); - // TODO: update names // RecordingApi.Instance.clear(); RecordingApi.Instance.start(); } @@ -176,7 +175,6 @@ export function RecordingView(props: IRecordingViewProps) { videoChunks = [] setRecording(false); setFinished(true); - console.log("finished recording") RecordingApi.Instance.pause(); } @@ -188,7 +186,6 @@ export function RecordingView(props: IRecordingViewProps) { // reset the temporary chunks videoChunks = [] setRecording(false); - console.log("paused recording") RecordingApi.Instance.pause(); } -- cgit v1.2.3-70-g09d2 From a2173243c7a447527e2e86ec0a00998ed8b9cc40 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 03:30:07 -0400 Subject: Get play and pause to work. There is a bug with getting double called that was causing choppiness. --- src/client/util/RecordingApi.ts | 207 ++++++++++++++------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 10 +- 3 files changed, 148 insertions(+), 71 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index b53cba79d..dfe8e98ca 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -183,11 +183,10 @@ export class RecordingApi { // TODO: extract this into different class with pause and resume recording private playFFView: CollectionFreeFormView | null; - private timers: Timer[] | null; + private timers: NodeJS.Timeout[] | null; - // public followMovements = (presentation: Presentation): undefined | Error => { - // console.log(presentation) - // if (presentation.startDate === null || this.playFFView === null) { + // public loadPresentation = (presentation: Presentation): undefined | Error => { + // if (presentation.startTime === null || this.playFFView === null) { // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') // } @@ -195,48 +194,85 @@ export class RecordingApi { // const { movements } = presentation // this.timers = movements.map(movement => { // const { panX, panY, time } = movement - // return new Timer(() => { + + // const t = new Timer(() => { // document._panX = panX; // document._panY = panY; // // TODO: consider cleaning this array to null or some state - // }, time) + // }, time - presentation.startTime) + // t.pause() + // return t // }) + // console.log(this.timers) // } public setPlayFFView = (view: CollectionFreeFormView): void => { this.playFFView = view } + // public pauseMovements = (): undefined | Error => { + // console.log('[recordingApi.ts] pauseMovements()') + // if (this.playFFView === null) { + // return new Error('[recordingApi.ts] pauseMovements() failed: no view') + // } + // // TODO: set userdoc presentMode to browsing + // this.timers?.forEach(timer => timer.pause()) + // } + public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') } - // TODO: set userdoc presentMode to browsing - this.timers?.forEach(timer => timer.pause()) - } - public resumeMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] resumeMovements() failed: no view') + if (!this._isPlaying) { + return new Error('[recordingApi.ts] pauseMovements() failed: not playing') } - this.timers?.forEach(timer => timer.resume()) + // TODO: set userdoc presentMode to browsing + //console.log('cleared timers', this.timers) + console.log(this.timers?.map(timer => clearTimeout(timer))) + console.log('[recordingApi.ts] pauseMovements()') + + this._isPlaying = false } - public followMovements = (presentation: Presentation): undefined | Error => { + // public resumeMovements = (): undefined | Error => { + // if (this.playFFView === null) { + // return new Error('[recordingApi.ts] resumeMovements() failed: no view') + // } + // console.log('resume') + // this.timers?.forEach(timer => timer.resume()) + // } + + private _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0): undefined | Error => { if (presentation.startTime === null || this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } + if (this._isPlaying) { + return new Error('[recordingApi.ts] playMovements() failed: already playing') + } + this._isPlaying = true; + + console.log(timeViewed) + const document = this.playFFView.Document const { movements } = presentation - movements.forEach(movement => { + this.timers = movements.reduce((arr: NodeJS.Timeout[], movement) => { const { panX, panY, time } = movement + const absoluteTime = time - presentation.startTime - timeViewed*1000 + if (absoluteTime < 0) return arr; + // set the pan to what was stored - setTimeout(() => { + arr.push(setTimeout(() => { document._panX = panX; document._panY = panY; - }, time - presentation.startTime) - }) + }, absoluteTime)) + return arr; + }, []) + + console.log(this.timers.length) } // Unfinished code for tracing multiple free form views // export let pres: Map = new Map() @@ -258,58 +294,95 @@ export class RecordingApi { /** Represents the `setTimeout` with an ability to perform pause/resume actions * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout */ -export class Timer { - private _start: Date; - private _remaining: number; - private _durationTimeoutId?: NodeJS.Timeout; - private _callback: (...args: any[]) => void; - private _done = false; - get done () { - return this._done; +class Timer { + private timerId: NodeJS.Timeout | null; + private callback: (...args: any[]) => void + private start: number + private remaining: number; + + constructor(callback: (...args: any[]) => void, delay: number) { + this.callback = callback; + this.remaining = delay; + + this.start = Date.now(); + this.timerId = setTimeout(this.callback, this.remaining); } - public constructor(callback: (...args: any[]) => void, ms = 0) { - this._callback = () => { - callback(); - this._done = true; - }; - this._remaining = ms; - this.resume(); - } - - /** pauses the timer */ - public pause(): Timer { - if (this._durationTimeoutId && !this._done) { - this._clearTimeoutRef(); - this._remaining -= new Date().getTime() - this._start.getTime(); - } - return this; - } + public pause = () => { + console.log('[timer.ts] pause()') + this.timerId !== null && clearTimeout(this.timerId); + this.timerId = null; + this.remaining -= (Date.now() - this.start); + }; - /** resumes the timer */ - public resume(): Timer { - if (!this._durationTimeoutId && !this._done) { - this._start = new Date; - this._durationTimeoutId = setTimeout(this._callback, this._remaining); + public resume = () => { + if (this.timerId) { + return; } - return this; - } - /** - * clears the timeout and marks it as done. - * - * After called, the timeout will not resume - */ - public clearTimeout() { - this._clearTimeoutRef(); - this._done = true; - } + this.start = Date.now(); + this.timerId = setTimeout(this.callback, this.remaining); + }; - private _clearTimeoutRef() { - if (this._durationTimeoutId) { - clearTimeout(this._durationTimeoutId); - this._durationTimeoutId = undefined; - } - } - -} \ No newline at end of file + public clear = () => { + console.log('[timer.ts] clear()') + this.timerId !== null && clearTimeout(this.timerId); + // this.timerId = null; + // this.remaining -= (Date.now() - this.start); + }; +} +// class Timer { +// private _start: Date; +// private _remaining: number; +// private _durationTimeoutId?: NodeJS.Timeout; +// private _callback: (...args: any[]) => void; +// private _done = false; +// get done () { +// return this._done; +// } + +// public constructor(callback: (...args: any[]) => void, ms = 0) { +// this._callback = () => { +// callback(); +// this._done = true; +// }; +// this._remaining = ms; +// this.resume(); +// } + +// /** pauses the timer */ +// public pause(): Timer { +// if (this._durationTimeoutId && !this._done) { +// this._clearTimeoutRef(); +// this._remaining -= new Date().getTime() - this._start.getTime(); +// } +// return this; +// } + +// /** resumes the timer */ +// public resume(): Timer { +// if (!this._durationTimeoutId && !this._done) { +// this._start = new Date; +// this._durationTimeoutId = setTimeout(this._callback, this._remaining); +// } +// return this; +// } + +// /** +// * clears the timeout and marks it as done. +// * +// * After called, the timeout will not resume +// */ +// public clearTimeout() { +// this._clearTimeoutRef(); +// this._done = true; +// } + +// private _clearTimeoutRef() { +// if (this._durationTimeoutId) { +// clearTimeout(this._durationTimeoutId); +// this._durationTimeoutId = undefined; +// } +// } + +// } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8bcf6f46f..214d4bbdc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -966,7 +966,7 @@ export class CollectionFreeFormView extends CollectionSubView { // if presentation isn't null, call followmovements on the recording api if (this.presentation) { - const err = RecordingApi.Instance.followMovements(this.presentation); - if (err) console.log(err); + const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0); + err && console.log(err) } - this._playing = true; const eleTime = this.player?.currentTime || 0; if (this.timeline) { @@ -198,6 +197,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + if (this.presentation) { + const err = RecordingApi.Instance.pauseMovements(); + err && console.log(err); + } + this._playing = false; this.removeCurrentlyPlaying(); try { -- cgit v1.2.3-70-g09d2 From 4ee1c1f87b311f54e1b928b417017c3a0429d1a4 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 03:31:46 -0400 Subject: Condense code. --- src/client/util/RecordingApi.ts | 147 +--------------------------------------- 1 file changed, 1 insertion(+), 146 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index dfe8e98ca..e779a4ab1 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -128,16 +128,6 @@ export class RecordingApi { this.absoluteStart = absoluteTimePaused } - // public finish = (): Error | Presentation => { - // if (this.isInitPresenation) { - // console.error('[recordingApi.ts] finish() failed: no presentation data. try calling start() first') - // return new Error('[recordingApi.ts] finish()') - // } - - // // return a copy of the the presentation data - // return { ...this.currentPresentation } - // } - private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { @@ -185,40 +175,10 @@ export class RecordingApi { private playFFView: CollectionFreeFormView | null; private timers: NodeJS.Timeout[] | null; - // public loadPresentation = (presentation: Presentation): undefined | Error => { - // if (presentation.startTime === null || this.playFFView === null) { - // return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - // } - - // const document = this.playFFView.Document - // const { movements } = presentation - // this.timers = movements.map(movement => { - // const { panX, panY, time } = movement - - // const t = new Timer(() => { - // document._panX = panX; - // document._panY = panY; - // // TODO: consider cleaning this array to null or some state - // }, time - presentation.startTime) - // t.pause() - // return t - // }) - // console.log(this.timers) - // } - public setPlayFFView = (view: CollectionFreeFormView): void => { this.playFFView = view } - // public pauseMovements = (): undefined | Error => { - // console.log('[recordingApi.ts] pauseMovements()') - // if (this.playFFView === null) { - // return new Error('[recordingApi.ts] pauseMovements() failed: no view') - // } - // // TODO: set userdoc presentMode to browsing - // this.timers?.forEach(timer => timer.pause()) - // } - public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') @@ -235,14 +195,6 @@ export class RecordingApi { this._isPlaying = false } - // public resumeMovements = (): undefined | Error => { - // if (this.playFFView === null) { - // return new Error('[recordingApi.ts] resumeMovements() failed: no view') - // } - // console.log('resume') - // this.timers?.forEach(timer => timer.resume()) - // } - private _isPlaying = false; public playMovements = (presentation: Presentation, timeViewed: number = 0): undefined | Error => { @@ -271,9 +223,8 @@ export class RecordingApi { }, absoluteTime)) return arr; }, []) - - console.log(this.timers.length) } + // Unfinished code for tracing multiple free form views // export let pres: Map = new Map() @@ -290,99 +241,3 @@ export class RecordingApi { // pres.delete(ffView) // } } - -/** Represents the `setTimeout` with an ability to perform pause/resume actions - * citation: https://stackoverflow.com/questions/3969475/javascript-pause-settimeout - */ -class Timer { - private timerId: NodeJS.Timeout | null; - private callback: (...args: any[]) => void - private start: number - private remaining: number; - - constructor(callback: (...args: any[]) => void, delay: number) { - this.callback = callback; - this.remaining = delay; - - this.start = Date.now(); - this.timerId = setTimeout(this.callback, this.remaining); - } - - public pause = () => { - console.log('[timer.ts] pause()') - this.timerId !== null && clearTimeout(this.timerId); - this.timerId = null; - this.remaining -= (Date.now() - this.start); - }; - - public resume = () => { - if (this.timerId) { - return; - } - - this.start = Date.now(); - this.timerId = setTimeout(this.callback, this.remaining); - }; - - public clear = () => { - console.log('[timer.ts] clear()') - this.timerId !== null && clearTimeout(this.timerId); - // this.timerId = null; - // this.remaining -= (Date.now() - this.start); - }; -} -// class Timer { -// private _start: Date; -// private _remaining: number; -// private _durationTimeoutId?: NodeJS.Timeout; -// private _callback: (...args: any[]) => void; -// private _done = false; -// get done () { -// return this._done; -// } - -// public constructor(callback: (...args: any[]) => void, ms = 0) { -// this._callback = () => { -// callback(); -// this._done = true; -// }; -// this._remaining = ms; -// this.resume(); -// } - -// /** pauses the timer */ -// public pause(): Timer { -// if (this._durationTimeoutId && !this._done) { -// this._clearTimeoutRef(); -// this._remaining -= new Date().getTime() - this._start.getTime(); -// } -// return this; -// } - -// /** resumes the timer */ -// public resume(): Timer { -// if (!this._durationTimeoutId && !this._done) { -// this._start = new Date; -// this._durationTimeoutId = setTimeout(this._callback, this._remaining); -// } -// return this; -// } - -// /** -// * clears the timeout and marks it as done. -// * -// * After called, the timeout will not resume -// */ -// public clearTimeout() { -// this._clearTimeoutRef(); -// this._done = true; -// } - -// private _clearTimeoutRef() { -// if (this._durationTimeoutId) { -// clearTimeout(this._durationTimeoutId); -// this._durationTimeoutId = undefined; -// } -// } - -// } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 32f1fb8df81b5dfc12539090c3fe589099bf165b Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 04:43:22 -0400 Subject: Pause works with presentation now. --- src/client/util/RecordingApi.ts | 86 ++++++++++++---------- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 +- src/client/views/nodes/VideoBox.tsx | 2 +- 3 files changed, 56 insertions(+), 39 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index e779a4ab1..ee427e5df 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -2,6 +2,7 @@ import { CollectionFreeFormView } from "../views/collections/collectionFreeForm" import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; import { Doc } from "../../fields/Doc"; +import { VideoBox } from "../views/nodes/VideoBox"; type Movement = { time: number, @@ -10,17 +11,15 @@ type Movement = { } type Presentation = { - movements: Array + movements: Array | null meta: Object, - startTime: number | null, } export class RecordingApi { private static NULL_PRESENTATION: Presentation = { - movements: [], + movements: null, meta: {}, - startTime: null, } // instance variables @@ -52,7 +51,7 @@ export class RecordingApi { // little helper :) private get isInitPresenation(): boolean { - return this.currentPresentation.startTime === null + return this.currentPresentation.movements === null } public start = (meta?: Object): Error | undefined => { @@ -68,12 +67,12 @@ export class RecordingApi { // (1a) get start date for presenation const startDate = new Date() // (1b) set start timestamp to absolute timestamp - // this.absoluteStart = startDate.getTime() + this.absoluteStart = startDate.getTime() // (2) assign meta content if it exists this.currentPresentation.meta = meta || {} // (3) assign start date to currentPresenation - this.currentPresentation.startTime = startDate.getTime() + this.currentPresentation.movements = [] // (4) set isRecording true to allow trackMovements this.isRecording = true } @@ -110,39 +109,35 @@ export class RecordingApi { // don't allow track movments this.isRecording = false - // set relativeStart to the pausedTimestamp + // set adjust absoluteStart to add the time difference const timestamp = new Date().getTime() - this.absoluteStart = timestamp + this.absoluteStart = timestamp - this.absoluteStart } public resume = () => { - const timestamp = new Date().getTime() - const startTimestamp = this.currentPresentation.startTime - if (!startTimestamp) { - console.error('[recordingApi.ts] resume() failed: no presentation data. try calling start() first') - return new Error('[recordingApi.ts] pause()') - } - - // update absoluteStart to bridge the paused time - const absoluteTimePaused = timestamp - this.absoluteStart - this.absoluteStart = absoluteTimePaused + this.isRecording = true + // set absoluteStart to the pausedTimestamp + this.absoluteStart = new Date().getTime() - this.absoluteStart } private trackMovements = (panX: number, panY: number): Error | undefined => { // ensure we are recording if (!this.isRecording) { - console.error('[recordingApi.ts] trackMovements() failed: recording is paused()') return new Error('[recordingApi.ts] trackMovements()') } + // check to see if the presetation is init + if (this.isInitPresenation) { + return new Error('[recordingApi.ts] trackMovements(): no presentation') + } // get the time - const timestamp = new Date().getTime() - - // make new movement struct - const movement: Movement = { time: timestamp, panX, panY } + const time = new Date().getTime() - this.absoluteStart + // make new movement object + console.log(time) + const movement: Movement = { time, panX, panY } // add that movement to the current presentation data's movement array - this.currentPresentation.movements.push(movement) + this.currentPresentation.movements && this.currentPresentation.movements.push(movement) } // instance variable for the FFView @@ -172,6 +167,7 @@ export class RecordingApi { } // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements private playFFView: CollectionFreeFormView | null; private timers: NodeJS.Timeout[] | null; @@ -185,42 +181,58 @@ export class RecordingApi { } if (!this._isPlaying) { - return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return } + this._isPlaying = false // TODO: set userdoc presentMode to browsing - //console.log('cleared timers', this.timers) - console.log(this.timers?.map(timer => clearTimeout(timer))) - console.log('[recordingApi.ts] pauseMovements()') + this.timers?.map(timer => clearTimeout(timer)) - this._isPlaying = false + this.videoBox = null; + } + + private videoBox: VideoBox | null = null; + + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause() + return this.videoBox === null } private _isPlaying = false; - public playMovements = (presentation: Presentation, timeViewed: number = 0): undefined | Error => { - if (presentation.startTime === null || this.playFFView === null) { + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } if (this._isPlaying) { - return new Error('[recordingApi.ts] playMovements() failed: already playing') + //return new Error('[recordingApi.ts] playMovements() failed: already playing') + return } this._isPlaying = true; + // Doc.UserDoc().presentationMode = 'watching'; - console.log(timeViewed) + // TODO: consider this bug at the end of the clip on seek + console.log(timeViewed, videoBox?.player?.currentTime) + this.videoBox = videoBox || null; const document = this.playFFView.Document const { movements } = presentation this.timers = movements.reduce((arr: NodeJS.Timeout[], movement) => { const { panX, panY, time } = movement - const absoluteTime = time - presentation.startTime - timeViewed*1000 - if (absoluteTime < 0) return arr; + + const timeDiff = time - timeViewed*1000 + if (timeDiff < 0) return arr; // set the pan to what was stored arr.push(setTimeout(() => { document._panX = panX; document._panY = panY; - }, absoluteTime)) + // if (movement === movements[movements.length - 1]) { + // this._isPlaying = false; + // } + }, timeDiff)) return arr; }, []) } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 214d4bbdc..9e57bca4f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -968,7 +968,12 @@ export class CollectionFreeFormView extends CollectionSubView { // if presentation isn't null, call followmovements on the recording api if (this.presentation) { - const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0); + const err = RecordingApi.Instance.playMovements(this.presentation, this.player?.currentTime || 0, this); err && console.log(err) } -- cgit v1.2.3-70-g09d2 From a20320fc54f2cddffeabce79b22451a6da551aa5 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 04:48:35 -0400 Subject: Stopping on interaction will pause the presenation and the video. --- src/client/util/RecordingApi.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index ee427e5df..8fb503b00 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -211,10 +211,10 @@ export class RecordingApi { return } this._isPlaying = true; - // Doc.UserDoc().presentationMode = 'watching'; + Doc.UserDoc().presentationMode = 'watching'; // TODO: consider this bug at the end of the clip on seek - console.log(timeViewed, videoBox?.player?.currentTime) + // console.log(timeViewed) this.videoBox = videoBox || null; const document = this.playFFView.Document @@ -229,9 +229,10 @@ export class RecordingApi { arr.push(setTimeout(() => { document._panX = panX; document._panY = panY; - // if (movement === movements[movements.length - 1]) { - // this._isPlaying = false; - // } + if (movement === movements[movements.length - 1]) { + this._isPlaying = false; + Doc.UserDoc().presentationMode = 'none'; + } }, timeDiff)) return arr; }, []) -- cgit v1.2.3-70-g09d2 From a9ff0c90656de71e37edafba68e946807d41403f Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 4 May 2022 13:36:13 -0400 Subject: fix infinite pause/play bug with a stopper, but still very buggy --- src/client/util/RecordingApi.ts | 3 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- .../views/nodes/RecordingBox/RecordingView.scss | 20 ++++++++++++-------- .../views/nodes/RecordingBox/RecordingView.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 5 +++++ 5 files changed, 22 insertions(+), 12 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 8fb503b00..fec579486 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -188,7 +188,7 @@ export class RecordingApi { // TODO: set userdoc presentMode to browsing this.timers?.map(timer => clearTimeout(timer)) - this.videoBox = null; + // this.videoBox = null; } private videoBox: VideoBox | null = null; @@ -236,6 +236,7 @@ export class RecordingApi { }, timeDiff)) return arr; }, []) + } // Unfinished code for tracing multiple free form views diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9e57bca4f..aa2e0c417 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -970,9 +970,9 @@ export class CollectionFreeFormView extends CollectionSubView
); @@ -572,7 +572,7 @@ export class SharingManager extends React.Component<{}> {
Owner -
+
) : null, @@ -622,10 +622,10 @@ export class SharingManager extends React.Component<{}> { {this.sharingOptions(uniform, group.title === "Override")} ) : ( -
- {permissions} -
- )} +
+ {permissions} +
+ )}
); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index eb2bcc18c..936946df0 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -113,7 +113,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P } } - titleEntered = (e: React.KeyboardEvent) => e.key === "Enter" && (e.target as any).blur(); + titleEntered = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.stopPropagation(); + (e.target as any).blur(); + } + } @action onTitleDown = (e: React.PointerEvent): void => { setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => { @@ -288,7 +293,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P if (fixedAspect && (resizeHdl === "documentDecorations-bottomRightResizer" || resizeHdl === "documentDecorations-topLeftResizer")) { // need to generalize for bl and tr drag handles const project = (p: number[], a: number[], b: number[]) => { const atob = [b[0] - a[0], b[1] - a[1]]; - const atop = [p[0] - a[0], p[1] - a[1]]; first + const atop = [p[0] - a[0], p[1] - a[1]]; const len = atob[0] * atob[0] + atob[1] * atob[1]; let dot = atop[0] * atob[0] + atop[1] * atob[1]; const t = dot / len; @@ -501,7 +506,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P value={this._accumulatedTitle} onBlur={e => this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} - onKeyPress={this.titleEntered} /> : + onKeyDown={this.titleEntered} /> :
{`${this.selectionTitle}`}
; @@ -521,7 +526,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P const useRotation = seldoc.ComponentView instanceof InkingStroke || seldoc.ComponentView instanceof ImageBox; const resizerScheme = colorScheme ? "documentDecorations-resizer" + colorScheme : ""; - const rotation = NumCast(seldoc.rootDoc._jitterRotation) + const rotation = NumCast(seldoc.rootDoc._jitterRotation); const rdeg = rotation / 180 * Math.PI; return (
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c794c73db..db51c54f8 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -133,7 +133,7 @@ export class MainView extends React.Component { window.addEventListener("paste", KeyManager.Instance.paste as any); document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on const id = FormattedTextBox.GetDocFromUrl(e.detail); - DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined) : (null)); + DocServer.GetRefField(id).then(doc => (doc instanceof Doc) ? DocumentManager.Instance.jumpToDocument(doc, false, undefined, []) : (null)); }); document.addEventListener("linkAnnotationToDash", Hypothesis.linkListener); this.initEventListeners(); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 78b125e07..8dfc7edc7 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -256,7 +256,7 @@ export class TabDocView extends React.Component { } PresBox.Instance?._selectedArray.clear(); pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - DocumentManager.Instance.jumpToDocument(doc, false, undefined); + DocumentManager.Instance.jumpToDocument(doc, false, undefined, []); batch.end(); }); } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 72480b094..164021358 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -72,7 +72,7 @@ export enum TreeSort { Down = "down", Zindex = "z", None = "none" -}; +} /** * Renders a treeView of a collection of documents * diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5d1e1892c..5b3a7db41 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1423,7 +1423,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (oldDiv.childNodes) { + if (oldDiv.childNodes && newDiv) { for (let i = 0; i < oldDiv.childNodes.length; i++) { this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); } diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index e3d4f86c1..c615bfb8e 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -231,7 +231,7 @@ export class CollectionLinearView extends CollectionSubView() { Currently playing: {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => - DocumentManager.Instance.jumpToDocument(clip, true)}> + DocumentManager.Instance.jumpToDocument(clip, true, undefined, [])}> {clip.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? "" : ",")} )} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx index c2bb3b3ac..adcd9e1e3 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx @@ -199,7 +199,7 @@ export class CollectionSchemaCell extends React.Component { const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc); const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null); // Jump to the this document - DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext, + DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext ? [targetContext] : [], undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc)); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8570f6fff..02ea87103 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -663,7 +663,7 @@ export class DocumentViewInternal extends DocComponent !this.props.isSelected() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb && + !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) && !this._componentView?.isAnyChildContentActive?.() ? true : false @computed get contents() { TraceMobx(); diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index a9d33f161..ccac66996 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -54,6 +54,7 @@ export class LinkDescriptionPopup extends React.Component<{}> { top: LinkDescriptionPopup.popupY ? LinkDescriptionPopup.popupY : 350, }}> e.stopPropagation()} onKeyPress={e => e.key === "Enter" && this.onDismiss(true)} placeholder={"(Optional) Enter link description..."} onChange={(e) => this.descriptionChanged(e)}> diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 69662d53a..48465976a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -75,6 +75,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { + return; // currently we render pdf icons as text labels const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index b037e7220..d8dd074a5 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -156,6 +156,9 @@ position: absolute; top: 0; left: 0; + cursor: text; + padding: 15px; + height: 100% } .webBox-cont { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 80961c92f..f441e5d36 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -207,8 +207,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (this._mainCont.current) { + createTextAnnotation = (sel: Selection, selRange: Range | undefined) => { + if (this._mainCont.current && selRange) { const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); @@ -235,7 +235,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.urlEditor; // controls to be added to the top bar when a document of this type is selected scrollFocus = (doc: Doc, smooth: boolean) => { - this.props.select(true); + this.props.select(false); // hack to preven the webBox from being replaced with a thumbnail that currently doesn't show the the anchors if (StrCast(doc.webUrl) !== this._url) this.submitURL(StrCast(doc.webUrl), !smooth); if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) { this.toggleSidebar(!smooth); @@ -252,7 +252,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { @@ -279,7 +279,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.createTextAnnotation(sel, sel.getRangeAt(0)); + this._textAnnotationCreator = () => this.createTextAnnotation(sel, !sel.isCollapsed ? sel.getRangeAt(0) : undefined); AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale); } @@ -650,6 +650,7 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}> {this.urlContent}
; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 30ad43562..820d7ef96 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -389,11 +389,11 @@ export class PresBox extends ViewBoxBaseComponent() { LightboxView.SetLightboxDoc(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { LightboxView.SetLightboxDoc(undefined); - await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right + await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { LightboxView.SetLightboxDoc(undefined); //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right + await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, [srcContext], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 9257cb75e..3d36efbc4 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -365,7 +365,7 @@ export class SearchBox extends ViewBoxBaseComponent() { * or opening it in a new tab. */ selectElement = async (doc: Doc, finishFunc: () => void) => { - await DocumentManager.Instance.jumpToDocument(doc, true, undefined, undefined, undefined, undefined, undefined, finishFunc); + await DocumentManager.Instance.jumpToDocument(doc, true, undefined, [], undefined, undefined, undefined, finishFunc); } /** -- cgit v1.2.3-70-g09d2 From 3847b2bbba9a64b846ecd6af481fd04df55bd798 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 13 May 2022 16:26:33 -0400 Subject: fixed bug with following links in lightbox. --- src/client/util/LinkManager.ts | 2 +- src/server/server_Initialization.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index b28662a57..2100b1195 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -246,7 +246,7 @@ export class LinkManager { while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) { containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; } - const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]] : containerDocContext; + const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished); } } else { diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index 8bf84622d..bdea57cb2 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -228,9 +228,9 @@ function registerEmbeddedBrowseRelativePathHandler(server: express.Express) { try { const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:/corsProxy/https://en.wikipedia.org/wiki/Engelbart) const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:/corsProxy/ ) - const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : http:s//en.wikipedia.org/wiki/Engelbart) + const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ""); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart) const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^\/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org) - const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e..g, http://localhost:/corsProxy/https://en.wikipedia.org/) + const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:/corsProxy/https://en.wikipedia.org/) if (relativeUrl.startsWith("//")) res.redirect("http:" + relativeUrl); else res.redirect(redirectedProxiedUrl); } catch (e) { -- cgit v1.2.3-70-g09d2 From 21ea42b425946d54eb8a811e10734e714c0164a0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 13 May 2022 18:46:05 -0400 Subject: dragging docs already within a locked document can now still be dropped onto the locked document. --- src/client/util/DragManager.ts | 1 - .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 13e5940d6..12c0fe6c0 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,4 +1,3 @@ -import { extend } from "lodash"; import { action } from "mobx"; import { DateField } from "../../fields/DateField"; import { Doc, Field, Opt } from "../../fields/Doc"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 322944d92..a60253914 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -221,7 +221,7 @@ export class CollectionFreeFormView extends CollectionSubView { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); - const pointerEvents = this.props.isContentActive() === false ? "none" : this.props.childPointerEvents ? "all" : - (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.(); + const pointerEvents = this.props.isContentActive() === false ? "none" : + this.props.childPointerEvents ? "all" : + (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.(); return pointerEvents; } getChildDocView(entry: PoolData) { @@ -1735,7 +1736,7 @@ export class CollectionFreeFormView extends CollectionSubView Date: Sun, 15 May 2022 11:32:35 -0400 Subject: fixed enterting titles in tab's to not trigger inconify. added drop placement for mulitirow/col. added time delay for switching to tab dragging. all this helps support a worktop-style topBar. --- src/client/util/DragManager.ts | 27 ++++++++++----- src/client/views/collections/TabDocView.tsx | 3 ++ .../CollectionMulticolumnView.tsx | 39 +++++++++++++++++++--- .../CollectionMultirowView.tsx | 38 ++++++++++++++++++--- 4 files changed, 89 insertions(+), 18 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 12c0fe6c0..037da132b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -449,20 +449,27 @@ export namespace DragManager { SnappingManager.clearSnapLines(); batch.end(); }); - const moveHandler = async (e: PointerEvent) => { + var startWindowDragTimer: any; + const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; } if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { - dragData.dropAction = dragData.userDropAction || "same"; - AbortDrag(); - await finishDrag?.(new DragCompleteEvent(true, dragData)); - DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => { - if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) { - dragData.removeDocument?.(dragData.draggedDocuments[0]); - } - }); + if (!startWindowDragTimer) startWindowDragTimer = setTimeout(async () => { + startWindowDragTimer = undefined; + dragData.dropAction = dragData.userDropAction || "same"; + AbortDrag(); + await finishDrag?.(new DragCompleteEvent(true, dragData)); + DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => { + if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) { + dragData.removeDocument?.(dragData.draggedDocuments[0]); + } + }); + }, 500); + } else { + clearTimeout(startWindowDragTimer); + startWindowDragTimer = undefined; } const target = document.elementFromPoint(e.x, e.y); @@ -528,6 +535,8 @@ export namespace DragManager { ); }; const upHandler = (e: PointerEvent) => { + clearTimeout(startWindowDragTimer); + startWindowDragTimer = undefined; dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, cleanupDrag); }; document.addEventListener("pointermove", moveHandler, true); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 73e5a9178..1310a582c 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -83,6 +83,9 @@ export class TabDocView extends React.Component { titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; + titleEle.onkeydown = (e: KeyboardEvent) => { + e.stopPropagation(); + } titleEle.onchange = undoBatch(action((e: any) => { titleEle.size = e.currentTarget.value.length + 3; Doc.GetProto(doc).title = e.currentTarget.value; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 92f9b2f49..3582718a3 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -1,10 +1,10 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from "react"; -import { Doc } from '../../../../fields/Doc'; +import { Doc, DocListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnTrue } from '../../../../Utils'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; @@ -194,11 +194,38 @@ export class CollectionMulticolumnView extends CollectionSubView() { @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { - if (super.onInternalDrop(e, de)) { + let dropInd = -1; + if (de.complete.docDragData && this._mainCont) { + let curInd = -1; de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d._dimUnit = "*"; - d._dimMagnitude = 1; + curInd = this.childDocs.indexOf(d); })); + Array.from(this._mainCont.children).forEach((child, index) => { + const brect = child.getBoundingClientRect(); + if (brect.x < de.x && brect.x + brect.width > de.x) { + if (curInd !== -1 && curInd === Math.floor(index / 2)) { + dropInd = curInd; + } + else if (child.className === "multiColumnResizer") { + dropInd = Math.floor(index / 2); + } else { + dropInd = Math.ceil(index / 2 + (de.x - brect.x > brect.width / 2 ? 0 : -1)); + } + } + }) + if (super.onInternalDrop(e, de)) { + de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { + d._dimUnit = "*"; + d._dimMagnitude = 1; + if (dropInd !== curInd || dropInd === -1) { + if (this.childDocs.includes(d)) { + if (dropInd > this.childDocs.indexOf(d)) dropInd--; + } + Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); + Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); + } + })); + } } return false; } @@ -214,6 +241,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { } return this.props.addDocTab(doc, where); } + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return { - if (super.onInternalDrop(e, de)) { + let dropInd = -1; + if (de.complete.docDragData && this._mainCont) { + let curInd = -1; de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d._dimUnit = "*"; - d._dimMagnitude = 1; + curInd = this.childDocs.indexOf(d); })); + Array.from(this._mainCont.children).forEach((child, index) => { + const brect = child.getBoundingClientRect(); + if (brect.y < de.y && brect.y + brect.height > de.y) { + if (curInd !== -1 && curInd === Math.floor(index / 2)) { + dropInd = curInd; + } + else if (child.className === "multiColumnResizer") { + dropInd = Math.floor(index / 2); + } else { + dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1)); + } + } + }) + if (super.onInternalDrop(e, de)) { + de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { + d._dimUnit = "*"; + d._dimMagnitude = 1; + if (dropInd !== curInd || dropInd === -1) { + if (this.childDocs.includes(d)) { + if (dropInd > this.childDocs.indexOf(d)) dropInd--; + } + Doc.RemoveDocFromList(this.rootDoc, this.props.fieldKey, d); + Doc.AddDocToList(this.rootDoc, this.props.fieldKey, d, DocListCast(this.rootDoc[this.props.fieldKey])[dropInd], undefined, dropInd === -1); + } + })); + } } return false; } @@ -213,6 +241,7 @@ export class CollectionMultirowView extends CollectionSubView() { } return this.props.addDocTab(doc, where); } + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return Date: Mon, 16 May 2022 08:45:46 -0400 Subject: re-added fwd/back animation buttons --- src/client/util/CurrentUserUtils.ts | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9720343e1..78bf531c6 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -16,11 +16,13 @@ import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; +import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; +import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; @@ -991,6 +993,14 @@ export class CurrentUserUtils { CollectionViewType.Grid], script: 'setView(value, _readOnly_)', }, // Always show + { + title: "back", toolTip: "prev", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame()', icon: "chevron-left", + hidden: 'IsNoviceMode()' + }, + { + title: "Forward", toolTip: "next", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame()', icon: "chevron-right", + hidden: 'IsNoviceMode()' + }, { title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' @@ -1588,4 +1598,29 @@ ScriptingGlobals.add(function makeTopLevelFolder() { }); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; -}); \ No newline at end of file +}); +ScriptingGlobals.add(function nextKeyFrame() { + const sel = SelectionManager.Views()[0]; + const col = (sel.ComponentView as CollectionFreeFormView); + const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); + if (currentFrame === undefined) { + sel.props.Document._currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0); + } + CollectionFreeFormDocumentView.updateKeyframe(col.childDocs, currentFrame || 0); + sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); + sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame)); +} +); +ScriptingGlobals.add(function prevKeyFrame() { + const sel = SelectionManager.Views()[0]; + const col = (sel.ComponentView as CollectionFreeFormView); + const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); + if (currentFrame === undefined) { + sel.props.Document._currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0); + } + CollectionFreeFormDocumentView.gotoKeyframe(col.childDocs.slice()); + sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); +} +); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ee1fa0ad22f1643888e4c467c6f8df837cc69178 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 17 May 2022 10:18:45 -0400 Subject: fixed pointerevents on treview open icon. added browse mode for freeformcollections --- src/client/util/DragManager.ts | 24 ++++++++++++---------- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 13 ++++++++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 23 ++++++++++++++++----- .../CollectionMulticolumnView.tsx | 2 +- .../CollectionMultirowView.tsx | 2 +- 6 files changed, 43 insertions(+), 23 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 037da132b..5a00e0c04 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -456,17 +456,19 @@ export namespace DragManager { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; } if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { - if (!startWindowDragTimer) startWindowDragTimer = setTimeout(async () => { - startWindowDragTimer = undefined; - dragData.dropAction = dragData.userDropAction || "same"; - AbortDrag(); - await finishDrag?.(new DragCompleteEvent(true, dragData)); - DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => { - if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) { - dragData.removeDocument?.(dragData.draggedDocuments[0]); - } - }); - }, 500); + if (!startWindowDragTimer) { + startWindowDragTimer = setTimeout(async () => { + startWindowDragTimer = undefined; + dragData.dropAction = dragData.userDropAction || "same"; + AbortDrag(); + await finishDrag?.(new DragCompleteEvent(true, dragData)); + DragManager.StartWindowDrag?.(e, dragData.droppedDocuments, (aborted) => { + if (!aborted && (dragData.dropAction === "move" || dragData.dropAction === "same")) { + dragData.removeDocument?.(dragData.draggedDocuments[0]); + } + }); + }, 500); + } } else { clearTimeout(startWindowDragTimer); startWindowDragTimer = undefined; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 1310a582c..cebc3f779 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -85,7 +85,7 @@ export class TabDocView extends React.Component { titleEle.value = doc.title; titleEle.onkeydown = (e: KeyboardEvent) => { e.stopPropagation(); - } + }; titleEle.onchange = undoBatch(action((e: any) => { titleEle.size = e.currentTarget.value.length + 3; Doc.GetProto(doc).title = e.currentTarget.value; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index d608f800c..c004b4d49 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -494,10 +494,15 @@ export class TreeView extends React.Component { return
+ style={this.props.treeView.outlineMode ? + { + opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) + } : + { + pointerEvents: this.props.isContentActive() ? "all" : undefined, + opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4, + color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), + }}> {this.props.treeView.outlineMode ? !(this.doc.text as RichTextField)?.Text ? (null) : : diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a60253914..68a51ef69 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -53,6 +53,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; export const panZoomSchema = createSchema({ _panX: "number", @@ -154,7 +155,7 @@ export class CollectionFreeFormView extends CollectionSubView this._keyframeEditing = set; getKeyFrameEditing = () => this._keyframeEditing; - onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); + onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick, ScriptCast(this.layoutDoc.onBrowseClick)); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; shrinkWrap = () => { @@ -621,7 +622,13 @@ export class CollectionFreeFormView extends CollectionSubView { - if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { + + if (this.layoutDoc.onBrowseClick) { + this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); + e.stopPropagation(); + e.preventDefault(); + } + else if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { if (e.shiftKey) { if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); @@ -1127,7 +1134,7 @@ export class CollectionFreeFormView extends CollectionSubView this.layoutDoc.onBrowseClick = this.layoutDoc.onBrowseClick ? undefined : ScriptField.MakeScript("CollectionBrowseClick(documentView)", { documentView: "any" }), icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); @@ -1973,4 +1980,10 @@ class CollectionFreeFormBackgroundGrid extends React.Component; } -} \ No newline at end of file +} + +export function CollectionBrowseClick(dv: DocumentView) { + dv.props.focus(dv.props.Document, { willZoom: true }); + Doc.linkFollowHighlight(dv?.props.Document, false); +} +ScriptingGlobals.add(CollectionBrowseClick); \ No newline at end of file diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index c18b4cec7..b6b59a2d6 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -212,7 +212,7 @@ export class CollectionMulticolumnView extends CollectionSubView() { dropInd = Math.ceil(index / 2 + (de.x - brect.x > brect.width / 2 ? 0 : -1)); } } - }) + }); if (super.onInternalDrop(e, de)) { de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { d._dimUnit = "*"; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 95456cf46..4074f8d69 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -212,7 +212,7 @@ export class CollectionMultirowView extends CollectionSubView() { dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1)); } } - }) + }); if (super.onInternalDrop(e, de)) { de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { d._dimUnit = "*"; -- cgit v1.2.3-70-g09d2 From 99558fd67dcdb028692f1d219331ab86e07898f1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 20 May 2022 08:42:48 -0400 Subject: fixed script repl to show doc id for tabdocs. fixed hitting 'enter' in several dialogs to stop propagation to prevent iconifying --- src/client/util/SharingManager.tsx | 1 + src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/ScriptingRepl.tsx | 2 +- src/client/views/linking/LinkEditor.tsx | 18 +++++++----------- src/client/views/nodes/DocumentView.tsx | 2 +- 5 files changed, 11 insertions(+), 14 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 11450309a..f439d4488 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -658,6 +658,7 @@ export class SharingManager extends React.Component<{}> { isSearchable closeMenuOnSelect={false} options={options} + onKeyDown={e => e.stopPropagation()} onChange={this.handleUsersChange} value={this.selectedUsers} styles={{ diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 19119bb56..ae3a91c4d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -64,7 +64,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P @computed get Bounds() { const views = SelectionManager.Views(); - return views.map(dv => dv.getBounds()).reduce((bounds, rect) => + return views.filter(dv => dv.props.renderDepth > 0).map(dv => dv.getBounds()).reduce((bounds, rect) => !rect ? bounds : { x: Math.min(rect.left, bounds.x), diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index 4ed6da24a..2b045aa6c 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -212,7 +212,7 @@ export class ScriptingRepl extends React.Component { } onBlur = () => { - this.overlayDisposer && this.overlayDisposer(); + this.overlayDisposer?.(); } render() { diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index c3e0aff11..1414bfdf7 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -22,7 +22,7 @@ export class LinkEditor extends React.Component { @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); - @observable zoomFollow1 = StrCast(this.props.sourceDoc.followLinkZoom); + @observable zoomFollow = StrCast(this.props.sourceDoc.followLinkZoom); @observable openDropdown: boolean = false; @observable showInfo: boolean = false; @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } @@ -114,6 +114,7 @@ export class LinkEditor extends React.Component { this.setDescripValue(this.description); document.getElementById('input')?.blur(); } + e.stopPropagation(); } onRelationshipKey = (e: React.KeyboardEvent) => { @@ -121,6 +122,7 @@ export class LinkEditor extends React.Component { this.setRelationshipValue(this.relationship); document.getElementById('input')?.blur(); } + e.stopPropagation(); } onDescriptionDown = () => this.setDescripValue(this.description); @@ -193,16 +195,11 @@ export class LinkEditor extends React.Component { + value={this.zoomFollow} + onChange={this.handleZoomFollowChange} />
-
; +
; } @computed @@ -300,9 +297,8 @@ export class LinkEditor extends React.Component { render() { const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); - return !destination ? (null) : ( -
+
e.stopPropagation()}>
Return to link menu
} placement="top">
} placement="bottom"> +
MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> + {"Explore"} +
+
@@ -64,7 +75,6 @@ export class TopBar extends React.Component {
SettingsManager.Instance.open()}> {"Settings"}
-
-- cgit v1.2.3-70-g09d2 From 6df80c76b4046ef1ac9e98e52f766dfaf540ead2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 20 May 2022 12:51:51 -0400 Subject: removed enumerables field stuff which was hacky and never used. cleaned up some of currentUserUtils initialiazation. fixed treeView sorting for 'none" which fixes problem with slide template. --- src/client/documents/Documents.ts | 24 -- src/client/util/CurrentUserUtils.ts | 242 +++++---------------- .../views/collections/CollectionStackingView.tsx | 1 - src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/ComparisonBox.tsx | 2 +- .../views/nodes/formattedText/DashFieldView.tsx | 14 -- 6 files changed, 57 insertions(+), 228 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2dd8de089..4be9674f7 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1406,30 +1406,6 @@ export namespace DocUtils { return undefined; } - export async function addFieldEnumerations(doc: Opt, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) { - let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey); - if (!(optionsCollection instanceof Doc)) { - optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set`, system: true }, enumeratedFieldKey); - Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc); - } - const options = optionsCollection as Doc; - const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc); - const docFind = `options.data?.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`; - targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options })); - targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options })); - targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options })); - enumerations.map(enumeration => { - const found = DocListCast(options.data).find(d => d.title === enumeration.title); - if (found) { - found._backgroundColor = enumeration._backgroundColor || found._backgroundColor; - found._color = enumeration.color || found._color; - } else { - Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, { ...enumeration, system: true })); - } - }); - return optionsCollection; - } - // /** // * // * @param dms Degree Minute Second format exif gps data diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8fed6e940..e0580d3c4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -75,201 +75,68 @@ export class CurrentUserUtils { @observable public static searchPanelWidth: number = 0; // sets up the default User Templates - slideView, headerView - static setupUserTemplateButtons(doc: Doc) { - // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location) - if (doc["template-mobile-button"] === undefined) { - const queryTemplate = this.mobileButton({ - title: "NEW MOBILE BUTTON", - onClick: undefined, - }, - [this.createToolButton({ - ignoreClick: true, - icon: "mobile", - btnType: ButtonType.ToolButton, - backgroundColor: "transparent" - }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); - doc["template-mobile-button"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ToolButton, - }); - } - - if (doc["template-button-slides"] === undefined) { - const slideTemplate = Docs.Create.MultirowDocument( - [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) - ], - { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } - ); - slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); - doc["template-button-slides"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card", - btnType: ButtonType.ToolButton - }); - } - - if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to. - const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created - linkTemplate.system = true; - Doc.GetProto(linkTemplate).layout = - "
" + - " " + - " " + - "
"; - (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView"); - - const rtf2 = { - doc: { - type: "doc", content: [ - { - type: "paragraph", - content: [{ - type: "dashField", - attrs: { - fieldKey: "src", - hideKey: false - } - }] - }, - { type: "paragraph" }, - { - type: "paragraph", - content: [{ - type: "dashField", - attrs: { - fieldKey: "dst", - hideKey: false - } - }] - }] + static setupExperimentalTemplateButtons(doc: Doc) { + if (doc["template-experimental-buttons"] === undefined) { + const requiredTypeNameFields = [ + { + type: "slide", icon: "address-card", template: Docs.Create.MultirowDocument( + [ + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) + ], + { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } + ) }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - linkTemplate.header = new RichTextField(JSON.stringify(rtf2), ""); - - doc["template-button-link"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true, - btnType: ButtonType.ToolButton + { + type: "mobile", icon: "mobile", template: this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, + [this.createToolButton({ ignoreClick: true, icon: "mobile", btnType: ButtonType.ToolButton, backgroundColor: "transparent" }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) + ] + ) + }, + ]; + const requiredTypes = requiredTypeNameFields.map(({ type, icon, template }) => { + if (doc[`template-button-${type}`] === undefined) { + template.isTemplateDoc = makeTemplate(template); + doc[`template-button-${type}`] = CurrentUserUtils.createToolButton({ + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + dragFactory: new PrefetchProxy(template) as any as Doc, + title: type, + icon, + btnType: ButtonType.ToolButton, + }); + } + return doc[`template-button-${type}`] as Doc; }); - } - // 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-link"] as Doc, - //doc["template-button-switch"] as Doc] - ]; - if (doc["template-buttons"] === undefined) { - doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { - title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true, + doc["template-experimental-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { + title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, _stayInCollection: true, _hideContextMenu: true, _forceActive: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true })); - } else { - const curButnTypes = Cast(doc["template-buttons"], Doc, null); - DocListCastAsync(curButnTypes.data).then(async curBtns => { - curBtns && await Promise.all(curBtns); - requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype)); - }); } - return doc["template-buttons"] as Doc; + return doc["template-experimental-buttons"] as Doc; } // setup the different note type skins static setupNoteTemplates(doc: Doc) { - if (doc["template-note-Note"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true, icon: "sticky-note", - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); - doc["template-note-Note"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Idea"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", backgroundColor: "pink", system: true, icon: "lightbulb", - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); - doc["template-note-Idea"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Topic"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", backgroundColor: "lightblue", system: true, icon: "book-open", - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); - doc["template-note-Topic"] = new PrefetchProxy(noteView); - } - // if (doc["template-note-Todo"] === undefined) { - // const noteView = Docs.Create.TextDocument("", { - // title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", - // layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true, - // _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - // }); - // noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); - // doc["template-note-Todo"] = new PrefetchProxy(noteView); - // } - // const taskStatusValues = [ - // { title: "todo", _backgroundColor: "blue", color: "white", system: true }, - // { title: "in progress", _backgroundColor: "yellow", color: "black", system: true }, - // { title: "completed", _backgroundColor: "green", color: "white", system: true } - // ]; - // if (doc.fieldTypes === undefined) { - // doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations", system: true }); - // DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues); - // } - if (doc["template-notes"] === undefined) { - doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc], // doc["template-note-Todo"] as any as Doc], - { title: "Note Layouts", _height: 75, system: true })); - } else { - const curNoteTypes = Cast(doc["template-notes"], Doc, null); - const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc]; - DocListCastAsync(curNoteTypes.data).then(async curNotes => { - curNotes && await Promise.all(curNotes); - requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype)); - }); + const requiredTypeNameFields = [ + { type: "Note", backgroundColor: "yellow", icon: "sticky-note" }, + { type: "Idea", backgroundColor: "pink", icon: "lightbulb" }, + { type: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; + const requiredTypes = requiredTypeNameFields.map(({ type, backgroundColor, icon }) => + doc[`template-note-${type}`] as Doc ?? + (() => { + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor, system: true, icon }); + noteView.isTemplateDoc = makeTemplate(noteView, true, type); + return doc[`template-note-${type}`] = new PrefetchProxy(noteView); + })()); + + doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument(requiredTypes, { title: "Note Layouts", _height: 75, system: true })); } return doc["template-notes"] as Doc; @@ -277,11 +144,12 @@ export class CurrentUserUtils { // creates Note templates, and initial "user" templates static setupDocTemplates(doc: Doc) { - const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc); - const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); - const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc); if (doc.templateDocs === undefined) { - doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], { + doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([ + CurrentUserUtils.setupNoteTemplates(doc), + CurrentUserUtils.setupExperimentalTemplateButtons(doc), + CurrentUserUtils.setupClickEditorTemplates(doc) + ], { title: "template layouts", _xMargin: 0, system: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) })); @@ -717,7 +585,7 @@ export class CurrentUserUtils { static async setupToolsBtnPanel(doc: Doc) { // setup a masonry view of all he creators const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); - const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); + const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); doc["tabs-button-tools"] = undefined; @@ -1260,7 +1128,7 @@ export class CurrentUserUtils { doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; doc._raiseWhenDragged = true; - doc._showLabel = false; + doc._showLabel = true; doc._showMenuLabel = true; doc.textAlign = StrCast(doc.textAlign, "left"); doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4338dedcc..4630b3bf2 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -482,7 +482,6 @@ export class CollectionStackingView extends CollectionSubView { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion)); + const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); const rowWidth = () => panelWidth() - treeBulletWidth(); const treeViewRefs = new Map(); return docs.filter(child => child instanceof Doc).map((child, i) => { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 49cd39f43..5ea6d567a 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -76,7 +76,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { return
e.stopPropagation()} // prevent triggering slider movement in registerSliding - onClick={e => this.clearDoc(e, `compareBox-${which}`)}> + onClick={e => this.clearDoc(e, which)}>
; }; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 34908e54b..1a8352b72 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -58,7 +58,6 @@ export class DashFieldViewInternal extends React.Component(); - @observable _showEnumerables: boolean = false; @observable _dashDoc: Doc | undefined; constructor(props: IDashFieldViewInternal) { @@ -110,7 +109,6 @@ export class DashFieldViewInternal extends React.Component this.fieldSpanKeyDown(e, r)); r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); r?.addEventListener("pointerdown", action((e) => { - this._showEnumerables = true; e.stopPropagation(); })); }} > @@ -124,7 +122,6 @@ export class DashFieldViewInternal extends React.Component { if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. - e.ctrlKey && DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); this.updateText(span.textContent!, true); e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view } @@ -142,7 +139,6 @@ export class DashFieldViewInternal extends React.Component { - this._showEnumerables = false; if (nodeText) { const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; @@ -154,7 +150,6 @@ export class DashFieldViewInternal extends React.Component (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); if (modText) { // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; - DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true); } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key else if (nodeText.startsWith(":=")) { @@ -178,13 +173,6 @@ export class DashFieldViewInternal extends React.Component { - e.stopPropagation(); - const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); - collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right"); - } - // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label @@ -220,8 +208,6 @@ export class DashFieldViewInternal extends React.Component} -
; } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 5dd7905530dbfbac3318e55fc625fe46427e23c4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 20 May 2022 13:45:21 -0400 Subject: adjusted placement of fonticonbox labels --- src/client/util/CurrentUserUtils.ts | 64 ++++++++++++-------------- src/client/views/MainView.tsx | 2 +- src/client/views/nodes/button/FontIconBox.scss | 8 ++-- src/client/views/nodes/button/FontIconBox.tsx | 23 +++++---- 4 files changed, 51 insertions(+), 46 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e0580d3c4..b9d2d63ba 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -181,21 +181,22 @@ export class CurrentUserUtils { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", system: true, onClick: deiconifyScript(), ...extra }); - const newIconsList = [ - makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), - makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), - makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), - makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), - makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), - makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), - makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })), - // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) - ].filter(d => d).map(d => d!); if (!templateIconsDoc) { + const newIconsList = [ + makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), + makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), + makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), + makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), + makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), + makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), + makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })), + // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) + ].filter(d => d).map(d => d!); + doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true }); - newIconsList.map(d => (doc["template-icons"] as Doc)[StrCast(d.title)] = new PrefetchProxy(d)); + newIconsList.forEach(d => (doc["template-icons"] as Doc)[StrCast(d.title)] = new PrefetchProxy(d)); } } @@ -224,7 +225,6 @@ export class CurrentUserUtils { backgroundColor: "white", system: true, cloneFieldFilter: new List(["system"]) }); Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); - FormattedTextBox.SelectOnLoad = textDoc[Id]; doc.emptySlide = textDoc; } if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) { @@ -287,11 +287,7 @@ export class CurrentUserUtils { ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { - _width: 200, title: "text note", _autoHeight: true, system: true, - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - cloneFieldFilter: new List(["system"]) - }); + doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, cloneFieldFilter: new List(["system"]) }); ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyImage === undefined) { @@ -790,20 +786,20 @@ export class CurrentUserUtils { "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], 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(value, _readOnly_)' }, - { title: "Font color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor(value, _readOnly_)' }, + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' }, + { title: "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: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' }, + { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' }, + { title: "#", toolTip: "Number List", 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", _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_)' }, + { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' }, + { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' }, + { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' }, ]; return tools; } @@ -816,9 +812,9 @@ export class CurrentUserUtils { { 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_)' }, + { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" }, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' }, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' }, ]; return tools; } @@ -862,19 +858,19 @@ export class CurrentUserUtils { script: 'setView(value, _readOnly_)', }, // Always show { - title: "back", toolTip: "prev", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame()', icon: "chevron-left", + title: "Back", toolTip: "Prev AnimationFrame", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame()', icon: "chevron-left", hidden: 'IsNoviceMode()' }, { - title: "Forward", toolTip: "next", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame()', icon: "chevron-right", + title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame()', icon: "chevron-right", hidden: 'IsNoviceMode()' }, { - title: "Background Color", toolTip: "Background Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", + title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", 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", + title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', }, // Only when a document is selected { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", backgroundColor: 'toggleOverlay(true)', click: 'toggleOverlay()', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9be00fa9e..45dd84e31 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -173,7 +173,7 @@ export class MainView extends React.Component { fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt, fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faArrowsAltV, - fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, + fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, fa.faFlag, fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any, diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index e3e2be515..df9046f12 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -64,7 +64,7 @@ justify-items: center; &:hover { - filter:brightness(0.85) !important; + filter: brightness(0.85) !important; } } @@ -166,8 +166,8 @@ width: 45% !important; height: 45%; } - - &:hover{ + + &:hover { filter: brightness(0.85); } } @@ -278,10 +278,12 @@ background-color: #e3e3e3; box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); border-radius: $standard-border-radius; + input[type=range]::-webkit-slider-runnable-track { background: gray; height: 3px; } + input[type=range]::-webkit-slider-thumb { box-shadow: 1px 1px 1px #000000; border: 1px solid #000000; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index d3ff2586b..20b2be216 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -107,6 +107,11 @@ export class FontIconBox extends DocComponent() { // Script for checking the outcome of the toggle const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0; + const label = !Doc.UserDoc()._showLabel ? (null) : +
+ {this.label} +
; + if (numBtnType === NumButtonType.Slider) { const dropdown =
() { onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)} > {checkResult} + {label} {this.rootDoc.dropDownOpen ? dropdown : null}
); @@ -206,7 +212,7 @@ export class FontIconBox extends DocComponent() { style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}> - {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} + {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
}
@@ -277,7 +283,7 @@ export class FontIconBox extends DocComponent() { }); const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; @@ -331,7 +337,7 @@ export class FontIconBox extends DocComponent() { const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent"; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; @@ -375,7 +381,7 @@ export class FontIconBox extends DocComponent() { // Button label const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; @@ -387,7 +393,7 @@ export class FontIconBox extends DocComponent() { - +
); @@ -416,7 +422,8 @@ export class FontIconBox extends DocComponent() { style={{ backgroundColor: "transparent", borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
- {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} + {!this.label || !Doc.UserDoc()._showLabel ? (null) : +
{this.label}
}
); @@ -443,12 +450,12 @@ export class FontIconBox extends DocComponent() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : -
+
{this.label}
; -- cgit v1.2.3-70-g09d2 From 366cd5f3799a78c297c9a0dc31eb0bd8c0e4c4bd Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 20 May 2022 16:35:24 -0400 Subject: some cleanup of currentUserUtils. css fixes for fonticon boxes with labels --- src/client/util/CurrentUserUtils.ts | 288 +++++++-------------- .../collectionLinear/CollectionLinearView.tsx | 2 +- src/client/views/nodes/button/FontIconBox.scss | 44 +++- src/client/views/nodes/button/FontIconBox.tsx | 44 ++-- 4 files changed, 146 insertions(+), 232 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b9d2d63ba..b9789ab7f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -6,7 +6,6 @@ import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; -import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { nullAudio } from "../../fields/URLField"; @@ -23,7 +22,6 @@ import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; -import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; import { DragManager } from "./DragManager"; @@ -53,6 +51,7 @@ interface Button { list?: string[]; ignoreClick?: boolean; buttonText?: string; + hidden?: string; } export let resolvedPorts: { server: number, socket: number }; @@ -62,7 +61,6 @@ export class CurrentUserUtils { //TODO tfs: these should be temporary... private static mainDocId: string | undefined; - public static searchBtn: Doc; public static get id() { return this.curr_id; } public static get MainDocId() { return this.mainDocId; } public static set MainDocId(id: string | undefined) { this.mainDocId = id; } @@ -196,7 +194,6 @@ export class CurrentUserUtils { ].filter(d => d).map(d => d!); doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true }); - newIconsList.forEach(d => (doc["template-icons"] as Doc)[StrCast(d.title)] = new PrefetchProxy(d)); } } @@ -329,15 +326,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) { + static setupCreatorButtons(doc: Doc) { let alreadyCreatedButtons: string[] = []; const dragCreatorSet = Cast(doc.myItemCreators, Doc, null); if (dragCreatorSet) { - const dragCreators = Cast(dragCreatorSet.data, listSpec(Doc)); - if (dragCreators) { - const dragDocs = await Promise.all(Array.from(dragCreators)); - alreadyCreatedButtons = dragDocs.map(d => StrCast(d.title)); - } + alreadyCreatedButtons = DocListCast(dragCreatorSet.data).map(d => StrCast(d.title)); } const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ @@ -373,7 +366,7 @@ export class CurrentUserUtils { return doc.myItemCreators as Doc; } - static async menuBtnDescriptions(doc: Doc) { + static menuBtnDescriptions(doc: Doc) { return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, @@ -390,7 +383,7 @@ export class CurrentUserUtils { static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.menuStack === undefined) { await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments, hidden }) => + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) => Docs.Create.FontIconDocument({ icon, btnType: ButtonType.MenuButton, @@ -411,17 +404,7 @@ export class CurrentUserUtils { }) ); - menuBtns.forEach(menuBtn => { - if (menuBtn.title === "Search") { - this.searchBtn = menuBtn; - } - }); - - menuBtns.forEach(menuBtn => { - if (menuBtn.title === "Search") { - doc.searchBtn = menuBtn; - } - }); + doc.searchBtn = menuBtns.find(btn => btn.title === "Search"); doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, { title: "menuItemPanel", @@ -436,19 +419,6 @@ export class CurrentUserUtils { _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true })); } - // this resets all sidebar buttons to being deactivated - PromiseValue(Cast(doc.menuStack, Doc)).then(stack => { - stack && PromiseValue(stack.data).then(btns => { - DocListCastAsync(btns).then(bts => bts?.forEach(btn => { - btn.dontUndo = true; - btn.system = true; - if (btn.title === "Catalog" || btn.title === "My Files") { // migration from Catalog to My Files - btn.target = Doc.UserDoc().myFilesystem; - btn.title = "My Files"; - } - })); - }); - }); return doc.menuStack as Doc; } @@ -578,35 +548,26 @@ export class CurrentUserUtils { // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static async setupToolsBtnPanel(doc: Doc) { + static setupToolsBtnPanel(doc: Doc) { // setup a masonry view of all he creators - const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); + const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc); const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); - doc["tabs-button-tools"] = undefined; - - if (doc.myCreators === undefined) { - doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { - title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true, - _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true, - })); - } - // setup a color picker - if (doc.myColorPicker === undefined) { - const color = Docs.Create.ColorDocument({ - title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true - }); - doc.myColorPicker = new PrefetchProxy(color); - } + doc.myCreators = doc.myCreators ?? new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { + title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true, + _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true, + })); + if (!DocListCast(doc.myCreators).includes(creatorBtns as Doc) || !DocListCast(doc.myCreators).includes(templateBtns as Doc)) Doc.GetProto(doc.myCreators as Doc).data = new List([creatorBtns, templateBtns]); - if (doc.myTools === undefined) { - const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], { - title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, - system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0", - })) as any as Doc; + doc.myColorPicker = doc.myColorPicker ?? new PrefetchProxy(Docs.Create.ColorDocument({ + title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true + })); - doc.myTools = toolsStack; - } + doc.myTools = doc.myTools ?? new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], { + title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, + system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0", + })) as any as Doc; + if (!DocListCast(doc.myTools).includes(doc.myCreators as Doc)) Doc.GetProto(doc.myTools as Doc).data = new List([doc.myCreators as Doc]); } static async setupDashboards(doc: Doc) { @@ -639,8 +600,7 @@ export class CurrentUserUtils { return doc.myDashboards as any as Doc; } - static async setupPresentations(doc: Doc) { - await doc.myTrails; + static setupPresentations(doc: Doc) { if (doc.myTrails === undefined) { const newTrail = ScriptField.MakeScript(`createNewPresentation()`); const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "Create new trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); @@ -743,7 +703,7 @@ export class CurrentUserUtils { // setup the list of sidebar mode buttons which determine what is displayed in the sidebar static async setupSidebarButtons(doc: Doc) { CurrentUserUtils.setupSidebarContainer(doc); - await CurrentUserUtils.setupToolsBtnPanel(doc); + CurrentUserUtils.setupToolsBtnPanel(doc); CurrentUserUtils.setupImportSidebar(doc); CurrentUserUtils.setupDashboards(doc); CurrentUserUtils.setupPresentations(doc); @@ -764,17 +724,26 @@ export class CurrentUserUtils { /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc) { - if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("undo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo", title: "undo", icon: "undo-alt", system: true }); - } - if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ onClick: ScriptField.MakeScript("redo()"), _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, btnType: ButtonType.ToolButton, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", title: "redo", icon: "redo-alt", system: true }); - } if (doc.dockedBtns === undefined) { - doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); + if (doc["dockedBtn-undo"] === undefined) { + doc["dockedBtn-undo"] = CurrentUserUtils.createToolButton({ + title: "undo", icon: "undo-alt", _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, system: true, + onClick: ScriptField.MakeScript("undo()"), btnType: ButtonType.ToolButton, _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo" + }); + } + if (doc["dockedBtn-redo"] === undefined) { + doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ + title: "redo", icon: "redo-alt", _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, system: true, + onClick: ScriptField.MakeScript("redo()"), btnType: ButtonType.ToolButton, _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", + }); + } + doc.dockedBtns = CurrentUserUtils.linearButtonList({ + title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, + linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true + }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); } - (doc["dockedBtn-undo"] as Doc).dontUndo = true; - (doc["dockedBtn-redo"] as Doc).dontUndo = true; } static textTools(doc: Doc) { @@ -821,16 +790,7 @@ export class CurrentUserUtils { static schemaTools(doc: Doc) { const tools: Button[] = - [ - { - title: "Show preview", - toolTip: "Show preview of selected document", - btnType: ButtonType.ToggleButton, - buttonText: "Show Preview", - icon: "eye", - click: 'toggleSchemaPreview(_readOnly_)', - }, - ]; + [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", click: 'toggleSchemaPreview(_readOnly_)', }]; return tools; } @@ -846,7 +806,7 @@ export class CurrentUserUtils { return tools; } - static async contextMenuTools(doc: Doc) { + static contextMenuTools(doc: Doc) { return [ { title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, @@ -857,128 +817,52 @@ export class CurrentUserUtils { CollectionViewType.Grid], script: 'setView(value, _readOnly_)', }, // Always show - { - title: "Back", toolTip: "Prev AnimationFrame", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame()', icon: "chevron-left", - hidden: 'IsNoviceMode()' - }, - { - title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame()', icon: "chevron-right", - hidden: 'IsNoviceMode()' - }, - { - title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", - script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' - }, // Only when a document is selected - { - title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", - script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', - }, // Only when a document is selected - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", backgroundColor: 'toggleOverlay(true)', click: 'toggleOverlay()', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform + { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame(_readOnly_)', icon: "chevron-left", hidden: 'IsNoviceMode()' }, + { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame(_readOnly_)', icon: "chevron-right", hidden: 'IsNoviceMode()' }, + { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' }, // Only when a document is selected + { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', }, + { 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 - { title: "Web", type: "webTools", subMenu: true, hidden: 'selectedDocumentType("web")' }, // Only when Web is selected - { title: "Schema", type: "schemaTools", subMenu: true, hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected + { title: "Text", type: "textTools", subMenu: CurrentUserUtils.textTools(doc), expanded: 'selectedDocumentType("rtf")' }, // Always available + { title: "Ink", type: "inkTools", subMenu: CurrentUserUtils.inkTools(doc), expanded: 'selectedDocumentType("ink")' }, // Always available + { title: "Web", type: "webTools", subMenu: CurrentUserUtils.webTools(doc), hidden: 'selectedDocumentType("web")' }, // Only when Web is selected + { title: "Schema", type: "schemaTools", subMenu: CurrentUserUtils.schemaTools(doc), hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected ]; } // Sets up the default context menu buttons - static async setupContextMenuButtons(doc: Doc) { + static setupContextMenuButtons(doc: Doc) { + const btnFunc = (params: Button) => Docs.Create.FontIconDocument({ + title: params.title, icon: params.icon, toolTip: params.toolTip, color: Colors.WHITE, system: true, dontUndo: true, ignoreClick: params.ignoreClick, + _nativeWidth: params.width ? params.width : 30, + _nativeHeight: 30, + _width: params.width ? params.width : 30, + _height: 30, + btnType: params.btnType, + numBtnType: params.numBtnType, numBtnMin: params.numBtnMin, numBtnMax: params.numBtnMax, + btnList: new List(params.list), + _stayInCollection: true, + _hideContextMenu: true, + _lockedPosition: true, + _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + script: params.script ? ScriptField.MakeScript(params.script, { value: "any" }) : undefined, + backgroundColor: params.click ? ComputedField.MakeFunction(params.click) as any : "transparent", + onClick: params.click ? ScriptField.MakeScript(params.click, { scriptContext: "any" }, { _readOnly_: false }) : undefined, + hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, + }); if (doc.contextMenuBtns === undefined) { - const docList: Doc[] = []; - - (await CurrentUserUtils.contextMenuTools(doc)).map(({ title, width, list, toolTip, backgroundColor, ignoreClick, icon, type, btnType, click, script, subMenu, hidden, expanded }) => { - const menuDocList: Doc[] = []; - if (subMenu) { - // default is textTools - let tools: Button[]; - switch (type) { - case "inkTools": - tools = CurrentUserUtils.inkTools(doc); - break; - case "schemaTools": - tools = CurrentUserUtils.schemaTools(doc); - break; - case "webTools": - tools = CurrentUserUtils.webTools(doc); - break; - case "textTools": - tools = CurrentUserUtils.textTools(doc); - break; - default: - tools = CurrentUserUtils.textTools(doc); - break; - } - tools.map(({ title, toolTip, icon, btnType, numBtnType, numBtnMax, numBtnMin, click, script, width, list, ignoreClick, switchToggle }) => { - const backgroundColor = click ? ComputedField.MakeFunction(click) as any : "transparent"; - menuDocList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 25, - _nativeHeight: 25, - _width: width ? width : 25, - _height: 25, - icon, - toolTip, - numBtnType, - numBtnMin, - numBtnMax, - script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined, - btnType: btnType, - btnList: new List(list), - ignoreClick: ignoreClick, - _stayInCollection: true, - _hideContextMenu: true, - _lockedPosition: true, - system: true, - dontUndo: true, - title, - switchToggle, - color: Colors.WHITE, - backgroundColor, - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - onClick: click ? ScriptField.MakeScript(click) : undefined - })); - }); - docList.push(CurrentUserUtils.linearButtonList({ - linearViewSubMenu: true, - flexGap: 0, - ignoreClick: true, - linearViewExpandable: true, - icon: title, - _height: 30, - // 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)); - } else { - docList.push(Docs.Create.FontIconDocument({ - _nativeWidth: width ? width : 25, - _nativeHeight: 25, - _width: width ? width : 25, - _height: 25, - icon, - toolTip, - script: script ? ScriptField.MakeScript(script, { value: "any" }) : undefined, - btnType, - btnList: new List(list), - ignoreClick, - _stayInCollection: true, - _hideContextMenu: true, - _lockedPosition: true, - system: true, - dontUndo: true, - title, - color: Colors.WHITE, - backgroundColor: backgroundColor ? ComputedField.MakeFunction(backgroundColor) 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" }, { _readOnly_: false }) : undefined - })); - } - }); - - doc.contextMenuBtns = CurrentUserUtils.linearButtonList({ title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, docList); + doc.contextMenuBtns = CurrentUserUtils.linearButtonList( + { title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, + CurrentUserUtils.contextMenuTools(doc).map(params => + !params.subMenu ? + btnFunc(params) : + CurrentUserUtils.linearButtonList({ + linearViewSubMenu: true, flexGap: 0, ignoreClick: true, + linearViewExpandable: true, icon: params.title, _height: 30, + linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, + hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, + }, params.subMenu.map(subParams => btnFunc(subParams))))); } } @@ -1463,7 +1347,8 @@ ScriptingGlobals.add(function makeTopLevelFolder() { ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }); -ScriptingGlobals.add(function nextKeyFrame() { +ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { + if (readOnly) return; const sel = SelectionManager.Views()[0]; const col = (sel.ComponentView as CollectionFreeFormView); const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); @@ -1476,7 +1361,8 @@ ScriptingGlobals.add(function nextKeyFrame() { sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame)); } ); -ScriptingGlobals.add(function prevKeyFrame() { +ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { + if (readOnly) return; const sel = SelectionManager.Views()[0]; const col = (sel.ComponentView as CollectionFreeFormView); const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index bec582dcd..c0a33a5e0 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -151,7 +151,7 @@ export class CollectionLinearView extends CollectionSubView() { removeDocument={this.props.removeDocument} ScreenToLocalTransform={docXf} PanelWidth={nested ? doc[WidthSym] : this.dimension} - PanelHeight={nested ? doc[HeightSym] : this.dimension} + PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} renderDepth={this.props.renderDepth + 1} focus={emptyFunction} styleProvider={this.props.styleProvider} diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/button/FontIconBox.scss index df9046f12..6cd56f84e 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/button/FontIconBox.scss @@ -18,12 +18,12 @@ .fontIconBox-label { color: $white; - position: relative; + bottom: 0; + position: absolute; text-align: center; font-size: 7px; letter-spacing: normal; background-color: inherit; - margin-top: 5px; border-radius: 8px; padding: 0; width: 100%; @@ -40,8 +40,10 @@ height: 80%; } - &.clickBtn { + &.clickBtn, + &.clickBtnLabel { cursor: pointer; + flex-direction: column; &:hover { background-color: rgba(0, 0, 0, 0.3) !important; @@ -53,6 +55,12 @@ } } + &.clickBtnLabel { + svg { + margin-top: -4px; + } + } + &.textBtn { display: grid; /* grid-row: auto; */ @@ -68,8 +76,10 @@ } } - &.tglBtn { + &.tglBtn, + &.tglBtnLabel { cursor: pointer; + flex-direction: column; &.switch { //TOGGLE @@ -146,10 +156,19 @@ } } - &.toolBtn { + &.tglBtnLabel { + svg { + margin-top: -4px; + } + } + + &.toolBtn, + &.toolBtnLabel { cursor: pointer; width: 100%; border-radius: 100%; + flex-direction: column; + margin-top: -4px; svg { width: 60% !important; @@ -157,6 +176,12 @@ } } + &.toolBtnLabel { + svg { + margin-top: -4px; + } + } + &.menuBtn { cursor: pointer !important; border-radius: 0px; @@ -174,7 +199,8 @@ - &.colorBtn { + &.colorBtn, + &.colorBtnLabel { color: black; cursor: pointer; flex-direction: column; @@ -204,6 +230,12 @@ } } + &.colorBtnLabel { + svg { + margin-top: -4px; + } + } + &.drpdownList { width: 100%; display: grid; diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 20b2be216..e6d8fe88d 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -108,23 +108,19 @@ export class FontIconBox extends DocComponent() { const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0; const label = !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; if (numBtnType === NumButtonType.Slider) { - const dropdown = -
e.stopPropagation()} - > - this._batch = UndoManager.StartBatch("presDuration")} - onPointerUp={() => this._batch?.end()} - onChange={e => { e.stopPropagation(); setValue(Number(e.target.value)); }} - /> -
; + const dropdown =
e.stopPropagation()} > + this._batch = UndoManager.StartBatch("presDuration")} + onPointerUp={() => this._batch?.end()} + onChange={e => { e.stopPropagation(); setValue(Number(e.target.value)); }} + /> +
; return (
() { style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}> - {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
} + {!this.label || !Doc.UserDoc()._showLabel ? (null) :
{this.label}
}
@@ -283,7 +279,7 @@ export class FontIconBox extends DocComponent() { }); const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; @@ -337,7 +333,7 @@ export class FontIconBox extends DocComponent() { const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent"; const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; @@ -348,7 +344,7 @@ export class FontIconBox extends DocComponent() {
; setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return ( -
this.colorPickerClosed = !this.colorPickerClosed)} onPointerDown={e => e.stopPropagation()}> @@ -381,7 +377,7 @@ export class FontIconBox extends DocComponent() { // Button label const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; @@ -399,7 +395,7 @@ export class FontIconBox extends DocComponent() { ); } else { return ( -
{label} @@ -423,7 +419,7 @@ export class FontIconBox extends DocComponent() {
{!this.label || !Doc.UserDoc()._showLabel ? (null) : -
{this.label}
} +
{this.label}
}
); @@ -450,12 +446,12 @@ export class FontIconBox extends DocComponent() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : -
+
{this.label}
; const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : -
+
{this.label}
; @@ -496,7 +492,7 @@ export class FontIconBox extends DocComponent() { break; case ButtonType.ToolButton: button = ( -
+
{label}
@@ -508,7 +504,7 @@ export class FontIconBox extends DocComponent() { break; case ButtonType.ClickButton: button = ( -
+
{label}
-- cgit v1.2.3-70-g09d2 From 0cddc2350124f7ae870e362a4774de6f4d9a0545 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 May 2022 10:12:19 -0400 Subject: more code cleanup in currentuserutils. some fixes to dragFactory-count titles. --- src/Utils.ts | 1 + src/client/documents/Documents.ts | 9 +- src/client/util/CurrentUserUtils.ts | 156 ++++++--------------- src/client/util/DragManager.ts | 6 +- src/client/views/MainView.tsx | 3 +- src/client/views/collections/CollectionMenu.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 13 +- 7 files changed, 66 insertions(+), 124 deletions(-) (limited to 'src/client/util') diff --git a/src/Utils.ts b/src/Utils.ts index c3c13a555..dbae0aa8c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -8,6 +8,7 @@ import Color = require('color'); export namespace Utils { export let DRAG_THRESHOLD = 4; + export let SNAP_THRESHOLD = 10; export function readUploadedFileAsText(inputFile: File) { const temporaryFileReader = new FileReader(); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4be9674f7..6fa467368 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -156,6 +156,7 @@ export class DocumentOptions { "_carousel-caption-yMargin"?: number; "icon-nativeWidth"?: number; "icon-nativeHeight"?: number; + "dragFactory-count"?: number; // number of items created from a drag button (used for setting title with incrementing index) x?: number; y?: number; z?: number; // whether document is in overlay (1) or not (0 or undefined) @@ -672,8 +673,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options }); } - export function PresDocument(initial: List = new List(), options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options); + export function PresDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List(), options); } export function ScriptingDocument(script: Opt, options: DocumentOptions = {}, fieldKey?: string) { @@ -693,8 +694,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options); } - export function ScreenshotDocument(title: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", { ...options, title }); + export function ScreenshotDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options); } export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b9789ab7f..85769d915 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -24,7 +24,7 @@ import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; -import { DragManager } from "./DragManager"; +import { DragManager, dropActionType } from "./DragManager"; import { makeTemplate, MakeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; @@ -55,7 +55,6 @@ interface Button { } export let resolvedPorts: { server: number, socket: number }; -const headerViewVersion = "0.1"; export class CurrentUserUtils { private static curr_id: string; //TODO tfs: these should be temporary... @@ -87,7 +86,7 @@ export class CurrentUserUtils { }, { type: "mobile", icon: "mobile", template: this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, - [this.createToolButton({ ignoreClick: true, icon: "mobile", btnType: ButtonType.ToolButton, backgroundColor: "transparent" }), + [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), this.mobileTextContainer({}, [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) ] @@ -102,7 +101,6 @@ export class CurrentUserUtils { dragFactory: new PrefetchProxy(template) as any as Doc, title: type, icon, - btnType: ButtonType.ToolButton, }); } return doc[`template-button-${type}`] as Doc; @@ -179,6 +177,9 @@ export class CurrentUserUtils { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", system: true, onClick: deiconifyScript(), ...extra }); + const fontBox = () => Docs.Create.FontIconDocument({ + _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() + }); if (!templateIconsDoc) { const newIconsList = [ makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), @@ -189,7 +190,7 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })), + makeIconTemplate(DocumentType.BUTTON, "data", () => fontBox()), // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) ].filter(d => d).map(d => d!); @@ -201,30 +202,23 @@ export class CurrentUserUtils { title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc }[] { + const standardOps = () => ({ _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); if (doc.emptyPresentation === undefined) { - doc.emptyPresentation = Docs.Create.PresDocument(new List(), - { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyPresentation = Docs.Create.PresDocument({ ...standardOps(), title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); } if (doc.emptyCollection === undefined) { - doc.emptyCollection = Docs.Create.FreeformDocument([], - { _nativeWidth: undefined, _nativeHeight: undefined, _fitWidth: true, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyCollection = Docs.Create.FreeformDocument([], { ...standardOps(), title: "freeform", _width: 150, _height: 100 }); } if (doc.emptyPane === undefined) { - doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _backgroundGridShow: true, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyPane = Docs.Create.FreeformDocument([], { ...standardOps(), title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }); } if (doc.emptySlide === undefined) { - const textDoc = Docs.Create.TreeDocument([], { - title: "Slide", _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, - allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, - backgroundColor: "white", system: true, cloneFieldFilter: new List(["system"]) + doc.emptySlide = Docs.Create.TreeDocument([], { + ...standardOps(), title: ComputedField.MakeFunction('self.text?.Text') as any, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined, + allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" }); - Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); - doc.emptySlide = textDoc; } - if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) { + if (doc.emptyHeader === undefined) { const json = { doc: { type: "doc", @@ -245,9 +239,8 @@ export class CurrentUserUtils { storedMarks: [] }; const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { - title: "text", version: headerViewVersion, _height: 70, _headerPointerEvents: "all", - _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true, - cloneFieldFilter: new List(["system"]) + ...standardOps(), title: "text", _height: 70, + _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, }, "header"); const headerBtnHgt = 10; headerTemplate[DataSym].layout = @@ -263,43 +256,34 @@ export class CurrentUserUtils { // "
"; (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView"); doc.emptyHeader = headerTemplate; - ((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "comparison box", _width: 300, _height: 300, system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "Comparer", _width: 300, _height: 300 }); } if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { ...standardOps(), title: "script", _width: 200, _height: 250, }); } if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, title: "empty screenshot", _width: 400, _height: 200, system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyScreenshot = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "empty screenshot", _width: 400, _height: 200 }); } if (doc.emptyWall === undefined) { - doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyWall = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "screen snapshot", _width: 400, _height: 200, }); (doc.emptyWall as Doc).videoWall = true; } if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { ...standardOps(), title: "audio recording", _width: 200, _height: 100, }); } if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyImage === undefined) { - doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true }); + doc.emptyNote = Docs.Create.TextDocument("", { ...standardOps(), title: "text note", _width: 200, _autoHeight: true }); } if (doc.emptyButton === undefined) { - doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyButton = Docs.Create.ButtonDocument({ ...standardOps(), title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }); } if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { ...standardOps(), title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }); } if (doc.emptyMap === undefined) { - doc.emptyMap = Docs.Create.MapDocument([], { title: "map", _showSidebar: true, _width: 800, _height: 600, system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyMap as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyMap = Docs.Create.MapDocument([], { ...standardOps(), title: "map", _showSidebar: true, _width: 800, _height: 600, }); } if (doc.activeMobileMenu === undefined) { this.setupActiveMobileMenu(doc); @@ -309,7 +293,6 @@ export class CurrentUserUtils { { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc }, - { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc }, { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc }, { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc }, @@ -719,30 +702,21 @@ export class CurrentUserUtils { })) as any as Doc static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ - ...opts, btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _removeDropProperties: new List(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true + btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, })) as any as Doc /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc) { if (doc.dockedBtns === undefined) { - if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.createToolButton({ - title: "undo", icon: "undo-alt", _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, system: true, - onClick: ScriptField.MakeScript("undo()"), btnType: ButtonType.ToolButton, _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo" - }); - } - if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ - title: "redo", icon: "redo-alt", _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, system: true, - onClick: ScriptField.MakeScript("redo()"), btnType: ButtonType.ToolButton, _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", - }); - } + const dockBtn = (opts: DocumentOptions) => CurrentUserUtils.createToolButton({ _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...opts }); + const btnDescs = [ + { title: "undo", opts: () => ({ icon: "undo-alt", onClick: ScriptField.MakeScript("undo()"), toolTip: "Click to undo" }) }, + { title: "redo", opts: () => ({ icon: "redo-alt", onClick: ScriptField.MakeScript("redo()"), toolTip: "Click to redo" }) } + ]; doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true - }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); + }, btnDescs.map(desc => doc[`dockedBtn-${desc.title}`] as Doc ?? (doc[`dockedBtn-${desc.title}`] = dockBtn({ title: desc.title, ...desc.opts() })))); } } @@ -996,7 +970,7 @@ export class CurrentUserUtils { static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); - const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + await DocListCastAsync((doc.globalGroupDatabase as Doc).data); reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]), async () => { const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); @@ -1025,9 +999,6 @@ export class CurrentUserUtils { doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false); doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); doc.noviceMode = BoolCast(doc.noviceMode, true); - doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // - doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // - Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); doc.savedFilters = new List(); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; @@ -1049,15 +1020,6 @@ export class CurrentUserUtils { doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - // uncomment this to setup a default note style that uses the custom header layout - // PromiseValue(doc.emptyHeader).then(factory => { - // if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) { - // const deleg = Doc.delegateDragFactory(factory as Doc); - // deleg.title = "header"; - // doc.defaultTextLayout = new PrefetchProxy(deleg); - // Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg); - // } - // }); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { @@ -1264,26 +1226,16 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { view && SelectionManager.SelectView(view, false); } }); -ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, - "document containing all shared Docs"); -ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, - "is Dash in novice mode"); -ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, - "creates a snapshot copy of a dashboard"); -ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, - "creates a new dashboard when called"); -ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, - "creates a new presentation when called"); -ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, - "creates a new folder in myFiles when called"); -ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, - "returns all the links to the document or its annotations", "(doc: any)"); -ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, - "imports files from device directly into the import sidebar"); -ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { - SharingManager.Instance.open(undefined, dashboard); -}, - "opens sharing dialog for Dashboard"); +ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, "document containing all shared Docs"); +ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode"); +ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); +ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); +ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called"); +ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); +ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); +ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); +ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); +ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); if (dashboards && dashboards.length > 1) { @@ -1292,21 +1244,10 @@ ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { } }, "Remove Dashboard from Dashboards"); -ScriptingGlobals.add(async function addToDashboards(dashboard: Doc) { +ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); - - const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]); - - // moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly - // dashboard["data-all"] = new List(allDocs); - // dashboardAlias["data-all"] = new List((allDocs || []).map(doc => Doc.MakeAlias(doc))); - - // const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig)); - // dashboardAlias.dockingConfig = JSON.stringify(dockingConfig); - dashboardAlias.data = new List(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder))); DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias); - //new List(); DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any; Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias); @@ -1344,9 +1285,6 @@ ScriptingGlobals.add(function makeTopLevelFolder() { TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined }; return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder); }); -ScriptingGlobals.add(function toggleComicMode() { - Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; -}); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { if (readOnly) return; const sel = SelectionManager.Views()[0]; @@ -1359,8 +1297,7 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { CollectionFreeFormDocumentView.updateKeyframe(col.childDocs, currentFrame || 0); sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame)); -} -); +}); ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { if (readOnly) return; const sel = SelectionManager.Views()[0]; @@ -1372,5 +1309,4 @@ ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { } CollectionFreeFormDocumentView.gotoKeyframe(col.childDocs.slice()); sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); -} -); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 5a00e0c04..9f8c49081 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -7,7 +7,7 @@ import { listSpec } from "../../fields/Schema"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; -import { emptyFunction } from "../../Utils"; +import { emptyFunction, Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import * as globalCssVariables from "../views/global/globalCssVariables.scss"; import { DocumentView } from "../views/nodes/DocumentView"; @@ -277,7 +277,7 @@ export namespace DragManager { SnappingManager.setSnapLines(horizLines, vertLines); } export function snapDragAspect(dragPt: number[], snapAspect: number) { - let closest = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10); + let closest = Utils.SNAP_THRESHOLD; let near = dragPt; const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => { if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0 @@ -312,7 +312,7 @@ export namespace DragManager { } // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { - const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10); + const snapThreshold = Utils.SNAP_THRESHOLD; const snapVal = (pts: number[], drag: number, snapLines: number[]) => { if (snapLines.length) { const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 45dd84e31..6a2df3b51 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -244,8 +244,7 @@ export class MainView extends React.Component { title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true })); } - const pres = Docs.Create.PresDocument(new List(), - { title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); + const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); CollectionDockingView.AddSplit(pres, "right"); this.userDoc.activePresentation = pres; Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index b3fbd4ca4..c322d9e16 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -467,7 +467,7 @@ export class CollectionViewBaseChrome extends React.Component { - const doc = Docs.Create.ScreenshotDocument("screen recording", { _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); + const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); CollectionDockingView.AddSplit(doc, "right"); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d0c8c5826..164bcb9b0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -17,7 +17,7 @@ import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; -import { Cast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; @@ -62,6 +62,7 @@ import { schema } from "./schema_rts"; import { SummaryView } from "./SummaryView"; import applyDevTools = require("prosemirror-dev-tools"); import React = require("react"); +import { ComputedField } from '../../../../fields/ScriptField'; const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { @@ -371,9 +372,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; 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); + + const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title)); + if (!(cfield instanceof ComputedField)) { + this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); + if (str.startsWith("@") && str.length > 1) { + Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc); + } } } } -- cgit v1.2.3-70-g09d2 From 3fe0e0e02a6c9bd717b3df9b000c13d1131f6eb4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 May 2022 12:28:31 -0400 Subject: cleaning up singleLine text box api and TreeView types --- src/client/util/CurrentUserUtils.ts | 7 +-- src/client/views/MainView.tsx | 5 +- .../views/collections/CollectionTreeView.tsx | 29 ++++++---- src/client/views/collections/TreeView.tsx | 35 ++++++------ .../collectionFreeForm/CollectionFreeFormView.tsx | 34 ++++++++++-- .../collections/collectionFreeForm/MarqueeView.tsx | 13 +++-- src/client/views/nodes/DocumentView.tsx | 2 + src/client/views/nodes/FieldView.tsx | 3 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 7 +-- .../formattedText/ProsemirrorExampleTransfer.ts | 62 ++++++---------------- 10 files changed, 101 insertions(+), 96 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 85769d915..1ebb5365a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -16,6 +16,7 @@ import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; +import { TreeViewType } from "../views/collections/CollectionTreeView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; @@ -215,7 +216,7 @@ export class CurrentUserUtils { if (doc.emptySlide === undefined) { doc.emptySlide = Docs.Create.TreeDocument([], { ...standardOps(), title: ComputedField.MakeFunction('self.text?.Text') as any, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined, - allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" + allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" }); } if (doc.emptyHeader === undefined) { @@ -563,7 +564,7 @@ export class CurrentUserUtils { title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: "fileSystem", isFolder: true, system: true, + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." })); const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`); @@ -616,7 +617,7 @@ export class CurrentUserUtils { title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100, treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, - isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true, + isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true, explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." })); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6a2df3b51..09a57843c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -8,11 +8,9 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; import { PromiseValue, StrCast } from '../../fields/Types'; -import { TraceMobx } from '../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; @@ -34,6 +32,7 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; +import { TreeViewType } from './collections/CollectionTreeView'; import { CollectionViewType } from './collections/CollectionView'; import "./collections/TreeView.scss"; import { ComponentDecorations } from './ComponentDecorations'; @@ -263,7 +262,7 @@ export class MainView extends React.Component { title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", treeViewTruncateTitleWidth: 150, ignoreClick: true, - isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true, + isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true, explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." })); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 5e89016db..99ffb9bb0 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -27,6 +27,7 @@ import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import { TreeView } from "./TreeView"; import React = require("react"); +import { FieldViewProps } from "../nodes/FieldView"; const _global = (window /* browser */ || global /* node */) as any; export type collectionTreeViewProps = { @@ -39,6 +40,12 @@ export type collectionTreeViewProps = { onChildClick?: () => ScriptField; }; +export enum TreeViewType { + outline = "outline", + fileSystem = "fileSystem", + default = "default" +} + @observer export class CollectionTreeView extends CollectionSubView>() { private _treedropDisposer?: DragManager.DragDropDisposer; @@ -54,8 +61,8 @@ export class CollectionTreeView extends CollectionSubView StrCast(this.dataDoc.title)} SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(this.treeChildren); + if (enter && this.props.Document.treeViewType === TreeViewType.outline) this.makeTextCollection(this.treeChildren); this.dataDoc.title = value; return true; })} />; } + onKey = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + if (this.outlineMode && e.key === "Enter") { + e.stopPropagation(); + this.makeTextCollection(this.treeChildren); + return true; + } + } get documentTitle() { return this._titleRef = r} - onKeyDown={e => { - if (this.outlineMode) { - e.stopPropagation(); - e.key === "Enter" && this.makeTextCollection(this.treeChildren); - } - }}> + ref={r => this._titleRef = r}> {this.outlineMode ? this.documentTitle : this.editableTitle}
; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 65f8fe248..70ad23f41 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -26,11 +26,12 @@ import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFun import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { StyleProp } from '../StyleProvider'; -import { CollectionTreeView } from './CollectionTreeView'; +import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); import { KeyValueBox } from '../nodes/KeyValueBox'; +import { FieldViewProps } from '../nodes/FieldView'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -241,7 +242,7 @@ export class TreeView extends React.Component { layout: CollectionView.LayoutString("data"), title: "-title-", treeViewExpandedViewLock: true, treeViewExpandedView: "data", - _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline", + _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: TreeViewType.outline, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _width: 1000, _height: 10 }); Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); @@ -604,24 +605,29 @@ export class TreeView extends React.Component { if (property.startsWith(StyleProp.Decorations)) return (null); return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView } - onKeyDown = (e: React.KeyboardEvent) => { + onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) { switch (e.key) { case "Tab": - e.stopPropagation(); - e.preventDefault(); + e.stopPropagation?.(); + e.preventDefault?.(); setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); - return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab"); + UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab"); + return true; case "Backspace": - e.stopPropagation(); - e.preventDefault(); - return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc); + if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) { + e.stopPropagation?.(); + e.preventDefault?.(); + return true; + } + break; case "Enter": - e.stopPropagation(); - e.preventDefault(); + e.stopPropagation?.(); + e.preventDefault?.(); return UndoManager.RunInBatch(this.makeTextCollection, "bullet"); } } + return false; } titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth())); @@ -758,6 +764,7 @@ export class TreeView extends React.Component { hideResizeHandles={this.props.treeView.outlineMode} focus={this.refocus} ContentScaling={returnOne} + onKey={this.onKeyDown} hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} ScreenToLocalTransform={this.docTransform} @@ -800,7 +807,7 @@ export class TreeView extends React.Component { @computed get renderBorder() { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string, label: string } }; - return
+ return
{!this.treeViewOpen ? (null) : this.renderContent}
; } @@ -821,9 +828,7 @@ export class TreeView extends React.Component { return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document - onKeyDown={this.onKeyDown}> + onDrop={this.onTreeDrop}>
  • {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader" this.renderEmbeddedDocument(false, returnFalse) : diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index df09569ef..3b32cf57d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,18 +1,18 @@ import { Bezier } from "bezier-js"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { DateField } from "../../../../fields/DateField"; -import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; +import { DataSym, Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; -import { ImageField } from "../../../../fields/URLField"; import { createSchema, listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { ImageField } from "../../../../fields/URLField"; import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; @@ -26,6 +26,7 @@ import { DragManager, dropActionType } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; import { InteractionUtils } from "../../../util/InteractionUtils"; import { LinkManager } from "../../../util/LinkManager"; +import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; import { SearchUtil } from "../../../util/SearchUtil"; import { SelectionManager } from "../../../util/SelectionManager"; import { ColorScheme } from "../../../util/SettingsManager"; @@ -47,13 +48,14 @@ import { CreateImage } from "../../nodes/WebBoxRenderer"; import { StyleProp } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; +import { TreeViewType } from "../CollectionTreeView"; import { CollectionViewType } from "../CollectionView"; import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; +import { FieldView, FieldViewProps } from "../../nodes/FieldView"; export const panZoomSchema = createSchema({ _panX: "number", @@ -957,7 +959,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === "outline") return; + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -1160,6 +1162,27 @@ export class CollectionFreeFormView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); + @undoBatch + @action + onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + const docView = fieldProps.DocumentView?.(); + if (docView && docView.rootDoc._singleLine && ["Tab", "Enter"].includes(e.key)) { + e.stopPropagation?.(); + const below = !e.altKey && e.key !== "Tab"; + const layoutKey = StrCast(docView.LayoutFieldKey); + const newDoc = Doc.MakeCopy(docView.rootDoc, true); + const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; + newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; + if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10; + else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10; + if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = docView.rootDoc[layoutKey]; + } + Doc.GetProto(newDoc).text = undefined; + FormattedTextBox.SelectOnLoad = newDoc[Id]; + return this.addDocument?.(newDoc); + } + } pointerEvents = () => { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); const pointerEvents = this.props.isContentActive() === false ? "none" : @@ -1185,6 +1208,7 @@ export class CollectionFreeFormView extends CollectionSubView Transform; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f5ad47ca7..f7312e59c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -50,6 +50,7 @@ import { ScriptingBox } from "./ScriptingBox"; import { PresBox } from './trails/PresBox'; import React = require("react"); import { DocServer } from "../../DocServer"; +import { FieldViewProps } from "./FieldView"; const { Howl } = require('howler'); interface Window { @@ -178,6 +179,7 @@ export interface DocumentViewProps extends DocumentViewSharedProps { onPointerDown?: () => ScriptField; onPointerUp?: () => ScriptField; onBrowseClick?: () => (ScriptField | undefined); + onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined); } // these props are only available in DocumentViewIntenral diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 94937430e..79c1f1c40 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -5,7 +5,7 @@ import { DateField } from "../../../fields/DateField"; import { Doc, Field, FieldResult, Opt } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { WebField } from "../../../fields/URLField"; -import { DocumentViewSharedProps } from "./DocumentView"; +import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; import { ScriptField } from "../../../fields/ScriptField"; // @@ -25,6 +25,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { scaling?: () => number; setHeight?: (height: number) => void; onBrowseClick?: () => (ScriptField | undefined); + onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => (boolean | undefined); // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) pointerEvents?: () => Opt; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 164bcb9b0..0e29461a6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -17,6 +17,7 @@ import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; +import { ComputedField } from '../../../../fields/ScriptField'; import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; @@ -62,7 +63,6 @@ import { schema } from "./schema_rts"; import { SummaryView } from "./SummaryView"; import applyDevTools = require("prosemirror-dev-tools"); import React = require("react"); -import { ComputedField } from '../../../../fields/ScriptField'; const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { @@ -1471,11 +1471,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, "titler"); } } + onKeyDown = (e: React.KeyboardEvent) => { - // single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container) - if (this.rootDoc._singleLine && ((e.key === "Backspace" && !this.dataDoc[this.fieldKey]?.Text) || ["Tab", "Enter"].includes(e.key))) { - return; - } if (e.altKey) { e.preventDefault(); return; diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index c76eda859..0dd0a8411 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -1,20 +1,15 @@ -import { chainCommands, exitCode, joinDown, joinUp, lift, deleteSelection, joinBackward, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn, newlineInCode } from "prosemirror-commands"; -import { liftTarget } from "prosemirror-transform"; +import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands"; import { redo, undo } from "prosemirror-history"; import { Schema } from "prosemirror-model"; -import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; -import { splitListItem, wrapInList, } from "prosemirror-schema-list"; -import { EditorState, Transaction, TextSelection } from "prosemirror-state"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { NumCast, BoolCast, Cast, StrCast } from "../../../../fields/Types"; -import { Doc, DataSym, DocListCast, AclAugment, AclSelfEdit } from "../../../../fields/Doc"; -import { FormattedTextBox } from "./FormattedTextBox"; -import { Id } from "../../../../fields/FieldSymbols"; -import { Docs } from "../../../documents/Documents"; -import { Utils } from "../../../../Utils"; -import { listSpec } from "../../../../fields/Schema"; -import { List } from "../../../../fields/List"; +import { splitListItem, wrapInList } from "prosemirror-schema-list"; +import { EditorState, TextSelection, Transaction } from "prosemirror-state"; +import { liftTarget } from "prosemirror-transform"; +import { AclAugment, AclSelfEdit, Doc } from "../../../../fields/Doc"; import { GetEffectiveAcl } from "../../../../fields/util"; +import { Utils } from "../../../../Utils"; +import { Docs } from "../../../documents/Documents"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; @@ -48,29 +43,6 @@ export function buildKeymap>(schema: S, props: any, mapKey keys[key] = cmd; } - /// bcz; Argh!! replace with an onEnter func that conditionally handles Enter - const addTextBox = (below: boolean, force?: boolean) => { - if (props.Document.treeViewType === "outline") return true; // bcz: Arghh .. need to determine if this is an treeViewOutlineBox in which case Enter's are ignored.. - const layoutDoc = props.Document; - const originalDoc = layoutDoc.rootDocument || layoutDoc; - if (force || props.Document._singleLine) { - const layoutKey = StrCast(originalDoc.layoutKey); - const newDoc = Doc.MakeCopy(originalDoc, true); - const dataField = originalDoc[Doc.LayoutFieldKey(newDoc)]; - newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; - if (below) newDoc.y = NumCast(originalDoc.y) + NumCast(originalDoc._height) + 10; - else newDoc.x = NumCast(originalDoc.x) + NumCast(originalDoc._width) + 10; - if (layoutKey !== "layout" && originalDoc[layoutKey] instanceof Doc) { - newDoc[layoutKey] = originalDoc[layoutKey]; - } - Doc.GetProto(newDoc).text = undefined; - FormattedTextBox.SelectOnLoad = newDoc[Id]; - props.addDocument(newDoc); - return true; - } - return false; - }; - const canEdit = (state: any) => { switch (GetEffectiveAcl(props.Document)) { case AclAugment: return false; @@ -109,11 +81,7 @@ export function buildKeymap>(schema: S, props: any, mapKey bind("Ctrl-i", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - /// bcz; Argh!! replace layotuTEmpalteString with a onTab prop conditionally handles Tab); - if (props.Document._singleLine) { - if (!props.LayoutTemplateString) return addTextBox(false, true); - return true; - } + if (props.onKey?.({ key: "Tab" }, props)) return true; if (!canEdit(state)) return true; const ref = state.selection; const range = ref.$from.blockRange(ref.$to); @@ -138,8 +106,7 @@ export function buildKeymap>(schema: S, props: any, mapKey }); bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - /// bcz; Argh!! replace with a onShiftTab prop conditionally handles Tab); - if (props.Document._singleLine) return true; + if (props.onKey?.({ key: "Tab", shiftKey: true })) return true; // single line docs don't process tabs so that their containers can decide what to do. This should be a prop if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); @@ -188,13 +155,14 @@ export function buildKeymap>(schema: S, props: any, mapKey }; //Command to create a text document to the right of the selected textbox - bind("Alt-Enter", () => addTextBox(false, true)); + bind("Alt-Enter", () => true); //Command to create a text document to the bottom of the selected textbox - bind("Ctrl-Enter", () => addTextBox(true, true)); + bind("Ctrl-Enter", () => true); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); bind("Backspace", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; if (!deleteSelection(state, (tx: Transaction) => { @@ -216,8 +184,8 @@ export function buildKeymap>(schema: S, props: any, mapKey //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock //command to break line bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { - if (addTextBox(true, false)) return true; + if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const trange = state.selection.$from.blockRange(state.selection.$to); -- cgit v1.2.3-70-g09d2 From 414b56260c55f5ea7cac43dad83b41688fb924b5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 May 2022 20:34:07 -0400 Subject: added headerBar for closed tabs and other docs. extended multirow/col to support hiding chile doc decorations and for making child content/document active work better. changed background style to work better for sub collections. --- src/client/util/CurrentUserUtils.ts | 3 + src/client/views/DocumentDecorations.tsx | 7 +- src/client/views/MainView.tsx | 115 +++++++++++++++------ src/client/views/StyleProvider.tsx | 6 +- src/client/views/collections/CollectionMenu.tsx | 17 ++- src/client/views/collections/CollectionView.tsx | 6 +- src/client/views/collections/TabDocView.tsx | 2 + .../CollectionMulticolumnView.tsx | 11 +- .../CollectionMultirowView.tsx | 11 +- src/client/views/nodes/DocumentView.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 - 11 files changed, 125 insertions(+), 60 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 1ebb5365a..1e42b9073 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -70,6 +70,7 @@ export class CurrentUserUtils { @observable public static GuestDashboard: Doc | undefined; @observable public static GuestMobile: Doc | undefined; @observable public static propertiesWidth: number = 0; + @observable public static headerBarHeight: number = 0; @observable public static searchPanelWidth: number = 0; // sets up the default User Templates - slideView, headerView @@ -1003,6 +1004,7 @@ export class CurrentUserUtils { doc.savedFilters = new List(); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; + doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates this.setupImportSidebar(doc); // sets up the import sidebar @@ -1210,6 +1212,7 @@ export class CurrentUserUtils { 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 MyHeaderBarDoc() { return Cast(Doc.UserDoc().myHeaderBarDoc, Doc, null); } public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); } public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index ae3a91c4d..29e088143 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -511,11 +511,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P {`${this.selectionTitle}`}
  • ; - let inMainMenuPanel = false; - for (let node = seldoc.ContentDiv; node && !inMainMenuPanel; node = node?.parentNode as any) { - if (node.className === "mainView-mainContent") inMainMenuPanel = true; - } - const leftBounds = inMainMenuPanel ? 0 : this.props.boundsLeft; + + const leftBounds = this.props.boundsLeft; const topBounds = LightboxView.LightboxDoc ? 0 : this.props.boundsTop; bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 09a57843c..69e394790 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -33,7 +33,7 @@ import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOpti import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; import { TreeViewType } from './collections/CollectionTreeView'; -import { CollectionViewType } from './collections/CollectionView'; +import { CollectionView, CollectionViewType } from './collections/CollectionView'; import "./collections/TreeView.scss"; import { ComponentDecorations } from './ComponentDecorations'; import { ContextMenu } from './ContextMenu'; @@ -81,14 +81,19 @@ export class MainView extends React.Component { @computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @computed private get topOfDashUI() { return Number(DASHBOARD_SELECTOR_HEIGHT.replace("px", "")); } - @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight(); } + @computed private get topOfHeaderBarDoc() { return this.topOfDashUI + this.topMenuHeight(); } + @computed private get topOfSidebarDoc() { return this.topOfDashUI + this.topMenuHeight(); } + @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight(); } @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; } @computed private get leftScreenOffsetOfMainDocView() { return this.leftMenuWidth() - 2; } @computed private get userDoc() { return Doc.UserDoc(); } @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); } @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; } + @computed private get headerBarDoc() { return this.userDoc ? CurrentUserUtils.MyHeaderBarDoc : CurrentUserUtils.MyHeaderBarDoc; } @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } + headerBarDocWidth = () => this._dashUIWidth; + headerBarDocHeight = () => CurrentUserUtils.headerBarHeight ?? 0; topMenuHeight = () => 35; topMenuWidth = returnZero; // value is ignored ... leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace("px", "")); @@ -98,7 +103,7 @@ export class MainView extends React.Component { propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0)); propertiesHeight = () => this._dashUIHeight; mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth(); - mainDocViewHeight = () => this._dashUIHeight; + mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); componentDidMount() { document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!)); @@ -177,8 +182,8 @@ export class MainView extends React.Component { fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faClipboard, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper as any, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, far.faCircle as any, - fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, faBuffer as any, fa.faExpand, fa.faUndo, fa.faSlidersH, fa.faAngleDoubleLeft, fa.faAngleUp, - fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl, + fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight, fa.faAngleDoubleDown, fa.faAngleDoubleLeft, fa.faAngleDoubleUp, faBuffer as any, fa.faExpand, fa.faUndo, + fa.faSlidersH, fa.faAngleUp, fa.faAngleDown, fa.faPlayCircle, fa.faClock, fa.faRocket, fa.faExchangeAlt, fa.faHashtag, fa.faAlignJustify, fa.faCheckSquare, fa.faListUl, fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo, fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, @@ -276,33 +281,67 @@ export class MainView extends React.Component { return () => this._exploreMode ? ScriptField.MakeScript("CollectionBrowseClick(documentView, clientX, clientY)", { documentView: "any", clientX: "number", clientY: "number" })! : undefined; } - + headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1); + + headerContentActive = () => SnappingManager.GetIsDragging() ? false : true; + @computed get headerBarDocView() { + console.log(this.headerBarDoc) + return
    +
    ; + } @computed get mainDocView() { - return ; + return <> + {this.headerBarDocView} + ; } @computed get dockingContent() { @@ -331,7 +370,7 @@ export class MainView extends React.Component { this.closeFlyout); } - sidebarScreenToLocal = () => new Transform(0, -this.topOfMainDoc, 1); + sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1); mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0); addDocTabFunc = (doc: Doc, location: string): boolean => { const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); @@ -461,6 +500,16 @@ export class MainView extends React.Component { ; } + @computed get headerBar() { + return !this.userDoc ? (null) : +
    + {this.headerBarDocView} +
    ; + } + @computed get mainDashboardArea() { return !this.userDoc ? (null) :
    { @@ -620,7 +669,7 @@ export class MainView extends React.Component { - + {LinkDescriptionPopup.descriptionPopup ? : null} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 7ac40fc8e..c44443264 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -158,12 +158,12 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? + Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : - Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? + Doc.UserDoc().activeCollectionBackground, "string") ?? (darkScheme() ? Colors.BLACK : "linear-gradient(#065fff, #85c1f9)")) - )); + ); break; //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index c322d9e16..96029a8e4 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -84,11 +84,11 @@ export class CollectionMenu extends AntimodeMenu{ } @action - toggleProperties = () => { - if (CurrentUserUtils.propertiesWidth > 0) { - CurrentUserUtils.propertiesWidth = 0; + toggleTopBar = () => { + if (CurrentUserUtils.headerBarHeight > 0) { + CurrentUserUtils.headerBarHeight = 0; } else { - CurrentUserUtils.propertiesWidth = 250; + CurrentUserUtils.headerBarHeight = 60; } } @@ -137,14 +137,13 @@ export class CollectionMenu extends AntimodeMenu{ render() { - const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; - const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel"; + const propIcon = CurrentUserUtils.headerBarHeight > 0 ? "angle-double-up" : "angle-double-down"; + const propTitle = CurrentUserUtils.headerBarHeight > 0 ? "Close Header Bar" : "Open Header Bar"; - const prop = {propTitle}
    } key="properties" placement="bottom"> + const prop = {propTitle}
    } key="topar" placement="bottom">
    0 ? Colors.MEDIUM_BLUE : undefined }} - key="properties" - onPointerDown={this.toggleProperties}> + onPointerDown={this.toggleTopBar}>
    ; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 19485a0eb..4f92e305e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -6,7 +6,7 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { ObjectField } from '../../../fields/ObjectField'; import { ScriptField } from '../../../fields/ScriptField'; -import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { returnEmptyString } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; @@ -237,6 +237,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent this.props.PanelWidth(); + childHideResizeHandles = () => this.props.childHideResizeHandles?.() ?? BoolCast(this.Document.childHideResizeHandles); + childHideDecorationTitle = () => this.props.childHideDecorationTitle?.() ?? BoolCast(this.Document.childHideDecorationTitle); childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); } @@ -258,6 +260,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent { closeWrap.id = "lm_closeWrap"; closeWrap.onclick = (e: MouseEvent) => { tab.header.parent.contentItem.remove(); + Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); }; const docIcon = ; @@ -179,6 +180,7 @@ export class TabDocView extends React.Component { tab.closeElement.off('click') //unbind the current click handler .click(function () { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); + Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", doc); Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true); SelectionManager.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index b6b59a2d6..66cc3613d 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -241,7 +241,9 @@ export class CollectionMulticolumnView extends CollectionSubView() { } return this.props.addDocTab(doc, where); } - isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + isContentActive = () => this.props.isSelected() ? true : undefined; + isChildContentActive = () => this.props.isContentActive?.() === false ? false : undefined; + isChildDocumentActive = () => (this.props.childDocumentsActive?.() && this.props.isDocumentActive?.()) || this.isContentActive() || this.props.isContentActive(); getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return this.props.isSelected() || this.props.isContentActive(); + isContentActive = () => this.props.isSelected() ? true : undefined; + isChildContentActive = () => this.props.isContentActive?.() === false ? false : undefined; + isChildDocumentActive = () => (this.props.childDocumentsActive?.() && this.props.isDocumentActive?.()) || this.isContentActive() || this.props.isContentActive(); getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return {!this._retryThumb || !this.thumbShown() ? (null) : diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0e29461a6..822bc996b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -635,7 +635,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }), icon: icon }); }); - !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); const highlighting: ContextMenuProps[] = []; const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others"]; const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; -- cgit v1.2.3-70-g09d2 From d50b851d8e92797d214fc35cf243bfb29e970b36 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 May 2022 21:58:26 -0400 Subject: simplified dashboards by getting rid of confusing on-screen/off-screen tabs. Now off screen tabs appear in the header bar which is not currently specific to a dashboard. --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 33 +-------------- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionDockingView.tsx | 48 +++++++++++++++++----- src/client/views/collections/TabDocView.tsx | 3 +- 5 files changed, 43 insertions(+), 49 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6fa467368..c3b6953b5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -905,9 +905,7 @@ export namespace Docs { } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true }); - const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true }); - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 1e42b9073..9212d8ed9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1172,22 +1172,8 @@ export class CurrentUserUtils { freeformDoc.context = dashboardDoc; // switching the tabs from the datadoc to the regular doc - const dashboardTabs = dashboardDoc[DataSym].data; - dashboardDoc[DataSym].data = new List(); - dashboardDoc.data = dashboardTabs; - - // collating all docs on the dashboard to make a data-all field - const allDocs = new List(); - const allDocs2 = new List(); // Array.from, spread, splice all cause so stack or acl issues for some reason - DocListCast(dashboardTabs).forEach(doc => { - const tabDocs = DocListCast(doc.data); - allDocs.push(...tabDocs); - allDocs2.push(...tabDocs); - }); - dashboardDoc[DataSym]["data-all"] = allDocs; - dashboardDoc["data-all"] = allDocs2; - DocListCast(dashboardDoc.data).forEach(doc => doc.dashboard = dashboardDoc); - DocListCast(dashboardDoc.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any; + const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); + dashboardDoc.data = new List(dashboardTabs); userDoc.activePresentation = presentation; @@ -1258,21 +1244,6 @@ ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { }, "adds Dashboard to set of Dashboards"); -/** - * Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard. - */ -ScriptingGlobals.add(function dynamicOffScreenDocs(dashboard: Doc) { - if (dashboard[DataSym] instanceof Doc) { - const allDocs = DocListCast(dashboard["data-all"]); - const onScreenTab = DocListCast(dashboard.data)[0]; - const onScreenDocs = DocListCast(onScreenTab.data); - return new List(allDocs.reduce((result: Doc[], doc) => { - !onScreenDocs.includes(doc) && !onScreenDocs.includes(doc.aliasOf as Doc) && (result.push(doc)); - return result; - }, [])); - } - return []; -}); ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) { let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined; if (selected && checkParent) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 69e394790..f3dc2cf85 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -92,7 +92,7 @@ export class MainView extends React.Component { @computed private get headerBarDoc() { return this.userDoc ? CurrentUserUtils.MyHeaderBarDoc : CurrentUserUtils.MyHeaderBarDoc; } @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } - headerBarDocWidth = () => this._dashUIWidth; + headerBarDocWidth = () => this.mainDocViewWidth(); headerBarDocHeight = () => CurrentUserUtils.headerBarHeight ?? 0; topMenuHeight = () => 35; topMenuWidth = returnZero; // value is ignored ... @@ -102,7 +102,7 @@ export class MainView extends React.Component { leftMenuFlyoutHeight = () => this._dashUIHeight; propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0)); propertiesHeight = () => this._dashUIHeight; - mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth(); + mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth(); mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); componentDidMount() { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 30f60bba6..1d65256a6 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -81,6 +81,8 @@ export class CollectionDockingView extends CollectionSubView() { tabItemDropped = () => DragManager.CompleteWindowDrag?.(false); tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => { + const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc; + dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc])); DragManager.CompleteWindowDrag = (aborted: boolean) => { if (aborted) { proxy._dragListener.AbortDrag(); @@ -90,6 +92,8 @@ export class CollectionDockingView extends CollectionSubView() { 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; + } else { + DragManager.DocDragData?.droppedDocuments.forEach(doc => this.addToTabDocList(doc)); } finishDrag?.(aborted); }; @@ -100,12 +104,43 @@ export class CollectionDockingView extends CollectionSubView() { this.stateChanged(); } + addToTabDocList = (document: Doc) => { + const instance = CollectionDockingView.Instance; + if (instance) { + const docList = DocListCast(instance.props.Document[DataSym].data); + // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases + !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data", document); + // adds an alias of the doc to the data-all field of the layoutdocs of the aliases + DocListCast(instance.props.Document.aliases).forEach(alias => { + const aliasDocList = DocListCast(alias.data); + // if aliasDocList contains the alias, don't do anything + // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias + !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); + }); + } + } + removeFromTabDocList = (document: Doc) => { + const instance = CollectionDockingView.Instance; + if (instance) { + // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases + Doc.RemoveDocFromList(instance.props.Document[DataSym], "data", document); + // adds an alias of the doc to the data-all field of the layoutdocs of the aliases + DocListCast(instance.props.Document.aliases).forEach(alias => { + // if aliasDocList contains the alias, don't do anything + // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias + Doc.RemoveDocFromList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); + }); + document.type !== DocumentType.KVP && Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", document); + } + } + @undoBatch public static CloseSplit(document: Opt, panelName?: string): boolean { const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document); if (tab) { const j = tab.header.parent.contentItems.indexOf(tab.contentItem); if (j !== -1) { + CollectionDockingView.Instance.removeFromTabDocList(tab.dashDoc as Doc); tab.header.parent.contentItems[j].remove(); return CollectionDockingView.Instance.layoutChanged(); } @@ -185,16 +220,7 @@ export class CollectionDockingView extends CollectionSubView() { const instance = CollectionDockingView.Instance; if (!instance) return false; else { - const docList = DocListCast(instance.props.Document[DataSym]["data-all"]); - // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases - !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document); - // adds an alias of the doc to the data-all field of the layoutdocs of the aliases - DocListCast(instance.props.Document[DataSym].aliases).forEach(alias => { - const aliasDocList = DocListCast(alias["data-all"]); - // if aliasDocList contains the alias, don't do anything - // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias - !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); - }); + instance.addToTabDocList(document); } const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName); @@ -477,7 +503,7 @@ export class CollectionDockingView extends CollectionSubView() { //if (confirm('really close this?')) { if (!stack.parent.parent.isRoot || stack.parent.contentItems.length > 1) { stack.remove(); - stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true)); + stack.contentItems.forEach((contentItem: any) => CollectionDockingView.Instance.removeFromTabDocList(contentItem.tab.DashDoc)); } else { alert('cant delete the last stack'); } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 0070d0b9d..88d14fdfe 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -180,8 +180,7 @@ export class TabDocView extends React.Component { tab.closeElement.off('click') //unbind the current click handler .click(function () { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", doc); - Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true); + CollectionDockingView.Instance?.removeFromTabDocList(doc); SelectionManager.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); }); -- cgit v1.2.3-70-g09d2 From 13924d1b0bd674146b940bb6e31ad6054c447b09 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 May 2022 22:15:17 -0400 Subject: fixed warnings. --- src/client/util/CurrentUserUtils.ts | 6 +++--- src/client/views/collections/CollectionTreeView.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9212d8ed9..e4dc175bd 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -192,7 +192,7 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.BUTTON, "data", () => fontBox()), + makeIconTemplate(DocumentType.BUTTON, "data", fontBox), // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) ].filter(d => d).map(d => d!); @@ -542,7 +542,7 @@ export class CurrentUserUtils { title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true, _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true, })); - if (!DocListCast(doc.myCreators).includes(creatorBtns as Doc) || !DocListCast(doc.myCreators).includes(templateBtns as Doc)) Doc.GetProto(doc.myCreators as Doc).data = new List([creatorBtns, templateBtns]); + if (!DocListCast(doc.myCreators).includes(creatorBtns) || !DocListCast(doc.myCreators).includes(templateBtns)) Doc.GetProto(doc.myCreators as Doc).data = new List([creatorBtns, templateBtns]); doc.myColorPicker = doc.myColorPicker ?? new PrefetchProxy(Docs.Create.ColorDocument({ title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true @@ -838,7 +838,7 @@ export class CurrentUserUtils { linearViewExpandable: true, icon: params.title, _height: 30, linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, - }, params.subMenu.map(subParams => btnFunc(subParams))))); + }, params.subMenu.map(btnFunc)))); } } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 99ffb9bb0..c49580046 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -61,7 +61,7 @@ export class CollectionTreeView extends CollectionSubView Date: Wed, 25 May 2022 15:18:46 -0400 Subject: variety of small fixes for text / sharing. made typed text default to 35 height so that when shared it doesn't start big and jump to small. changed permissions to be private until an Acl is set - this prevents private docs from flicking on momentarily when shared since fields are not distributed atomically. added Shift/Alt/Ctrl Enter for freeform and stacking views to create neighboring docs. fixed first typed char of text to have a user_mark. made shared text doc header overlap to prevent scrolling. --- src/client/DocServer.ts | 60 ++++++++++------------ src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/MainView.tsx | 4 +- src/client/views/StyleProvider.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 22 ++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 16 +++--- .../formattedText/ProsemirrorExampleTransfer.ts | 14 +++-- src/fields/util.ts | 7 ++- 10 files changed, 77 insertions(+), 57 deletions(-) (limited to 'src/client/util') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index e498a7cca..dbc4783d8 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -364,40 +364,36 @@ export namespace DocServer { const proms: Promise[] = []; runInAction(() => { for (const field of fields) { - if (field !== undefined && field !== null && !_cache[field.id]) { + const cached = _cache[field.id]; + if (!cached) { // deserialize - const cached = _cache[field.id]; - if (!cached) { - const prom = SerializationHelper.Deserialize(field).then(deserialized => { - fieldMap[field.id] = deserialized; - - //overwrite or delete any promises (that we inserted as flags - // to indicate that the field was in the process of being fetched). Now everything - // should be an actual value within or entirely absent from the cache. - if (deserialized !== undefined) { - _cache[field.id] = deserialized; - } else { - delete _cache[field.id]; - } - return deserialized; - }); - // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. - // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - // The mapping in the .then call ensures that when other callers await these promises, they'll - // get the resolved field - _cache[field.id] = prom; - - // adds to a list of promises that will be awaited asynchronously - proms.push(prom); - } else if (cached instanceof Promise) { - proms.push(cached as any); - } - } else if (_cache[field.id] instanceof Promise) { - proms.push(_cache[field.id] as any); - (_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f); + const prom = SerializationHelper.Deserialize(field).then(deserialized => { + fieldMap[field.id] = deserialized; + + //overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. + if (deserialized !== undefined) { + _cache[field.id] = deserialized; + } else { + delete _cache[field.id]; + } + return deserialized; + }); + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + _cache[field.id] = prom; + + // adds to a list of promises that will be awaited asynchronously + proms.push(prom); + } else if (cached instanceof Promise) { + proms.push(cached as any); + cached.then((f: any) => fieldMap[field.id] = f); } else if (field) { - proms.push(_cache[field.id] as any); + proms.push(cached as any); fieldMap[field.id] = DocServer.GetCachedRefField(field.id) || field; } } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c3b6953b5..ed37c48dc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -369,7 +369,7 @@ export namespace Docs { [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, options: { - _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, + _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, links: "@links(self)" } }], diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e4dc175bd..436237d6c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1184,7 +1184,7 @@ export class CurrentUserUtils { public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { const tbox = Docs.Create.TextDocument("", { _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor, - _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, title + _width: width || 200, _height: 35, x: x, y: y, _fitWidth: true, _autoHeight: true, title }); const template = Doc.UserDoc().defaultTextLayout; if (template instanceof Doc) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 183efc944..ff2857739 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -120,8 +120,8 @@ export class MainView extends React.Component { } this._sidebarContent.proto = undefined; if (!MainView.Live) { - DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition", - "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap", + DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "showSidebar", "sidebarWidthPercent", "viewTransition", + "panX", "panY", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap", "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's } DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index c44443264..553f84a67 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -70,6 +70,7 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab // export function DefaultStyleProvider(doc: Opt, props: Opt, property: string): any { + const remoteDocHeader = "author;creationDate;noMargin"; const docProps = testDocProps(props) ? props : undefined; const selected = property.includes(":selected"); const isCaption = property.includes(":caption"); @@ -111,7 +112,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color)); @@ -126,7 +127,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || - doc?.type === DocumentType.RTF || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; + (doc?.type === DocumentType.RTF && !showTitle()?.includes("noMargin")) || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY; let docColor: Opt = diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4630b3bf2..dddae4a34 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -28,6 +28,8 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; +import { FieldViewProps } from "../nodes/FieldView"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; const _global = (window /* browser */ || global /* node */) as any; @@ -207,6 +209,25 @@ export class CollectionStackingView extends CollectionSubView { + const docView = fieldProps.DocumentView?.(); + if (docView && ["Enter"].includes(e.key) && e.ctrlKey) { + e.stopPropagation?.(); + const below = !e.altKey && e.key !== "Tab"; + const layoutKey = StrCast(docView.LayoutFieldKey); + const newDoc = Doc.MakeCopy(docView.rootDoc, true); + const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; + newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; + if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = docView.rootDoc[layoutKey]; + } + Doc.GetProto(newDoc).text = undefined; + FormattedTextBox.SelectOnLoad = newDoc[Id]; + return this.addDocument?.(newDoc); + } + } isContentActive = () => this.props.isSelected() || this.props.isContentActive(); getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; @@ -225,6 +246,7 @@ export class CollectionStackingView extends CollectionSubView { const docView = fieldProps.DocumentView?.(); - if (docView && docView.rootDoc._singleLine && ["Tab", "Enter"].includes(e.key)) { + if (docView && (e.ctrlKey || e.shiftKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) { e.stopPropagation?.(); const below = !e.altKey && e.key !== "Tab"; const layoutKey = StrCast(docView.LayoutFieldKey); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 822bc996b..d468822c0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -245,8 +245,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - const tsel = this._editorView.state.selection.$from; - //tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype @@ -348,7 +346,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } autoLink = () => { - if (this._editorView) { + if (this._editorView?.state.doc.textContent) { const newAutoLinks = new Set(); const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords); const f = this._editorView.state.selection.from; @@ -962,7 +960,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); quickScroll = undefined; - setTimeout(this.tryUpdateScrollHeight, 10); + this.tryUpdateScrollHeight(); } pushToGoogleDoc = async () => { @@ -1193,19 +1191,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())); if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { + const selLoadChar = FormattedTextBox.SelectOnLoadChar; FormattedTextBox.SelectOnLoad = ""; this.props.select(false); - if (FormattedTextBox.SelectOnLoadChar && this._editorView) { + if (selLoadChar && this._editorView) { const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); - FormattedTextBox.SelectOnLoadChar = ""; - } else if (curText && !FormattedTextBox.DontSelectInitialText) { - selectAll(this._editorView!.state, this._editorView?.dispatch); + } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { + selectAll(this._editorView.state, this._editorView?.dispatch) this.startUndoTypingBatch(); + } else if (this._editorView) { + this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } FormattedTextBox.DontSelectInitialText = false; } diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 0dd0a8411..e979ae59e 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -80,8 +80,10 @@ export function buildKeymap>(schema: S, props: any, mapKey //Commands for lists bind("Ctrl-i", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); + bind("Ctrl-Tab", () => props.onKey?.(event, props) ? true : true); + bind("Alt-Tab", () => props.onKey?.(event, props) ? true : true); bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (props.onKey?.({ key: "Tab" }, props)) return true; + if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const ref = state.selection; const range = ref.$from.blockRange(ref.$to); @@ -106,7 +108,7 @@ export function buildKeymap>(schema: S, props: any, mapKey }); bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (props.onKey?.({ key: "Tab", shiftKey: true })) return true; // single line docs don't process tabs so that their containers can decide what to do. This should be a prop + if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); @@ -154,11 +156,9 @@ export function buildKeymap>(schema: S, props: any, mapKey return tx; }; - //Command to create a text document to the right of the selected textbox - bind("Alt-Enter", () => true); - //Command to create a text document to the bottom of the selected textbox - bind("Ctrl-Enter", () => true); + bind("Alt-Enter", () => props.onKey?.(event, props) ? true : true); + bind("Ctrl-Enter", () => props.onKey?.(event, props) ? true : true); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); bind("Backspace", (state: EditorState, dispatch: (tx: Transaction>) => void) => { @@ -244,8 +244,6 @@ export function buildKeymap>(schema: S, props: any, mapKey return false; }); - // mac && bind("Ctrl-Enter", cmd); - // bind("Mod-Enter", cmd); bind("Shift-Enter", cmd); return keys; diff --git a/src/fields/util.ts b/src/fields/util.ts index c708affe3..ef5ac79b8 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -175,8 +175,11 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { * Calculates the effective access right to a document for the current user. */ export function GetEffectiveAcl(target: any, user?: string): symbol { - return !target ? AclPrivate : - target[UpdatingFromServer] ? AclAdmin : getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) + if (!target) return AclPrivate; + if (target[UpdatingFromServer]) return AclAdmin; + // authored documents are private until an ACL is set. this also fixes notes that flicker on and off when a remote types to create a private note into a shared collection. + if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate; + return getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } function getPropAcl(target: any, prop: string | symbol | number) { -- cgit v1.2.3-70-g09d2 From 76d737a05093e4132f2ab56630c4003879747ef9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 26 May 2022 15:36:32 -0400 Subject: removed setting 'data' field of docking views on aliases. assigned acl's to delegates to match prototype. changed how documents are created to make sure acl's are set first so that, when sharing, remote clients will get the ACLs first and not render documents transiently when they don't have permissions. turned off inheriting ACL's from outer collection --- src/client/documents/Documents.ts | 15 +++++++++------ src/client/util/CurrentUserUtils.ts | 3 --- src/client/views/collections/CollectionDockingView.tsx | 17 ++--------------- src/client/views/global/globalCssVariables.scss | 2 +- src/fields/Doc.ts | 1 + src/fields/util.ts | 3 ++- 6 files changed, 15 insertions(+), 26 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ed37c48dc..8d29bcebe 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -639,13 +639,14 @@ export namespace Docs { const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); + dataProps["acl-Override"] = "None"; + dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + dataProps.system = viewProps.system; dataProps.isPrototype = true; dataProps.author = Doc.CurrentUserEmail; dataProps.creationDate = new DateField; dataProps[`${fieldKey}-lastModified`] = new DateField; - dataProps["acl-Override"] = "None"; - dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; dataProps[fieldKey] = data; @@ -654,10 +655,12 @@ export namespace Docs { dataProps[fieldKey + "-annotations"] = new List(); const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); - viewProps.author = Doc.CurrentUserEmail; - viewProps["acl-Override"] = "None"; - viewProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true); + const viewFirstProps: { [id: string]: any } = {}; + viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + viewFirstProps["acl-Override"] = "None"; + viewFirstProps.author = Doc.CurrentUserEmail; + const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); + Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) && diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 436237d6c..083530ce7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1236,9 +1236,6 @@ ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { "Remove Dashboard from Dashboards"); ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); - dashboardAlias.data = new List(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder))); - DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias); - DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any; Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias); }, diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1d65256a6..c60434d95 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -109,27 +109,14 @@ export class CollectionDockingView extends CollectionSubView() { if (instance) { const docList = DocListCast(instance.props.Document[DataSym].data); // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases - !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data", document); - // adds an alias of the doc to the data-all field of the layoutdocs of the aliases - DocListCast(instance.props.Document.aliases).forEach(alias => { - const aliasDocList = DocListCast(alias.data); - // if aliasDocList contains the alias, don't do anything - // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias - !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); - }); + !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document, "data", document); } } removeFromTabDocList = (document: Doc) => { const instance = CollectionDockingView.Instance; if (instance) { // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases - Doc.RemoveDocFromList(instance.props.Document[DataSym], "data", document); - // adds an alias of the doc to the data-all field of the layoutdocs of the aliases - DocListCast(instance.props.Document.aliases).forEach(alias => { - // if aliasDocList contains the alias, don't do anything - // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias - Doc.RemoveDocFromList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); - }); + Doc.RemoveDocFromList(instance.props.Document, "data", document); document.type !== DocumentType.KVP && Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", document); } } diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index 520ac9357..a14634bdc 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -10,7 +10,7 @@ $black: #000000; $light-blue: #bdddf5; $light-blue-transparent: #bdddf590; $medium-blue: #4476f7; -$medium-blue-alt: #4476f73d; +$medium-blue-alt: #0047ff54; $pink: #e0217d; $yellow: #f5d747; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 194b3ba27..b0a45091e 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -821,6 +821,7 @@ export namespace Doc { delegate[Initializing] = true; delegate.proto = doc; delegate.author = Doc.CurrentUserEmail; + Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => delegate[key] = doc[key]); if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate); title && (delegate.title = title); delegate[Initializing] = false; diff --git a/src/fields/util.ts b/src/fields/util.ts index ef5ac79b8..d1e565774 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -138,6 +138,7 @@ export function denormalizeEmail(email: string) { * Copies parent's acl fields to the child */ export function inheritParentAcls(parent: Doc, child: Doc) { + return; const dataDoc = parent[DataSym]; for (const key of Object.keys(dataDoc)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. @@ -177,7 +178,7 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { export function GetEffectiveAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer]) return AclAdmin; - // authored documents are private until an ACL is set. this also fixes notes that flicker on and off when a remote types to create a private note into a shared collection. + // authored documents are private until an ACL is set. if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate; return getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } -- cgit v1.2.3-70-g09d2 From 53cae5e2ab9267295a824ff721c119ada5e5dc20 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 31 May 2022 13:03:19 -0400 Subject: added a pop up menu for viewing dashField view collection. added a menu button for turning off autoLinking to text selection. added ability to add button to top contextMenu buttons without blowing away db. --- src/client/util/CurrentUserUtils.ts | 34 +++++++++ src/client/views/MainView.tsx | 2 + .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/button/FontIconBox.tsx | 8 +++ .../views/nodes/formattedText/DashFieldView.tsx | 81 +++++++++++++++++----- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 ++-- .../formattedText/ProsemirrorExampleTransfer.ts | 2 + .../views/nodes/formattedText/RichTextMenu.tsx | 14 +++- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/nodes/formattedText/marks_rts.ts | 14 ++++ 10 files changed, 144 insertions(+), 27 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 083530ce7..c4e2afd08 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -745,6 +745,7 @@ export class CurrentUserUtils { { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' }, { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' }, { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' }, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", click: 'toggleNoAutoLinkAnchor(_readOnly_)' }, ]; return tools; } @@ -834,11 +835,44 @@ export class CurrentUserUtils { !params.subMenu ? btnFunc(params) : CurrentUserUtils.linearButtonList({ + title: params.title, linearViewSubMenu: true, flexGap: 0, ignoreClick: true, linearViewExpandable: true, icon: params.title, _height: 30, linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, }, params.subMenu.map(btnFunc)))); + } else { + const menuBtnList = DocListCast((doc.contextMenuBtns as Doc).data); + let prev = ""; + CurrentUserUtils.contextMenuTools(doc).forEach(params => { + const menuBtnDoc = menuBtnList.find(doc => doc.title === params.title); + if (!menuBtnDoc) { + const newMenuBtnDoc = !params.subMenu ? + btnFunc(params) : + CurrentUserUtils.linearButtonList({ + title: params.title, + linearViewSubMenu: true, flexGap: 0, ignoreClick: true, + linearViewExpandable: true, icon: params.title, _height: 30, + linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, + hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, + }, params.subMenu.map(btnFunc)); + const after = menuBtnList.find(doc => doc.title === prev); + Doc.AddDocToList(doc.contextMenuBtns as Doc, "data", newMenuBtnDoc, after, false, !after); + } + const subMenuBtnList = menuBtnDoc?.data ? DocListCast(menuBtnDoc.data) : undefined; + if (menuBtnDoc && subMenuBtnList && params.subMenu && DocListCast(doc.data).length !== subMenuBtnList.length) { + let prevSub = ""; + params.subMenu.forEach(sub => { + if (!subMenuBtnList.find(doc => doc.title === sub.title)) { + const newSubMenuBtnDoc = btnFunc(sub); + const after = subMenuBtnList.find(doc => doc.title === prevSub); + Doc.AddDocToList(menuBtnDoc, "data", newSubMenuBtnDoc, after, false, !prevSub); + } + prevSub = params.title; + }) + } + prev = params.title; + }); } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ff2857739..55190001b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -50,6 +50,7 @@ import { AudioBox } from './nodes/AudioBox'; import { ButtonType } from './nodes/button/FontIconBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; +import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from './nodes/formattedText/RichTextMenu'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; @@ -686,6 +687,7 @@ export class MainView extends React.Component { + diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f4cb08f8f..259cc1ee5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1166,7 +1166,7 @@ export class CollectionFreeFormView extends CollectionSubView { const docView = fieldProps.DocumentView?.(); - if (docView && (e.ctrlKey || e.shiftKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) { + if (docView && (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) { e.stopPropagation?.(); const below = !e.altKey && e.key !== "Tab"; const layoutKey = StrCast(docView.LayoutFieldKey); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index e6d8fe88d..625f5d14a 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -666,6 +666,14 @@ ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: b if (editorView) RichTextMenu.Instance.setFontSize(size); else Doc.UserDoc()._fontSize = size; }); +ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) { + const editorView = RichTextMenu.Instance?.TextView?.EditorView; + if (checkResult) { + return (editorView ? RichTextMenu.Instance.noAutoLink : Doc.UserDoc().noAutoLink) ? Colors.MEDIUM_BLUE : "transparent"; + } + if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor(); + else Doc.UserDoc().noAutoLink = Doc.UserDoc().noAutoLink ? true : false; +}); ScriptingGlobals.add(function toggleBold(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 1a8352b72..6a3f9ed00 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -8,35 +8,41 @@ import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { ComputedField } from "../../../../fields/ScriptField"; import { Cast, StrCast } from "../../../../fields/Types"; import { DocServer } from "../../../DocServer"; -import { DocUtils } from "../../../documents/Documents"; import { CollectionViewType } from "../../collections/CollectionView"; import "./DashFieldView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import React = require("react"); +import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils"; +import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; +import { Tooltip } from "@material-ui/core"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export class DashFieldView { _fieldWrapper: HTMLDivElement; // container for label and value constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey); + this._fieldWrapper = document.createElement("div"); this._fieldWrapper.style.width = node.attrs.width; this._fieldWrapper.style.height = node.attrs.height; this._fieldWrapper.style.fontWeight = "bold"; this._fieldWrapper.style.position = "relative"; this._fieldWrapper.style.display = "inline-block"; + this._fieldWrapper.textContent = node.attrs.fieldKey.startsWith("#") ? node.attrs.fieldKey : node.attrs.fieldKey + " " + strVal; this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; - ReactDOM.render( ReactDOM.render(, this._fieldWrapper); + />, this._fieldWrapper)); (this as any).dom = this._fieldWrapper; } destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } @@ -76,16 +82,17 @@ export class DashFieldViewInternal extends React.Component { r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r)); r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); - r?.addEventListener("pointerdown", action((e) => { - e.stopPropagation(); - })); + r?.addEventListener("pointerdown", action(e => e.stopPropagation())); }} > {strVal} ; @@ -161,7 +166,7 @@ export class DashFieldViewInternal extends React.Component 1 ? new List(splits) : newText; if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; @@ -173,11 +178,7 @@ export class DashFieldViewInternal extends React.Component { - e.stopPropagation(); + createPivotForField = (e: React.MouseEvent) => { let container = this.props.tbox.props.ContainingCollectionView; while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) { container = container.props.ContainingCollectionView; @@ -196,6 +197,16 @@ export class DashFieldViewInternal extends React.Component { + setupMoveUpEvents(this, e, returnFalse, returnFalse, (e) => { + DashFieldViewMenu.createFieldView = this.createPivotForField; + DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16); + }); + } + render() { return
    ; } +} +@observer +export class DashFieldViewMenu extends AntimodeMenu { + static Instance: DashFieldViewMenu; + static createFieldView: (e: React.MouseEvent) => void = emptyFunction; + constructor(props: any) { + super(props); + DashFieldViewMenu.Instance = this; + } + @action + showFields = (e: React.MouseEvent) => { + DashFieldViewMenu.createFieldView(e); + DashFieldViewMenu.Instance.fadeOut(true); + } + + public show = (x: number, y: number) => { + this.jumpTo(x, y, true); + const hideMenu = () => { + this.fadeOut(true); + document.removeEventListener("pointerdown", hideMenu); + } + document.addEventListener("pointerdown", hideMenu) + } + render() { + const buttons = [ + {"Remove Link Anchor"}
    }> + + , + ]; + + return this.getElement(buttons); + } } \ No newline at end of file diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d468822c0..ce82821b6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -391,16 +391,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp flattened1.forEach((flat, i) => { const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - alink = alink ?? (DocListCast(this.Document.links).find(link => - Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && - Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, - LinkManager.AutoKeywords)!); - newAutoLinks.add(alink); const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); const sel = flattened[i]; tr = tr.addMark(sel.from, sel.to, splitter); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { - if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { + if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { + alink = alink ?? (DocListCast(this.Document.links).find(link => + Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && + Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, + LinkManager.AutoKeywords)!); + newAutoLinks.add(alink); const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" }); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index e979ae59e..fb49b0698 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -82,6 +82,8 @@ export function buildKeymap>(schema: S, props: any, mapKey bind("Ctrl-Tab", () => props.onKey?.(event, props) ? true : true); bind("Alt-Tab", () => props.onKey?.(event, props) ? true : true); + bind("Meta-Tab", () => props.onKey?.(event, props) ? true : true); + bind("Meta-Enter", () => props.onKey?.(event, props) ? true : true); bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index c5f76cc8d..3df1e45a5 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -35,6 +35,7 @@ export class RichTextMenu extends AntimodeMenu { public _brushMap: Map> = new Map(); @observable private collapsed: boolean = false; + @observable private _noLinkActive: boolean = false; @observable private _boldActive: boolean = false; @observable private _italicsActive: boolean = false; @observable private _underlineActive: boolean = false; @@ -79,6 +80,7 @@ export class RichTextMenu extends AntimodeMenu { this._reaction?.(); } + @computed get noAutoLink() { return this._noLinkActive; } @computed get bold() { return this._boldActive; } @computed get underline() { return this._underlineActive; } @computed get italics() { return this._italicsActive; } @@ -220,7 +222,7 @@ export class RichTextMenu extends AntimodeMenu { let activeMarks: MarkType[] = []; if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks; - const markGroup = [schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; + const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); //current selection const { empty, ranges, $to } = this.view.state.selection as TextSelection; @@ -264,6 +266,7 @@ export class RichTextMenu extends AntimodeMenu { setActiveMarkButtons(activeMarks: MarkType[] | undefined) { if (!activeMarks) return; + this._noLinkActive = false; this._boldActive = false; this._italicsActive = false; this._underlineActive = false; @@ -273,6 +276,7 @@ export class RichTextMenu extends AntimodeMenu { activeMarks.forEach(mark => { switch (mark.name) { + case "noAutoLinkAnchor": this._noLinkActive = true; break; case "strong": this._boldActive = true; break; case "em": this._italicsActive = true; break; case "underline": this._underlineActive = true; break; @@ -283,6 +287,14 @@ export class RichTextMenu extends AntimodeMenu { }); } + toggleNoAutoLinkAnchor = () => { + if (this.view) { + const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor); + this.setMark(mark, this.view.state, this.view.dispatch, false); + this.TextView.autoLink(); + this.view.focus(); + } + } toggleBold = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 427e05edb..8851d52e4 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -348,7 +348,7 @@ export class RichTextRules { this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`; } const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag }); - return state.tr.deleteRange(start, end).insert(start, fieldView); + return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(" "); }), diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 1f6ce014f..2fde5c7ba 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -48,6 +48,20 @@ export const marks: { [index: string]: MarkSpec } = { return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; } }, + noAutoLinkAnchor: { + attrs: {}, + inclusive: false, + parseDOM: [{ + tag: "div", getAttrs(dom: any) { + return { + noAutoLink: dom.getAttribute("data-noAutoLink"), + }; + } + }], + toDOM(node: any) { + return ["span", { "data-noAutoLink": "true" }, 0]; + } + }, // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text, // and a title for use in menus and hover. `title` // defaults to the empty string. Rendered and parsed as an `` -- cgit v1.2.3-70-g09d2 From f792c3573aec834bb5540d1d2ceb4486a92e03ef Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 1 Jun 2022 10:24:24 -0400 Subject: Merge with jenny trackScreen --- src/client/util/RecordingApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 7592199eb..7d8df0bfc 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -149,7 +149,7 @@ export class RecordingApi { // set the view to the current view if (view === this.recordingFFView || view === null) return; - //this.recordingFFView = view; + // this.recordingFFView = view; // set the reaction to track the movements this.disposeFunc = reaction( () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }), -- cgit v1.2.3-70-g09d2 From 22d39de00ed9a246c520c4c9b1d049a151465d73 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 1 Jun 2022 14:01:06 -0400 Subject: now tracks and replays scaling accurately --- src/client/util/RecordingApi.ts | 83 +++++++++++----------- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 ++- src/client/views/nodes/VideoBox.tsx | 5 +- 3 files changed, 49 insertions(+), 49 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 7d8df0bfc..87f44bec4 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -3,11 +3,14 @@ import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; import { Doc } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; +import { scaleDiverging } from "d3-scale"; +import { Transform } from "./Transform"; type Movement = { time: number, panX: number, panY: number, + scale: number, } type Presentation = { @@ -120,7 +123,7 @@ export class RecordingApi { this.absoluteStart = new Date().getTime() - this.absoluteStart } - private trackMovements = (panX: number, panY: number): Error | undefined => { + private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { // ensure we are recording if (!this.isRecording) { return new Error('[recordingApi.ts] trackMovements()') @@ -133,8 +136,7 @@ export class RecordingApi { // get the time const time = new Date().getTime() - this.absoluteStart // make new movement object - console.log(time) - const movement: Movement = { time, panX, panY } + const movement: Movement = { time, panX, panY, scale } // add that movement to the current presentation data's movement array this.currentPresentation.movements && this.currentPresentation.movements.push(movement) @@ -147,13 +149,16 @@ export class RecordingApi { // set the FFView that will be used in a reaction to track the movements public setRecordingFFView = (view: CollectionFreeFormView): void => { // set the view to the current view - if (view === this.recordingFFView || view === null) return; + if (view === this.recordingFFView || view == null) return; // this.recordingFFView = view; // set the reaction to track the movements this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1) }), - (res) => (res.x !== -1 && res.y !== -1) && this.trackMovements(res.x, res.y) + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), + (res) => { + console.log('scale', res.scale); + (res.x !== -1 && res.y !== -1 && this.isRecording) && this.trackMovements(res.x, res.y, res.scale); + } ) // for now, set the most recent recordingFFView to the playFFView @@ -175,6 +180,8 @@ export class RecordingApi { this.playFFView = view } + // pausing movements will dispose all timers that are planned to replay the movements + // play movemvents will recreate them when the user resumes the presentation public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { return new Error('[recordingApi.ts] pauseMovements() failed: no view') @@ -196,7 +203,9 @@ export class RecordingApi { // by calling pause on the VideoBox, the pauseMovements will be called public pauseVideoAndMovements = (): boolean => { this.videoBox?.Pause() - return this.videoBox === null + + this.pauseMovements() + return this.videoBox == null } public _isPlaying = false; @@ -215,48 +224,36 @@ export class RecordingApi { Doc.UserDoc().presentationMode = 'watching'; // TODO: consider this bug at the end of the clip on seek - // console.log(timeViewed) this.videoBox = videoBox || null; - const document = this.playFFView.Document - const { movements } = presentation + // only get the movements that are remaining in the video time left + const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) - const filteredMovements = movements.filter(movement => movement.time > timeViewed * 1000) - - document._panX = filteredMovements[0].panX; - document._panY = filteredMovements[0].panY; - + // helper to replay a movement + const document = this.playFFView + let preScale = -1; + const zoomAndPan = (movement: Movement) => { + const { panX, panY, scale } = movement; + (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + preScale = scale; + } + + // set the first frame to be at the start of the pres + zoomAndPan(filteredMovements[0]); + + // make timers that will execute each movement at the correct replay time this.timers = filteredMovements.map(movement => { - const { panX, panY, time } = movement - const timeDiff = time - timeViewed*1000 + const timeDiff = movement.time - timeViewed*1000 return setTimeout(() => { - document._panX = panX; - document._panY = panY; - if (movement === movements[movements.length - 1]) { - // stuff for recording API - RecordingApi.Instance._isPlaying = false; - } + // replay the movement + zoomAndPan(movement) + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; }, timeDiff) - }, []) - - // this.timers = movements.reduce((arr: NodeJS.Timeout[], movement) => { - // const { panX, panY, time } = movement - - // const timeDiff = time - timeViewed*1000 - // if (timeDiff < 0) return arr; - - // // set the pan to what was stored - // arr.push(setTimeout(() => { - // document._panX = panX; - // document._panY = panY; - // if (movement === movements[movements.length - 1]) { - // // stuff for recording API - // RecordingApi.Instance._isPlaying = false; - // } - // }, timeDiff)) - // return arr; - // }, []) - + }) } // Unfinished code for tracing multiple free form views diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index aa2e0c417..19ae99743 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -942,6 +942,12 @@ export class CollectionFreeFormView extends CollectionSubView= 0.05 || localTransform.Scale > this.zoomScaling()) { const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20); @@ -966,9 +972,9 @@ export class CollectionFreeFormView extends CollectionSubView this._fullScreen = vref.webkitDisplayingFullscreen); this._disposers.reactionDisposer?.(); this._disposers.reactionDisposer = reaction(() => NumCast(this.layoutDoc._currentTimecode), - time => { - !this._playing && (vref.currentTime = time); - console.log("vref time = " + vref.currentTime) - }, { fireImmediately: true }); + time => !this._playing && (vref.currentTime = time), { fireImmediately: true }); } } -- cgit v1.2.3-70-g09d2 From a46052f0ec637a4bb777d1964413c20ec53dc08b Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 1 Jun 2022 14:04:25 -0400 Subject: small refactoring --- src/client/util/RecordingApi.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 87f44bec4..93906faac 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -119,7 +119,7 @@ export class RecordingApi { public resume = () => { this.isRecording = true - // set absoluteStart to the pausedTimestamp + // set absoluteStart to the difference in time this.absoluteStart = new Date().getTime() - this.absoluteStart } @@ -214,13 +214,9 @@ export class RecordingApi { if (presentation.movements === null || this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } - - if (this._isPlaying) { - //return new Error('[recordingApi.ts] playMovements() failed: already playing') - return - } + if(this._isPlaying) return; + this._isPlaying = true; - console.log('setWatching lol') Doc.UserDoc().presentationMode = 'watching'; // TODO: consider this bug at the end of the clip on seek -- cgit v1.2.3-70-g09d2 From 560d7090702c3559724420f3571b11d86c930177 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 1 Jun 2022 15:13:06 -0400 Subject: small playview fix --- src/client/util/RecordingApi.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 93906faac..76d03156a 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -89,6 +89,8 @@ export class RecordingApi { // update the presentation mode Doc.UserDoc().presentationMode = 'none' + // set the previus recording view to the play view + this.playFFView = this.recordingFFView const presCopy = { ...this.currentPresentation } -- cgit v1.2.3-70-g09d2 From 19705d01102fbb594ea5df18bb980bd2dfec93d4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 3 Jun 2022 08:44:03 -0400 Subject: fixed presentation as list index display --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/nodes/trails/PresBox.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c4e2afd08..2ed6c3cbe 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -930,7 +930,7 @@ export class CurrentUserUtils { sharedDocs.childContextMenuIcons = new List(["user-plus",]); } - doc.mySharedDocs = new PrefetchProxy(sharedDocs); + doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc); } } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index d85f0b640..f4e792acd 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -102,7 +102,7 @@ export class PresBox extends ViewBoxBaseComponent() { // TODO: [AL] add childDocs() and treeMap fields // @computed get childDocs() { return DocListCast(this.rootDoc.presentationaLinearizedDocuments); } //_treeViewMap:Map - @computed get childDocs() { return DocListCast(this.rootDoc.presentationLinearizedDocuments); } + @computed get childDocs() { return this.layoutDoc._viewType === CollectionViewType.Tree ? DocListCast(this.rootDoc.presentationLinearizedDocuments) : DocListCast(this.rootDoc[this.fieldKey]); } @observable _treeViewMap: Map = new Map(); @computed get tagDocs() { @@ -2533,7 +2533,7 @@ export class PresBox extends ViewBoxBaseComponent() { } // this func communicates with PresBoxElement to send information of the doc ScriptingGlobals.add(function lookupPresBoxField(presBoxDoc: Doc, field: string, presEleDoc: Doc) { - if (field === 'indexInPres') return DocListCast(presBoxDoc.presentationLinearizedDocuments).indexOf(presEleDoc); + if (field === 'indexInPres') return DocListCast(presBoxDoc._viewType === CollectionViewType.Tree ? presBoxDoc.presentationLinearizedDocuments : presBoxDoc[StrCast(presBoxDoc.presentationFieldKey)]).indexOf(presEleDoc); if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(presBoxDoc._viewType as any) ? 35 : 31; if (field === 'presStatus') return presBoxDoc.presStatus; if (field === '_itemIndex') return presBoxDoc._itemIndex; -- cgit v1.2.3-70-g09d2 From ba7824a91da113cc813c58b678719092c96d79a2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 3 Jun 2022 16:02:24 -0400 Subject: fixed opening closed tabs from Files sidebar to re-use best Alias before making a new alias. --- src/.DS_Store | Bin 8196 -> 8196 bytes src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/DocumentManager.ts | 28 ++++++++++----------- src/client/views/LightboxView.tsx | 5 ++-- src/client/views/collections/TabDocView.tsx | 15 ++++++++++- src/client/views/collections/TreeView.tsx | 4 +-- src/client/views/nodes/DocumentIcon.tsx | 4 +-- src/client/views/nodes/DocumentView.tsx | 4 --- src/client/views/nodes/button/FontIconBox.tsx | 11 ++++---- .../views/nodes/formattedText/DashFieldView.tsx | 6 ++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 14 ++--------- src/client/views/pdf/PDFViewer.tsx | 2 +- 13 files changed, 48 insertions(+), 49 deletions(-) (limited to 'src/client/util') diff --git a/src/.DS_Store b/src/.DS_Store index b0987293b..4bf9cdac7 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2ed6c3cbe..85ad58726 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -869,7 +869,7 @@ export class CurrentUserUtils { Doc.AddDocToList(menuBtnDoc, "data", newSubMenuBtnDoc, after, false, !prevSub); } prevSub = params.title; - }) + }); } prev = params.title; }); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index aeddc3249..a82f99d5a 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -16,7 +16,7 @@ import { SelectionManager } from './SelectionManager'; export class DocumentManager { //global holds all of the nodes (regardless of which collection they're in) - @observable public DocumentViews: DocumentView[] = []; + @observable public DocumentViews = new Set(); @observable public LinkAnchorBoxViews: DocumentView[] = []; @observable public RecordingEvent = 0; @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = []; @@ -46,7 +46,7 @@ export class DocumentManager { // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " + // view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString)); } else { - this.DocumentViews.push(view); + this.DocumentViews.add(view); } } public RemoveView = action((view: DocumentView) => { @@ -61,8 +61,7 @@ export class DocumentManager { const index = this.LinkAnchorBoxViews.indexOf(view); this.LinkAnchorBoxViews.splice(index, 1); } else { - const index = this.DocumentViews.indexOf(view); - index !== -1 && this.DocumentViews.splice(index, 1); + this.DocumentViews.delete(view); } SelectionManager.DeselectView(view); }); @@ -70,13 +69,13 @@ export class DocumentManager { //gets all views public getDocumentViewsById(id: string) { const toReturn: DocumentView[] = []; - DocumentManager.Instance.DocumentViews.map(view => { + Array.from(DocumentManager.Instance.DocumentViews).map(view => { if (view.rootDoc[Id] === id) { toReturn.push(view); } }); if (toReturn.length === 0) { - DocumentManager.Instance.DocumentViews.map(view => { + Array.from(DocumentManager.Instance.DocumentViews).map(view => { const doc = view.rootDoc.proto; if (doc && doc[Id] && doc[Id] === id) { toReturn.push(view); @@ -96,14 +95,14 @@ export class DocumentManager { const passes = preferredCollection ? [preferredCollection, undefined] : [undefined]; for (const pass of passes) { - DocumentManager.Instance.DocumentViews.map(view => { + Array.from(DocumentManager.Instance.DocumentViews).map(view => { if (view.rootDoc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { toReturn = view; return; } }); if (!toReturn) { - DocumentManager.Instance.DocumentViews.map(view => { + Array.from(DocumentManager.Instance.DocumentViews).map(view => { const doc = view.rootDoc.proto; if (doc && doc[Id] === id && (!pass || view.props.ContainingCollectionView === preferredCollection)) { toReturn = view; @@ -122,9 +121,8 @@ export class DocumentManager { } public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { - const docViews = DocumentManager.Instance.DocumentViews; const views: DocumentView[] = []; - docViews.map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view)); + Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view)); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); } public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { @@ -133,8 +131,8 @@ export class DocumentManager { } public getDocumentViews(toFind: Doc): DocumentView[] { const toReturn: DocumentView[] = []; - const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.IsLightboxDocView(view.docViewPath)); - const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.IsLightboxDocView(view.docViewPath)); + const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath)); + const lightViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => LightboxView.IsLightboxDocView(view.docViewPath)); // heuristic to return the "best" documents first: // choose a document in the lightbox first @@ -290,7 +288,7 @@ export class DocumentManager { } } } -export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) { +export function DocFocusOrOpen(doc: Doc, 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)) { @@ -300,7 +298,9 @@ export function DocFocusOrOpen(doc: any, collectionDoc?: Doc) { else { const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; - CollectionDockingView.AddSplit(showDoc === Doc.GetProto(showDoc) ? Doc.MakeAlias(showDoc) : showDoc, "right") && context && + const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc; + + CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), "right") && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc)); } } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index ddfc8b249..3d2769f66 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -275,9 +275,8 @@ export class LightboxView extends React.Component {
    { e.stopPropagation(); - // CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, ""); - LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace"); - console.log("lightbox to tab triggered") + CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, ""); + //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace"); SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); }}> diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 8d84e9a56..d64cb2fd7 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -66,6 +66,8 @@ export class TabDocView extends React.Component { get stack() { return (this.props as any).glContainer.parent.parent; } get tab() { return (this.props as any).glContainer.tab; } get view() { return this._view; } + _lastTab: any; + _lastView: DocumentView | undefined; @action init = (tab: any, doc: Opt) => { @@ -266,6 +268,7 @@ export class TabDocView extends React.Component { componentWillUnmount() { this._tabReaction?.(); + this._view && DocumentManager.Instance.RemoveView(this._view); this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab); this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged); @@ -353,7 +356,11 @@ export class TabDocView extends React.Component { @computed get docView() { return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) : - <> this._view = r)} + <> { + this._lastView && DocumentManager.Instance.RemoveView(this._lastView); + this._view = r; + this._lastView = this._view; + })} renderDepth={0} Document={this._document} DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} @@ -409,6 +416,12 @@ export class TabDocView extends React.Component { height: "100%", width: "100%" }} ref={ref => { if (this._mainCont = ref) { + if (this._lastTab) { + console.log("DUP tab") + this._view && DocumentManager.Instance.RemoveView(this._view); + CollectionDockingView.Instance.tabMap.delete(this._lastTab); + } + this._lastTab = this.tab; (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); DocServer.GetRefField(this.props.documentId).then(action(doc => doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document))); new _global.ResizeObserver(action((entries: any) => this._forceInvalidateScreenToLocal++)).observe(ref); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 661db7997..9de44fb00 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -183,7 +183,7 @@ export class TreeView extends React.Component { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias - const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); + const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail); this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), "lightbox"); } @@ -609,7 +609,7 @@ export class TreeView extends React.Component { // TODO: currently doc focus works, but can't seem to edit title // onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); onChildClick = () => { - return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.()) + return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.()); } onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index a9c998757..56de2d1fc 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -52,7 +52,7 @@ export class DocumentIconContainer extends React.Component { }; }, getVars() { - const docs = DocumentManager.Instance.DocumentViews; + const docs = Array.from(DocumentManager.Instance.DocumentViews); const capturedVariables: { [name: string]: Field } = {}; usedDocuments.forEach(index => capturedVariables[`d${index}`] = docs[index].props.Document); return { capturedVariables }; @@ -60,6 +60,6 @@ export class DocumentIconContainer extends React.Component { }; } render() { - return DocumentManager.Instance.DocumentViews.map((dv, i) => ); + return Array.from(DocumentManager.Instance.DocumentViews).map((dv, i) => ); } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 14ededaf8..6e4d6a52e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -242,7 +242,6 @@ export class DocumentViewInternal extends DocComponent() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const script = ScriptCast(this.rootDoc.script); - if (!script) { return null }; + if (!script) { return null; } let noviceList: string[] = []; let text: string | undefined; @@ -459,7 +459,7 @@ export class FontIconBox extends DocComponent() { const buttonText = StrCast(this.rootDoc.buttonText); // TODO:glr Add label of button type - let button = this.defaultButton; + let button: JSX.Element | null = this.defaultButton; switch (this.type) { case ButtonType.TextButton: @@ -527,9 +527,10 @@ export class FontIconBox extends DocComponent() { } return !this.layoutDoc.toolTip || this.type === ButtonType.DropdownList || this.type === ButtonType.ColorButton || this.type === ButtonType.NumberButton || this.type === ButtonType.EditableText ? button : - {StrCast(this.layoutDoc.toolTip)}
    }> - {button} - ; + button !== null ? + {StrCast(this.layoutDoc.toolTip)}
    }> + {button} + : null } } diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 6a3f9ed00..bb3791f1e 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -86,7 +86,7 @@ export class DashFieldViewInternal extends React.Component { const hideMenu = () => { this.fadeOut(true); document.removeEventListener("pointerdown", hideMenu); - } - document.addEventListener("pointerdown", hideMenu) + }; + document.addEventListener("pointerdown", hideMenu); } render() { const buttons = [ diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ce82821b6..434fccf8d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1202,7 +1202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { - selectAll(this._editorView.state, this._editorView?.dispatch) + selectAll(this._editorView.state, this._editorView?.dispatch); this.startUndoTypingBatch(); } else if (this._editorView) { this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index f4e792acd..0af7715fe 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -2435,9 +2435,7 @@ export class PresBox extends ViewBoxBaseComponent() { indexNum += (index[i] * (10 ** (-i))); } this._treeViewMap.set(indexNum, treeViewDoc); - console.log(String(index), treeViewDoc) this.props.Document.presentationLinearizedDocuments = new List(this.sort(this._treeViewMap)); // this is a flat array of Docs - console.log(this.props.Document.presentationLinearizedDocuments) return this.childDocs; } @@ -2447,21 +2445,13 @@ export class PresBox extends ViewBoxBaseComponent() { indexNum += (index[i] * (10 ** (-i))); } console.log(String(index), treeViewDoc) - this._treeViewMap.delete(indexNum) + this._treeViewMap.delete(indexNum); this.props.Document.presentationLinearizedDocuments = new List(this.sort(this._treeViewMap)); return this.childDocs; } // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1]) - sort = (treeViewMap: Map): Doc[] => { - // TODO - const sortedMap = [...treeViewMap.entries()].sort(); - var sortedDocs = []; - for (var kv of sortedMap) { - sortedDocs.push(kv[1]); - } - return sortedDocs; - } + sort = (treeViewMap: Map) => [...treeViewMap.entries()].sort().map(kv => kv[1]); render() { // calling this method for keyEvents diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6db36f36a..d5fd425ad 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -154,7 +154,7 @@ export class PDFViewer extends React.Component { if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.scaling?.() || 1); const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, .1 * windowHeight, NumCast(this.props.Document.scrollHeight)); - if (scrollTo !== undefined && scrollTo != this.props.layoutDoc._scrollTop) { + if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { focusSpeed = 500; if (!this._pdfViewer) this._initialScroll = scrollTo; -- cgit v1.2.3-70-g09d2 From b51b78c641c3e64f04cf878f02b5d7b1a620769e Mon Sep 17 00:00:00 2001 From: mehekj Date: Sun, 5 Jun 2022 11:38:46 -0400 Subject: cleanup --- src/Utils.ts | 6 ---- src/client/util/InteractionUtils.tsx | 3 +- src/client/views/GestureOverlay.tsx | 10 ++----- src/client/views/InkTranscription.tsx | 22 +++----------- src/client/views/InkingStroke.tsx | 1 - src/client/views/LightboxView.tsx | 4 +-- src/client/views/Touchable.tsx | 2 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +--- .../collections/collectionFreeForm/MarqueeView.tsx | 1 - src/client/views/nodes/DocumentLinksButton.tsx | 3 -- src/client/views/nodes/EquationBox.scss | 34 ---------------------- src/client/views/nodes/button/FontIconBox.tsx | 7 ++--- 12 files changed, 13 insertions(+), 85 deletions(-) (limited to 'src/client/util') diff --git a/src/Utils.ts b/src/Utils.ts index 8c0e8c7c0..523f4eb4f 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -5,12 +5,6 @@ import { Socket } from 'socket.io'; import { Colors } from './client/views/global/globalEnums'; import { Message } from './server/Message'; import Color = require('color'); -import { InkTool } from './fields/InkField'; -import { action } from 'mobx'; -import { CurrentUserUtils } from './client/util/CurrentUserUtils'; -import { CollectionFreeFormView } from './client/views/collections/collectionFreeForm'; -import { WidthSym, HeightSym } from './fields/Doc'; -import { NumCast } from './fields/Types'; export namespace Utils { export let DRAG_THRESHOLD = 4; diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index e2c5fab64..289c5bc51 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -286,7 +286,7 @@ export namespace InteractionUtils { // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2 case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0); case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON); - case TOUCHTYPE: + case TOUCHTYPE: return e.pointerType === TOUCHTYPE; default: return e.pointerType === type; } @@ -357,7 +357,6 @@ export namespace InteractionUtils { } export function IsDragging(oldTouches: Map, newTouches: React.Touch[], leniency: number): boolean { - console.log("getting here"); for (const touch of newTouches) { if (touch) { const oldTouch = oldTouches.get(touch.identifier); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 566b7b65a..21b5a8d01 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -18,13 +18,13 @@ import { SelectionManager } from "../util/SelectionManager"; import { Transform } from "../util/Transform"; import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu"; import "./GestureOverlay.scss"; -import { ActiveArrowEnd, ActiveArrowStart, ActiveArrowScale, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke"; +import { ActiveArrowEnd, ActiveArrowScale, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke"; +import { checkInksToGroup, createInkGroup } from "./nodes/button/FontIconBox"; import { DocumentView } from "./nodes/DocumentView"; import { RadialMenu } from "./nodes/RadialMenu"; import HorizontalPalette from "./Palette"; import { Touchable } from "./Touchable"; import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; -import { checkInksToGroup, createInkGroup } from "./nodes/button/FontIconBox"; @observer export class GestureOverlay extends Touchable { @@ -210,7 +210,7 @@ export class GestureOverlay extends Touchable { const nts: any = this.getNewTouches(e); this._holdTimer && clearTimeout(this._holdTimer); this._holdTimer = undefined; - + document.dispatchEvent( new CustomEvent>("dashOnTouchMove", { @@ -296,9 +296,6 @@ export class GestureOverlay extends Touchable { else if (thumb.clientX === leftMost) { pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v); } - else { - // console.log("not hand"); - } this.pointerIdentifier = pointer?.identifier; runInAction(() => { @@ -632,7 +629,6 @@ export class GestureOverlay extends Touchable { if (!actionPerformed) { const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); newPoints.pop(); - // console.log("getting to bezier math"); const controlPoints: { X: number, Y: number }[] = []; const bezierCurves = fitCurve(newPoints, 10); diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 5e0667bed..6babb3915 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -4,12 +4,12 @@ import * as React from 'react'; import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc'; import { InkData, InkField } from "../../fields/InkField"; import { Cast, DateCast, NumCast } from '../../fields/Types'; -import { DocumentType } from "../documents/DocumentTypes"; -import './InkTranscription.scss'; import { aggregateBounds } from '../../Utils'; -import { CollectionFreeFormView } from './collections/collectionFreeForm'; +import { DocumentType } from "../documents/DocumentTypes"; import { DocumentManager } from "../util/DocumentManager"; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { InkingStroke } from './InkingStroke'; +import './InkTranscription.scss'; export class InkTranscription extends React.Component { @@ -21,7 +21,6 @@ export class InkTranscription extends React.Component { @observable _textRef: any; private lastJiix: any; private currGroup?: Doc; - private containingLayout?: Doc; constructor(props: Readonly<{}>) { super(props); @@ -104,29 +103,22 @@ export class InkTranscription extends React.Component { return this._textRef = r; } - transcribeInk = (groupDoc: Doc | undefined, containingLayout: Doc, inkDocs: Doc[], math: boolean, ffView?: CollectionFreeFormView) => { + transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean, ffView?: CollectionFreeFormView) => { if (!groupDoc) return; const validInks = inkDocs.filter(s => s.type === DocumentType.INK); const strokes: InkData[] = []; const times: number[] = []; - // console.log(validInks); validInks.filter(i => Cast(i.data, InkField)).forEach(i => { const d = Cast(i.data, InkField, null); - // const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); - // const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); - // strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top }))); const inkStroke = DocumentManager.Instance.getDocumentView(i)?.ComponentView as InkingStroke; strokes.push(d.inkData.map(pd => (inkStroke.ptToScreen({ X: pd.X, Y: pd.Y })))); times.push(DateCast(i.creationDate).getDate().getTime()); }); this.currGroup = groupDoc; - this.containingLayout = containingLayout; const pointerData = { "events": strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) }; - // console.log(JSON.stringify(pointerData)); - // console.log(pointerData); const processGestures = false; if (math) { @@ -225,19 +217,15 @@ export class InkTranscription extends React.Component { newCollection.title = word; } // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - // console.log(newCollection); newCollection && docView.props.addDocument?.(newCollection); }); } exportInk = (e: any, ref: any) => { const exports = e.detail.exports; - // console.log(e); if (exports) { if (exports['application/x-latex']) { const latex = exports['application/x-latex']; - // console.log(latex); - if (this.currGroup) { this.currGroup.text = latex; this.currGroup.title = latex; @@ -282,10 +270,8 @@ export class InkTranscription extends React.Component { } } const text = exports['text/plain']; - // console.log(text); if (this.currGroup) { - // console.log("curr grouping"); this.currGroup.transcription = text; this.currGroup.title = text.split("\n")[0]; } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 6c95f0f77..40fe6aa68 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -253,7 +253,6 @@ export class InkingStroke extends ViewBoxBaseComponent() { (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); const screenHdlPts = screenPts; - console.log(screenPts); const startMarker = StrCast(this.layoutDoc.strokeStartMarker); const endMarker = StrCast(this.layoutDoc.strokeEndMarker); diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index c8f6b5f0b..40c68878d 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -5,7 +5,7 @@ import "normalize.css"; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, Utils } from '../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; @@ -16,8 +16,6 @@ import { TabDocView } from './collections/TabDocView'; import "./LightboxView.scss"; import { DocumentView } from './nodes/DocumentView'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; -import { CollectionMenu } from './collections/CollectionMenu'; -import { utils } from 'mocha'; interface LightboxViewProps { PanelWidth: number; diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index c712e7ed3..789756a78 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -127,13 +127,11 @@ export abstract class Touchable extends React.Component { } handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { - console.log("getting to handle1PointersMove"); e.stopPropagation(); e.preventDefault(); } handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent): any => { - console.log("getting to handle2PointersMove"); e.stopPropagation(); e.preventDefault(); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ca8073dac..f292bd96e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -17,7 +17,7 @@ import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; -import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; +import { Docs, DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -50,7 +50,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { InkTranscription } from "../../InkTranscription"; export const panZoomSchema = createSchema({ _panX: "number", @@ -700,7 +699,6 @@ export class CollectionFreeFormView extends CollectionSubView { - console.log("this is onPointerMove"); if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { Doc.UserDoc().activeInkTool = InkTool.None; @@ -823,7 +821,6 @@ export class CollectionFreeFormView extends CollectionSubView) => { - console.log("getting here yeet"); if (!e.cancelBubble) { const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); if (myTouches[0]) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f762c6619..b60742eba 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -267,7 +267,6 @@ export class MarqueeView extends React.Component() { "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; } } catch (e) { - // console.log(e); + console.log(e); } // Get items to place into the list @@ -767,7 +766,7 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { // TODO: nda - will probably need to go through and only remove the unprocessed selected docs ffView.unprocessedDocs = []; - InkTranscription.Instance.transcribeInk(newCollection, ffView.layoutDoc, selected, false, ffView); + InkTranscription.Instance.transcribeInk(newCollection, selected, false, ffView); }); } CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); -- cgit v1.2.3-70-g09d2 From b0408fa264fc8a017c87a17325600d8e458fe711 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 6 Jun 2022 01:32:36 -0400 Subject: fixed up overlayDocs a bit so that they can be removed. made audioBox's appear in overlay when clicked from toolbar. add links to audio when navigating to doc in lightbox. --- src/client/util/CurrentUserUtils.ts | 4 ++-- src/client/views/LightboxView.tsx | 2 ++ src/client/views/OverlayView.tsx | 10 +++++++--- src/client/views/collections/CollectionDockingView.tsx | 2 ++ src/client/views/nodes/AudioBox.tsx | 9 ++++++++- 5 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index cad86c0a6..51bfdbbd2 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -276,7 +276,7 @@ export class CurrentUserUtils { (doc.emptyWall as Doc).videoWall = true; } if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { ...standardOps(), title: "audio recording", _width: 200, _height: 100, }); + doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { ...standardOps(), title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, }); } if (doc.emptyNote === undefined) { doc.emptyNote = Docs.Create.TextDocument("", { ...standardOps(), title: "text note", _width: 200, _autoHeight: true }); @@ -301,7 +301,7 @@ export class CurrentUserUtils { { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc }, { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc }, - { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, + { toolTip: "Tap to create an audio recorder in a overlay pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openInOverlay(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc }, // { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc }, diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 2543111b0..f67f37bfb 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -126,6 +126,8 @@ export class LightboxView extends React.Component { const target = LightboxView._docTarget = this._future?.pop(); const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (targetDocView && target) { + const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement(); + l && (Cast(l.anchor2, Doc, null).backgroundColor = "lightgreen"); targetDocView.focus(target, { originalTarget: target, willZoom: true, scale: 0.9 }); if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target }); } else { diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 18b87e3d5..95e146df4 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -146,6 +146,10 @@ export class OverlayView extends React.Component { return remove; } + removeOverlayDoc = (doc: Doc | Doc[]) => { + (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc)); + return true; + } @computed get overlayDocs() { return CurrentUserUtils.OverlayDocs?.map(d => { @@ -179,16 +183,16 @@ export class OverlayView extends React.Component { offsetx = NumCast(d.x) - e.clientX; offsety = NumCast(d.y) - e.clientY; }; - return
    + return
    new Transform(-NumCast(d.x), -NumCast(d.y), 1)} renderDepth={1} isDocumentActive={returnFalse} isContentActive={returnTrue} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 5c0d6b1c4..de2106e4a 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -475,4 +475,6 @@ ScriptingGlobals.add(function openInLightbox(doc: any) { LightboxView.AddDocTab( "opens up document in a lightbox", "(doc: any)"); ScriptingGlobals.add(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); }, "opens up document in tab on right side of the screen", "(doc: any)"); +ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); }, + "opens up document in screen overlay layer", "(doc: any)"); ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); }); \ No newline at end of file diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 9b92762a2..787ad939d 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -350,7 +350,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent Date: Wed, 8 Jun 2022 01:17:24 -0400 Subject: added ability to make thumbnails of dashboards. started to cleanup dockingView/goldenlayout undo event handling. cleaned up tab doc list in docking view. made titles editable again in treeview. fixed overlay view to work with image docs etc by setting top/left in css --- src/client/documents/Documents.ts | 6 +-- src/client/goldenLayout.js | 2 +- src/client/util/CurrentUserUtils.ts | 26 ++++++++--- src/client/views/DocumentButtonBar.tsx | 53 ++++++++++------------ src/client/views/MainView.tsx | 12 +---- src/client/views/OverlayView.scss | 2 + src/client/views/OverlayView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 21 ++++++--- src/client/views/collections/TabDocView.tsx | 28 ++++-------- src/client/views/collections/TreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 45 ++++++++++-------- src/client/views/nodes/WebBoxRenderer.js | 4 +- src/client/views/topbar/TopBar.tsx | 6 +-- 13 files changed, 108 insertions(+), 103 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 817478b2f..acc1e341a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -671,9 +671,9 @@ export namespace Docs { return viewDoc; } - export function ImageDocument(url: string, options: DocumentOptions = {}) { - const imgField = new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options }); + export function ImageDocument(url: string|ImageField, options: DocumentOptions = {}) { + const imgField = url instanceof ImageField ? url : new ImageField(url); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }); } export function PresDocument(options: DocumentOptions = {}) { diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 82b10608d..4735f216f 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -1431,7 +1431,7 @@ this.root.callDownwards('_$init'); if (config.maximisedItemId === '__glMaximised') { - this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise(); + this.root.getItemsById(config.maximisedItemId)[0]?.toggleMaximise(); } }, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 51bfdbbd2..885679b64 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -8,7 +8,7 @@ import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; +import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; @@ -22,11 +22,10 @@ import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; -import { LabelBox } from "../views/nodes/LabelBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; -import { DragManager, dropActionType } from "./DragManager"; +import { DragManager } from "./DragManager"; import { makeTemplate, MakeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; @@ -1185,9 +1184,24 @@ export class CurrentUserUtils { } public static async snapshotDashboard(userDoc: Doc) { - const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); - CurrentUserUtils.openDashboard(userDoc, copy); + const docView = CollectionDockingView.Instance.props.DocumentView?.(); + const content = docView?.ContentDiv; + if (docView && content) { + const _width = Number(getComputedStyle(content).width.replace("px","")); + const _height = Number(getComputedStyle(content).height.replace("px","")); + CollectionFreeFormView.UpdateIcon( + docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + content, + _width, _height, + _width, _height, 0, + (iconFile, _nativeWidth, _nativeHeight) => { + const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); + const proto = Cast(img.proto, Doc, null)!; + proto["data-nativeWidth"] = _width; + proto["data-nativeHeight"] = _height; + docView.dataDoc.thumb = img; + }); + } } public static createNewDashboard = async (userDoc: Doc, id?: string) => { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c0645f0ab..103734b9b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -213,34 +213,31 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @undoBatch @action pinWithView = (targetDoc: Doc) => { - if (targetDoc) { - TabDocView.PinDoc(targetDoc); - setTimeout(() => { - const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; - const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); - if (scrollable) { - const scroll = targetDoc._scrollTop; - activeDoc.presPinView = true; - activeDoc.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeDoc.presPinView = true; - activeDoc.presStartTime = targetDoc._currentTimecode; - activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if (pannable) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeDoc.presPinView = true; - activeDoc.presPinViewX = x; - activeDoc.presPinViewY = y; - activeDoc.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeDoc.presPinClipWidth = width; - activeDoc.presPinView = true; - } - }); + const activeDoc = targetDoc && TabDocView.PinDoc(targetDoc); + if (activeDoc) { + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; + const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); + if (scrollable) { + const scroll = targetDoc._scrollTop; + activeDoc.presPinView = true; + activeDoc.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { + activeDoc.presPinView = true; + activeDoc.presStartTime = targetDoc._currentTimecode; + activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; + } else if (pannable) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeDoc.presPinClipWidth = width; + activeDoc.presPinView = true; + } } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index cdf85c905..a30db66e4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -226,17 +226,7 @@ export class MainView extends React.Component { if (received && !this.userDoc) { reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true }); } else { - if (received && CurrentUserUtils._urlState.sharing) { - reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized, - initialized => initialized && received && DocServer.GetRefField(received).then(docField => { - if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) { - CollectionDockingView.AddSplit(docField, "right"); - } - }), - ); - } - const activeDash = PromiseValue(this.userDoc.activeDashboard); - activeDash.then(dash => { + PromiseValue(this.userDoc.activeDashboard).then(dash => { if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash); else CurrentUserUtils.createNewDashboard(this.userDoc); }); diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 555f4298d..f22e68ff5 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -46,4 +46,6 @@ .overlayView-doc { z-index: 9002; //so that it appears above chroma position: absolute; + top: 0; + left: 0; } \ No newline at end of file diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index c3c103b50..f5e686473 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -200,7 +200,7 @@ export class OverlayView extends React.Component { ScreenToLocalTransform={this.docScreenToLocalXf(d)} renderDepth={1} isDocumentActive={returnTrue} - isContentActive={retu} + isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index de2106e4a..5af72b7d1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -26,6 +26,7 @@ import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require("react"); +import { SelectionManager } from '../../util/SelectionManager'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -50,9 +51,8 @@ export class CollectionDockingView extends CollectionSubView() { public _flush: UndoManager.Batch | undefined; private _ignoreStateChange = ""; public tabMap: Set = new Set(); - public get initialized() { return this._goldenLayout !== null; } public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; } - @observable private _goldenLayout: any = null; + private _goldenLayout: any = null; constructor(props: SubCollectionViewProps) { super(props); @@ -118,6 +118,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch public static OpenFullScreen(doc: Doc) { + SelectionManager.DeselectAll(); const instance = CollectionDockingView.Instance; if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") { return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); @@ -271,7 +272,7 @@ export class CollectionDockingView extends CollectionSubView() { return true; } - async setupGoldenLayout() { + setupGoldenLayout = async () => { const config = StrCast(this.props.Document.dockingConfig); if (config) { const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); @@ -291,7 +292,7 @@ export class CollectionDockingView extends CollectionSubView() { } this.tabMap.clear(); this._goldenLayout?.destroy(); - runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config))); + this._goldenLayout = new GoldenLayout(JSON.parse(config)); this._goldenLayout.on('tabCreated', this.tabCreated); this._goldenLayout.on('tabDestroyed', this.tabDestroyed); this._goldenLayout.on('stackCreated', this.stackCreated); @@ -322,7 +323,7 @@ export class CollectionDockingView extends CollectionSubView() { } this._ignoreStateChange = ""; }); - setTimeout(() => this.setupGoldenLayout(), 0); + setTimeout(this.setupGoldenLayout); //window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window } } @@ -413,19 +414,25 @@ export class CollectionDockingView extends CollectionSubView() { const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc); const changesMade = this.props.Document.dockcingConfig !== json; if (changesMade && !this._flush) { - this.props.Document.dockingConfig = json; - this.props.Document.data = new List(docs); + UndoManager.RunInBatch(() => { + this.props.Document.dockingConfig = json; + this.props.Document.data = new List(docs); + }, "state changed"); } return changesMade; } tabDestroyed = (tab: any) => { + const dview = CollectionDockingView.Instance.props.Document; + const fieldKey = CollectionDockingView.Instance.props.fieldKey; + Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); 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) => { + this.tabMap.add(tab); 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 0141724ea..75a8207dd 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -82,7 +82,6 @@ export class TabDocView extends React.Component { const iconWrap = document.createElement("div"); const closeWrap = document.createElement("div"); - titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; titleEle.onkeydown = (e: KeyboardEvent) => { @@ -185,14 +184,13 @@ export class TabDocView extends React.Component { UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); }); } - CollectionDockingView.Instance.tabMap.add(tab); } /** * Adds a document to the presentation view **/ @action - public static async PinDoc(doc: Doc, pinProps?: PinProps) { + public static PinDoc(doc: Doc, pinProps?: PinProps) { //add this new doc to props.Document // all docs will be added to the ActivePresentation as stored on CurrentUserUtils @@ -246,19 +244,14 @@ export class TabDocView extends React.Component { pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - const dview = CollectionDockingView.Instance.props.Document; - const fieldKey = CollectionDockingView.Instance.props.fieldKey; - const tabdocs = await DocListCastAsync(dview[fieldKey]); - runInAction(() => { - if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) { - tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs - CollectionDockingView.AddSplit(curPres, "right"); - } - PresBox.Instance?._selectedArray.clear(); - pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - DocumentManager.Instance.jumpToDocument(doc, false, undefined, []); - batch.end(); - }); + if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { + CollectionDockingView.AddSplit(curPres, "right"); + setTimeout(() => DocumentManager.Instance.jumpToDocument(doc, false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things + } + PresBox.Instance?._selectedArray.clear(); + pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array + setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs + return pinDoc; } } @@ -282,7 +275,6 @@ export class TabDocView extends React.Component { componentWillUnmount() { this._tabReaction?.(); this._view && DocumentManager.Instance.RemoveView(this._view); - this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab); this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged); } @@ -430,9 +422,7 @@ export class TabDocView extends React.Component { }} ref={ref => { if (this._mainCont = ref) { if (this._lastTab) { - console.log("DUP tab") this._view && DocumentManager.Instance.RemoveView(this._view); - CollectionDockingView.Instance.tabMap.delete(this._lastTab); } this._lastTab = this.tab; (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index befd2020c..09f05f69a 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -609,10 +609,8 @@ export class TreeView extends React.Component { return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); } - // TODO: currently doc focus works, but can't seem to edit title - // onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); onChildClick = () => { - return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.()); + return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 77cf0fc84..5f927a2a9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1489,7 +1489,7 @@ export class CollectionFreeFormView extends CollectionSubView { + static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { if (oldDiv.childNodes && newDiv) { for (let i = 0; i < oldDiv.childNodes.length; i++) { this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); @@ -1508,32 +1508,39 @@ export class CollectionFreeFormView extends CollectionSubView { - const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + updateIcon = () => CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + this.props.docViewPath().lastElement().ContentDiv!, + this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), + this.props.PanelWidth(), this.props.PanelHeight(), 0, + (iconFile, nativeWidth, nativeHeight) => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }); + + public static UpdateIcon(filename:string, docViewContent:HTMLElement, width: number, height: number, + panelWidth:number, panelHeight: number, scrollTop:number, cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) { const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); - newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + newDiv.style.width = width.toString(); + newDiv.style.height = height.toString(); this.replaceCanvases(docViewContent, newDiv); - const htmlString = this._mainCont && new XMLSerializer().serializeToString(newDiv); - const nativeWidth = this.layoutDoc[WidthSym](); - const nativeHeight = this.layoutDoc[HeightSym](); - + const htmlString = new XMLSerializer().serializeToString(newDiv); + const nativeWidth = width; + const nativeHeight = height; CreateImage( - "", + Utils.prepend(""), document.styleSheets, htmlString, nativeWidth, - nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), - NumCast(this.layoutDoc._scrollTop) + nativeWidth * panelHeight / panelWidth, + scrollTop ).then ((data_url: any) => { - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then( - returnedfilename => setTimeout(action(() => { - - this.dataDoc.icon = new ImageField(returnedfilename); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }), 500)); + VideoBox.convertDataUri(data_url, filename).then( + returnedfilename => setTimeout(() => { + cb(returnedfilename as string, nativeWidth, nativeHeight); + }, 500)); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index d9524dd6e..f3f1bcf5c 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -214,8 +214,8 @@ var ForeignHtmlRenderer = function (styleSheets) { .replace(/noscript/g, "div").replace(/
    <\/div>/g, "") // when scripting isn't available (ie, rendering web pages here),
    } placement="bottom"> @@ -57,12 +57,12 @@ export class TopBar extends React.Component { await CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); batch.end(); }}> - {"Snapshot"} + Snapshot
    Browsing mode for directly navigating to documents
    } placement="bottom">
    MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> - {"Explore"} + Explore
    -- cgit v1.2.3-70-g09d2 From b316f7676c5b2acd9e5bd1202f1cdf33a81a6f6a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Jun 2022 13:46:23 -0400 Subject: tweaked doc decorations to be visible when clicking on items in headerBar. restored dashboard snapshotting. --- src/client/util/CurrentUserUtils.ts | 4 ++++ src/client/views/MainView.tsx | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 885679b64..a325122bc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1202,6 +1202,10 @@ export class CurrentUserUtils { docView.dataDoc.thumb = img; }); } + + const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); + Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); + CurrentUserUtils.openDashboard(userDoc, copy); } public static createNewDashboard = async (userDoc: Doc, id?: string) => { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a30db66e4..641178cd9 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -83,7 +83,7 @@ export class MainView extends React.Component { @computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @computed private get topOfDashUI() { return Number(DASHBOARD_SELECTOR_HEIGHT.replace("px", "")); } - @computed private get topOfHeaderBarDoc() { return this.topOfDashUI + this.topMenuHeight(); } + @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; } @computed private get topOfSidebarDoc() { return this.topOfDashUI + this.topMenuHeight(); } @computed private get topOfMainDoc() { return this.topOfDashUI + this.topMenuHeight() + this.headerBarDocHeight(); } @computed private get topOfMainDocContent() { return this.topOfMainDoc + this.dashboardTabHeight; } @@ -658,7 +658,7 @@ export class MainView extends React.Component { {LinkDescriptionPopup.descriptionPopup ? : null} {DocumentLinksButton.LinkEditorDocView ? DocumentLinksButton.LinkEditorDocView = undefined)} docView={DocumentLinksButton.LinkEditorDocView} /> : (null)} {LinkDocPreview.LinkInfo ? : (null)} -
    +
    -- cgit v1.2.3-70-g09d2 From 7c24d7bb4a24c5224b89585c940db070b82e89fc Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Jun 2022 20:03:04 -0400 Subject: fixes from merge --- src/client/Network.ts | 1 - src/client/util/DragManager.ts | 4 ---- src/client/views/GestureOverlay.tsx | 4 +++- src/client/views/nodes/AudioBox.tsx | 2 -- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- 5 files changed, 4 insertions(+), 9 deletions(-) (limited to 'src/client/util') diff --git a/src/client/Network.ts b/src/client/Network.ts index 1255e5ce0..3597e7b2b 100644 --- a/src/client/Network.ts +++ b/src/client/Network.ts @@ -19,7 +19,6 @@ export namespace Networking { } export async function UploadFilesToServer(files: File | File[]): Promise[]> { - console.log(files) const formData = new FormData(); if (Array.isArray(files)) { if (!files.length) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index f438a8c11..9f8c49081 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -210,10 +210,6 @@ export namespace DragManager { options?: DragOptions, dropEvent?: () => any ) { - // stop an 'accidental' on-click drag that may have occured if the user is in presenting mode - // note: dragData.dropAction is only undefined when the element itself being dragged without being selected - if (Doc.UserDoc()?.presentationMode === 'recording' && dragData.dropAction === undefined) return false; - const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField); dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 30982bfae..317f5f5d7 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -645,7 +645,9 @@ export class GestureOverlay extends Touchable { (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; this._points = controlPoints; - this.dispatchGesture(GestureUtils.Gestures.Stroke); + this.dispatchGesture(GestureUtils.Gestures.Stroke); + // TODO: nda - check inks to group here + checkInksToGroup(); } this._points = []; } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 70d730ad1..7ff3196da 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -4,8 +4,6 @@ import { action, computed, IReactionDisposer, observable, runInAction } from "mo import { observer } from "mobx-react"; import { DateField } from "../../../fields/DateField"; import { Doc, DocListCast } from "../../../fields/Doc"; -import { documentSchema } from "../../../fields/documentSchemas"; -import { makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; import { Cast, DateCast, NumCast } from "../../../fields/Types"; import { AudioField, nullAudio } from "../../../fields/URLField"; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 39c9478bf..8e53abdba 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -197,7 +197,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument; const dragItem: HTMLElement[] = []; if (dragArray.length === 1) { - const doc = dragArray[0]; + const doc = this._itemRef.current || dragArray[0]; doc.className = miniView ? "presItem-miniSlide" : "presItem-slide"; dragItem.push(doc); } else if (dragArray.length >= 1) { -- cgit v1.2.3-70-g09d2 From 6b9b7fefe5fa485b58512fdab10934c26092ab15 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 00:27:52 -0400 Subject: restored multi-line scripting by not adding 'return' to scripts when it's not requested. --- src/client/util/Scripting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 3b0a47b54..223b0268c 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -223,7 +223,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`); } const paramString = paramList.join(", "); - const body = addReturn ? `return ${script};` : `return ${script};`; + const body = addReturn ? `return ${script};` : script; const reqTypes = requiredType ? `: ${requiredType}` : ''; const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile("file.ts", funcScript); -- cgit v1.2.3-70-g09d2 From 140e2f643f97057ba9c89c502cff7843bd738cd6 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 16:59:09 -0400 Subject: fixed issues with taking dashboard snapshots to update dashboard view --- src/client/util/CurrentUserUtils.ts | 9 ++- src/client/views/DashboardView.tsx | 12 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 ++-- src/client/views/topbar/TopBar.tsx | 64 +--------------------- 4 files changed, 19 insertions(+), 76 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b1329e349..2d2dda2b8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1183,13 +1183,13 @@ export class CurrentUserUtils { input.click(); } - public static async snapshotDashboard(userDoc: Doc) { + public static CaptureDashboardThumbnail() { const docView = CollectionDockingView.Instance.props.DocumentView?.(); const content = docView?.ContentDiv; if (docView && content) { const _width = Number(getComputedStyle(content).width.replace("px","")); const _height = Number(getComputedStyle(content).height.replace("px","")); - CollectionFreeFormView.UpdateIcon( + return CollectionFreeFormView.UpdateIcon( docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), content, _width, _height, @@ -1199,10 +1199,13 @@ export class CurrentUserUtils { const proto = Cast(img.proto, Doc, null)!; proto["data-nativeWidth"] = _width; proto["data-nativeHeight"] = _height; - docView.dataDoc.thumb = img; + Doc.GetProto(CurrentUserUtils.ActiveDashboard).thumb = img; }); } + } + + public static async snapshotDashboard(userDoc: Doc) { const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); CurrentUserUtils.openDashboard(userDoc, copy); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 5fd9b550d..3cfece970 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,4 +1,5 @@ import { observable } from "mobx"; +import { extname } from 'path'; import { observer } from "mobx-react"; import * as React from 'react'; import { Doc, DocListCast } from "../../fields/Doc"; @@ -33,13 +34,16 @@ export class DashboardView extends React.Component {
    All Dashboards
    - {myDashboards.map((dashboard) => -
    { this.clickDashboard(e, dashboard) }}> - + {myDashboards.map((dashboard) => { + const url = ImageCast((dashboard.thumb as Doc)?.data)?.url; + const ext = url ? extname(url.href):""; + const href = url?.href.replace(ext, "_m"+ ext); // need to choose which resolution image to show. options are _s, _m, _l, _o (small/medium/large/original) + return
    this.clickDashboard(e, dashboard)}> +
    {StrCast(dashboard.title)}
    - )} + })} {myDashboards.map((dashboard) => { console.log(dashboard.thumb) })} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f4d2d55d5..ef3a896e1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1532,7 +1532,7 @@ export class CollectionFreeFormView extends CollectionSubView { - VideoBox.convertDataUri(data_url, filename).then( - returnedfilename => setTimeout(() => { - cb(returnedfilename as string, nativeWidth, nativeHeight); - }, 500)); + (async (data_url: any) => { + const returnedFilename = await VideoBox.convertDataUri(data_url, filename); + cb(returnedFilename as string, nativeWidth, nativeHeight); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index fcc72b73d..21ef807ff 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -21,7 +21,7 @@ import "./TopBar.scss"; @observer export class TopBar extends React.Component { navigateToHome = () => { - Doc.UserDoc().activeDashboard = undefined + CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => Doc.UserDoc().activeDashboard = undefined); } render() { const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); @@ -61,68 +61,6 @@ export class TopBar extends React.Component {
    - - - {/*
    -
    - {`${Doc.CurrentUserEmail}`} -
    -
    window.location.assign(Utils.prepend("/logout"))}> - {"Log out"} -
    -
    */} - {/*
    -
    -
    - {activeDashboard ? StrCast(activeDashboard.title) : "Dash"} -
    */} - - {/* */} - {/*
    */ } - {/*
    - Create a new dashboard
    } placement="bottom">
    { - const batch = UndoManager.StartBatch("new dash"); - await CurrentUserUtils.createNewDashboard(Doc.UserDoc()); - batch.end(); - }}> - New -
    - - Work on a copy of the dashboard layout
    } placement="bottom"> -
    { - const batch = UndoManager.StartBatch("snapshot"); - await CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); - batch.end(); - }}> - Snapshot -
    - - Browsing mode for directly navigating to documents
    } placement="bottom"> -
    MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> - Explore -
    - -
    */} - {/*
    */ } - {/*
    */ } - {/*
    window.open( - "https://brown-dash.github.io/Dash-Documentation/", "_blank")}> - -
    -
    SettingsManager.Instance.open()}> - -
    */} - {/*
    - -
    -
    SettingsManager.Instance.open()}> - -
    -
    */} ); } -- cgit v1.2.3-70-g09d2 From d1f268c1788f54c62c0779aba224b5b7d30edb01 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 17:13:49 -0400 Subject: changed tab numbering to be relative to dashboard. --- src/client/util/CurrentUserUtils.ts | 5 ++--- src/client/views/collections/CollectionDockingView.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2d2dda2b8..3107650d9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1215,8 +1215,6 @@ export class CurrentUserUtils { const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; const dashboardCount = DocListCast(dashboards.data).length + 1; - const emptyPane = Cast(userDoc.emptyPane, Doc, null); - emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; const freeformOptions: DocumentOptions = { x: 0, y: 400, @@ -1224,7 +1222,7 @@ export class CurrentUserUtils { _height: 1000, _fitWidth: true, _backgroundGridShow: true, - title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`, + title: `Untitled Tab 1`, }; const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); @@ -1233,6 +1231,7 @@ export class CurrentUserUtils { // switching the tabs from the datadoc to the regular doc const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); dashboardDoc.data = new List(dashboardTabs); + dashboardDoc["pane-count"] = 1; userDoc.activePresentation = presentation; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a790a0475..87149a4b8 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -428,10 +428,10 @@ export class CollectionDockingView extends CollectionSubView() { stackCreated = (stack: any) => { stack.header?.element.on('mousedown', (e: any) => { if (e.target === stack.header?.element[0] && e.button === 2) { - const emptyPane = CurrentUserUtils.EmptyPane; - emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; + const dashboard= CurrentUserUtils.ActiveDashboard; + dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`, + _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}`, }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); CollectionDockingView.AddSplit(docToAdd, "", stack); @@ -455,10 +455,10 @@ export class CollectionDockingView extends CollectionSubView() { .off('click') //unbind the current click handler .click(action(() => { // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size - const emptyPane = CurrentUserUtils.EmptyPane; - emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; + const dashboard = CurrentUserUtils.ActiveDashboard; + dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}` + _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}` }); this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); CollectionDockingView.AddSplit(docToAdd, "", stack); -- cgit v1.2.3-70-g09d2 From 43968431d3c94cea07f06421d29ac0f190c55cde Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 17:25:55 -0400 Subject: added activePage field for choosing dash page layout. --- src/client/util/CurrentUserUtils.ts | 1 + src/client/views/DashboardView.tsx | 3 ++- src/client/views/MainView.tsx | 21 +++++++++++---------- src/client/views/topbar/TopBar.tsx | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3107650d9..3b1009532 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1093,6 +1093,7 @@ export class CurrentUserUtils { Docs.newAccount = !(field instanceof Doc); await Docs.Prototypes.initialize(); const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; + Docs.newAccount &&(userDoc.activePage = "home"); const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); (await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager const a1 = await Cast(link?.anchor1, Doc, null); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 3cfece970..b08151c0f 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -22,7 +22,8 @@ export class DashboardView extends React.Component { clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => { if (e.detail === 2) { - Doc.UserDoc().activeDashboard = dashboard + Doc.UserDoc().activeDashboard = dashboard; + Doc.UserDoc().activePage = "dashboard"; } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ad041384c..5fd76c388 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -662,16 +662,17 @@ export class MainView extends React.Component { {DocumentLinksButton.LinkEditorDocView ? DocumentLinksButton.LinkEditorDocView = undefined)} docView={DocumentLinksButton.LinkEditorDocView} /> : (null)} {LinkDocPreview.LinkInfo ? : (null)} - {Doc.UserDoc().activeDashboard ? - <> -
    - -
    - - {this.mainDashboardArea} - - : - + {((page:string) => { + switch (page) { + case "dashboard": + default:return <> +
    + +
    + {this.mainDashboardArea} + ; + case "home": return ; + } })(StrCast(Doc.UserDoc().activePage)) } diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 21ef807ff..258891099 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -21,7 +21,7 @@ import "./TopBar.scss"; @observer export class TopBar extends React.Component { navigateToHome = () => { - CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => Doc.UserDoc().activeDashboard = undefined); + CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => Doc.UserDoc().activePage = "home"); } render() { const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); -- cgit v1.2.3-70-g09d2 From ab5e48a2340f06628fc22d1267d081de9dbc572f Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 23:24:34 -0400 Subject: fixed up creating dashboard icons to work with pdfs in freeformviews with a grid. no idea why having a grid breaks thrings. --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/DashboardView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 53 ++++++++++++++-------- src/client/views/nodes/PDFBox.tsx | 44 +++++++----------- 4 files changed, 53 insertions(+), 50 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3b1009532..9a01f3e5d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1194,7 +1194,7 @@ export class CurrentUserUtils { docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), content, _width, _height, - _width, _height, 0, + _width, _height, 0, 1, true, docView.layoutDoc[Id] + "-icon", (iconFile, _nativeWidth, _nativeHeight) => { const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); const proto = Cast(img.proto, Doc, null)!; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index b08151c0f..e003968d6 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -36,9 +36,7 @@ export class DashboardView extends React.Component {
    {myDashboards.map((dashboard) => { - const url = ImageCast((dashboard.thumb as Doc)?.data)?.url; - const ext = url ? extname(url.href):""; - const href = url?.href.replace(ext, "_m"+ ext); // need to choose which resolution image to show. options are _s, _m, _l, _o (small/medium/large/original) + const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; return
    this.clickDashboard(e, dashboard)}>
    {StrCast(dashboard.title)}
    diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ef3a896e1..99f74b8a2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1500,15 +1500,22 @@ export class CollectionFreeFormView extends CollectionSubView { - this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; }); - public static UpdateIcon(filename:string, docViewContent:HTMLElement, width: number, height: number, - panelWidth:number, panelHeight: number, scrollTop:number, cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) { + public static UpdateIcon( + filename:string, docViewContent:HTMLElement, + width: number, height: number, + panelWidth:number, panelHeight: number, + scrollTop:number, + realNativeHeight: number, + noSuffix: boolean, + replaceRootFilename: string| undefined, + cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) + { const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; newDiv.style.width = width.toString(); newDiv.style.height = height.toString(); @@ -1538,10 +1553,10 @@ export class CollectionFreeFormView extends CollectionSubView { - const returnedFilename = await VideoBox.convertDataUri(data_url, filename); + const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) .catch(function (error: any) { @@ -1771,7 +1786,7 @@ export class CollectionFreeFormView extends CollectionSubView
    {this.layoutDoc._backgroundGridShow ? - : (null)} + />
    : (null)} () { @@ -147,34 +148,23 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - return; // currently we render pdf icons as text labels + // currently we render pdf icons as text labels const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; - const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); - newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); - this.replaceCanvases(docViewContent, newDiv); - const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); - const nativeWidth = this.layoutDoc[WidthSym](); - const nativeHeight = this.layoutDoc[HeightSym](); - - CreateImage( - "", - document.styleSheets, - htmlString, - nativeWidth, - nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), - NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"]) - ).then - ((data_url: any) => { - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then( - returnedfilename => setTimeout(action(() => { - this.dataDoc.icon = new ImageField(returnedfilename); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }), 500)); - }) - .catch(function (error: any) { - console.error('oops, something went wrong!', error); + const filename = this.layoutDoc[Id] + "-icon" + (new Date()).getTime(); + this._pdfViewer?._mainCont.current && CollectionFreeFormView.UpdateIcon( + filename, docViewContent, + this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), + this.props.PanelWidth(), this.props.PanelHeight(), + NumCast(this.layoutDoc._scrollTop), + NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"], 1), + true, + this.layoutDoc[Id] + "-icon", + (iconFile:string, nativeWidth:number, nativeHeight:number) => { + setTimeout(() => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }, 500); }); } -- cgit v1.2.3-70-g09d2 From f550bca52735d9bd2195ff453e43beca18bef309 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 23:54:11 -0400 Subject: opening a shared dashboard now adds it to myDashboards --- src/client/util/CurrentUserUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9a01f3e5d..c801a6f2f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -931,7 +931,6 @@ export class CurrentUserUtils { sharedDocs.childContextMenuScripts = new List([addToDashboards!,]); sharedDocs.childContextMenuLabels = new List(["Add to Dashboards",]); sharedDocs.childContextMenuIcons = new List(["user-plus",]); - } doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc); } @@ -1111,6 +1110,9 @@ export class CurrentUserUtils { public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => { CurrentUserUtils.MainDocId = doc[Id]; + if (!DocListCast(CurrentUserUtils.MyDashboards.data).includes(doc)) { + Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); + } if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); -- cgit v1.2.3-70-g09d2 From c159cec257544ff10c256181d1aedfccf3ae7843 Mon Sep 17 00:00:00 2001 From: Jenny Yu Date: Thu, 9 Jun 2022 21:05:20 -0700 Subject: fix: close active dashboard --- src/client/util/CurrentUserUtils.ts | 6 +++++- src/client/views/topbar/TopBar.tsx | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3b1009532..d32f1bf70 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1212,6 +1212,10 @@ export class CurrentUserUtils { CurrentUserUtils.openDashboard(userDoc, copy); } + public static closeActiveDashboard = () => { + Doc.UserDoc().activeDashboard = undefined; + } + public static createNewDashboard = async (userDoc: Doc, id?: string) => { const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; @@ -1237,7 +1241,7 @@ export class CurrentUserUtils { userDoc.activePresentation = presentation; Doc.AddDocToList(dashboards, "data", dashboardDoc); - CurrentUserUtils.openDashboard(userDoc, dashboardDoc); + // CurrentUserUtils.openDashboard(userDoc, dashboardDoc); } public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 79d48e13c..57ceac2dd 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -22,6 +22,7 @@ import "./TopBar.scss"; @observer export class TopBar extends React.Component { navigateToHome = () => { + CurrentUserUtils.closeActiveDashboard(); CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => Doc.UserDoc().activePage = "home"); } render() { -- cgit v1.2.3-70-g09d2 From bc662b4e7eaaa1f33f41f64ce64d60579939b971 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Jun 2022 10:18:05 -0400 Subject: fixed some problems with dragging doc onto empty tab bar. made topBar be just one line to save vertical space. added a context menu to Dashboard --- src/client/util/CurrentUserUtils.ts | 14 ++++--- src/client/util/DragManager.ts | 2 +- .../collectionLinear/CollectionLinearView.tsx | 1 + src/client/views/global/globalCssVariables.scss | 2 +- src/client/views/nodes/DocumentView.tsx | 11 +++--- src/client/views/topbar/TopBar.scss | 6 +-- src/client/views/topbar/TopBar.tsx | 45 ++++++++++++---------- 7 files changed, 46 insertions(+), 35 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e46bb3b3e..60bfc165b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -381,6 +381,7 @@ export class CurrentUserUtils { dontUndo: true, title, target, + dontRegisterView: true, hidden: hidden ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), @@ -399,6 +400,7 @@ export class CurrentUserUtils { _chromeHidden: true, backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", + dontRegisterView: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), ignoreClick: true, _gridGap: 0, @@ -719,7 +721,7 @@ export class CurrentUserUtils { ]; doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, - linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true + childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }, btnDescs.map(desc => doc[`dockedBtn-${desc.title}`] as Doc ?? (doc[`dockedBtn-${desc.title}`] = dockBtn({ title: desc.title, ...desc.opts() })))); } } @@ -833,12 +835,13 @@ export class CurrentUserUtils { }); if (doc.contextMenuBtns === undefined) { doc.contextMenuBtns = CurrentUserUtils.linearButtonList( - { title: "menu buttons", flexGap: 0, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, + { title: "menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, CurrentUserUtils.contextMenuTools(doc).map(params => !params.subMenu ? btnFunc(params) : CurrentUserUtils.linearButtonList({ title: params.title, + childDontRegisterViews: true, linearViewSubMenu: true, flexGap: 0, ignoreClick: true, linearViewExpandable: true, icon: params.title, _height: 30, linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, @@ -854,6 +857,7 @@ export class CurrentUserUtils { btnFunc(params) : CurrentUserUtils.linearButtonList({ title: params.title, + childDontRegisterViews: true, linearViewSubMenu: true, flexGap: 0, ignoreClick: true, linearViewExpandable: true, icon: params.title, _height: 30, linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, @@ -919,7 +923,7 @@ export class CurrentUserUtils { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, _chromeHidden: true, boxShadow: "0 0", - explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" + dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" }, sharingDocumentId + "outer", sharingDocumentId); (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; } @@ -943,7 +947,7 @@ export class CurrentUserUtils { doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My Imports", _forceActive: true, buttonMenu: true, buttonMenuDoc: newImportButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, - explainer: "This is where documents that are Imported into Dash will go." + dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." })); } } @@ -952,7 +956,7 @@ export class CurrentUserUtils { static setupSearchSidebar(doc: Doc) { if (doc.mySearchPanel === undefined) { doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({ - backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true, + dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true, childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Panel", system: true })) as any as Doc; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9f8c49081..c3c6c22df 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -455,7 +455,7 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; } - if (((e.target as any)?.className === "lm_tabs" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { + if (((e.target as any)?.className === "lm_tabs" || ((e.target as any)?.className === "lm_header" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { if (!startWindowDragTimer) { startWindowDragTimer = setTimeout(async () => { startWindowDragTimer = undefined; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index c0a33a5e0..9b1cb1601 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -153,6 +153,7 @@ export class CollectionLinearView extends CollectionSubView() { PanelWidth={nested ? doc[WidthSym] : this.dimension} PanelHeight={nested || doc._height ? doc[HeightSym] : this.dimension} renderDepth={this.props.renderDepth + 1} + dontRegisterView={BoolCast(this.rootDoc.childDontRegisterViews)} focus={emptyFunction} styleProvider={this.props.styleProvider} docViewPath={returnEmptyDoclist} diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index ce9cc05d6..a14634bdc 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -38,7 +38,7 @@ $small-text: 9px; // misc values $border-radius: 0.3em; $search-thumnail-size: 130; -$topbar-height: 55px; +$topbar-height: 32px; $antimodemenu-height: 36px; // dragged items diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1591840e6..60d16f044 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -764,7 +764,7 @@ export class DocumentViewInternal extends DocComponent this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" }); !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" }); - if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { + if (!Doc.IsSystem(this.rootDoc) && this.rootDoc._viewType !== CollectionViewType.Docking && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" }); const existingOnClick = cm.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; @@ -834,10 +834,10 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); - helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); - !Doc.UserDoc().novice && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); - !Doc.UserDoc().novice && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); + helpItems.push({ description: "Show Fields ", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); + !Doc.UserDoc().noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); + !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); + !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); } @@ -1204,6 +1204,7 @@ export class DocumentView extends React.Component { @observable public docView: DocumentViewInternal | undefined | null; + showContextMenu(pageX:number, pageY:number) { return this.docView?.onContextMenu(undefined, pageX, pageY); } get Document() { return this.props.Document; } get topMost() { return this.props.renderDepth === 0; } get rootDoc() { return this.docView?.rootDoc || this.Document; } diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss index 6662386d4..c5b340514 100644 --- a/src/client/views/topbar/TopBar.scss +++ b/src/client/views/topbar/TopBar.scss @@ -22,9 +22,9 @@ grid-auto-columns: 33.3% 33.3% 33.3%; align-items: center; - &:first-child { - height: 20px; - } + // &:first-child { + // height: 20px; + // } } .topbar-button-text { diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 57ceac2dd..5bee58ccb 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -3,14 +3,18 @@ import { Tooltip } from "@material-ui/core"; import { action } from "mobx"; import { observer } from "mobx-react"; import * as React from 'react'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { AclAdmin, Doc, DocListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { Cast, StrCast } from '../../../fields/Types'; +import { GetEffectiveAcl } from "../../../fields/util"; import { Utils } from '../../../Utils'; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { DocumentManager } from "../../util/DocumentManager"; +import { SelectionManager } from "../../util/SelectionManager"; import { SettingsManager } from "../../util/SettingsManager"; import { SharingManager } from "../../util/SharingManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; import { Borders, Colors } from "../global/globalEnums"; import { MainView } from "../MainView"; import "./TopBar.scss"; @@ -22,22 +26,38 @@ import "./TopBar.scss"; @observer export class TopBar extends React.Component { navigateToHome = () => { - CurrentUserUtils.closeActiveDashboard(); - CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => Doc.UserDoc().activePage = "home"); + CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => { + Doc.UserDoc().activePage = "home"; + CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use + }); } render() { - const myDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); const activeDashboard = Cast(Doc.UserDoc().activeDashboard, Doc, null) return ( //TODO:glr Add support for light / dark mode
    -
    Home
    + {activeDashboard ?
    {Doc.CurrentUserEmail} (Placeholder)
    : (null)}
    +
    SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}> + {activeDashboard ? StrCast(activeDashboard.title) : "Dash"} +
    +
    { + const dashView = DocumentManager.Instance.getDocumentView(activeDashboard); + ContextMenu.Instance.addItem({ description: "Open Dashboard View", event: this.navigateToHome, icon: "edit" }); + dashView?.showContextMenu(e.clientX+20, e.clientY+30); + }}> + +
    +
    {SharingManager.Instance.open(undefined, activeDashboard)}}> + {/* TODO: if this is my dashboard, display share + if this is a shared dashboard, display "view original or view annotated" */} + { Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author === Doc.CurrentUserEmail ? "Share": "view original" } +
    window.open( "https://brown-dash.github.io/Dash-Documentation/", "_blank")}> @@ -47,21 +67,6 @@ export class TopBar extends React.Component {
    -
    -
    - {activeDashboard ?
    Freeform View (Placeholder)
    : (null)} -
    -
    -
    - {activeDashboard ? StrCast(activeDashboard.title) : "Dash"} -
    -
    -
    - {/* TODO: if this is my dashboard, display share - if this is a shared dashboard, display "view original or view annotated" */} -
    {SharingManager.Instance.open(undefined, activeDashboard)}}>Share
    -
    -
    ); -- cgit v1.2.3-70-g09d2 From 910131b13080a2649606ef8a618c77fd81248343 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Jun 2022 10:39:00 -0400 Subject: fixed errors --- src/client/util/DragManager.ts | 2 +- src/client/views/nodes/RecordingBox/RecordingBox.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index c3c6c22df..dbfba8992 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -455,7 +455,7 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : dragData.defaultDropAction; } - if (((e.target as any)?.className === "lm_tabs" || ((e.target as any)?.className === "lm_header" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { + if (((e.target as any)?.className === "lm_tabs" || (e.target as any)?.className === "lm_header" || e?.shiftKey) && dragData.draggedDocuments.length === 1) { if (!startWindowDragTimer) { startWindowDragTimer = setTimeout(async () => { startWindowDragTimer = undefined; diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index eac1c63f9..10393624b 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -55,7 +55,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { render() { return
    - {!this.result && } + {!this.result && }
    ; } } -- cgit v1.2.3-70-g09d2 From 04fd9f20ccdc204dad5724008b076cbf9ea7b874 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Jun 2022 13:02:49 -0400 Subject: fixed some initialization problems with tabs --- src/client/util/History.ts | 6 ++-- .../views/collections/CollectionDockingView.tsx | 32 ++++++++++++---------- 2 files changed, 22 insertions(+), 16 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/History.ts b/src/client/util/History.ts index e6f75a7db..632348306 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -58,8 +58,10 @@ export namespace HistoryUtil { export function getState(): ParsedUrl { const state = copyState(history.state); - state.initializers = state.initializers || {}; - return state; + if (state) { + state.initializers = state.initializers || {}; + } + return state ?? {initializers:{}}; } // export function addHandler(handler: (state: ParsedUrl | null) => void) { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 4119fb5ba..d2687df17 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -188,10 +188,13 @@ export class CollectionDockingView extends CollectionSubView() { stack.addChild(docContentConfig, undefined); stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1]); } else { - const newItemStackConfig = { type: 'stack', content: [docContentConfig] }; - const newContentItem = glayRoot.layoutManager.createContentItem(newItemStackConfig, instance._goldenLayout); + const newContentItem = () => { + const newItem = glayRoot.layoutManager.createContentItem({ type: 'stack', content: [docContentConfig] }, instance._goldenLayout); + newItem.callDownwards('_$init'); + return newItem; + } if (glayRoot.contentItems.length === 0) { // if no rows / columns - glayRoot.addChild(newContentItem); + glayRoot.addChild(newContentItem()); } else if (glayRoot.contentItems[0].isStack) { glayRoot.contentItems[0].addChild(docContentConfig); } else if ( @@ -203,32 +206,33 @@ export class CollectionDockingView extends CollectionSubView() { else if (instance._goldenLayout.root.contentItems[0].isRow) { // if row switch (pullSide) { default: - case "right": glayRoot.contentItems[0].addChild(newContentItem); break; - case "left": glayRoot.contentItems[0].addChild(newContentItem, 0); break; + case "right": glayRoot.contentItems[0].addChild(newContentItem()); break; + case "left": glayRoot.contentItems[0].addChild(newContentItem(), 0); break; case "top": case "bottom": // if not going in a row layout, must add already existing content into column const rowlayout = glayRoot.contentItems[0]; const newColumn = rowlayout.layoutManager.createContentItem({ type: "column" }, instance._goldenLayout); + const newItem = newContentItem(); instance._goldenLayout.saveScrollTops(rowlayout.element); rowlayout.parent.replaceChild(rowlayout, newColumn); if (pullSide === "top") { newColumn.addChild(rowlayout, undefined, true); - newColumn.addChild(newContentItem, 0, true); + newColumn.addChild(newItem, 0, true); } else if (pullSide === "bottom") { - newColumn.addChild(newContentItem, undefined, true); + newColumn.addChild(newItem, undefined, true); newColumn.addChild(rowlayout, 0, true); } instance._goldenLayout.restoreScrollTops(rowlayout.element); rowlayout.config.height = 50; - newContentItem.config.height = 50; + newItem.config.height = 50; } } else {// if (instance._goldenLayout.root.contentItems[0].isColumn) { // if column switch (pullSide) { - case "top": glayRoot.contentItems[0].addChild(newContentItem, 0); break; - case "bottom": glayRoot.contentItems[0].addChild(newContentItem); break; + case "top": glayRoot.contentItems[0].addChild(newContentItem(), 0); break; + case "bottom": glayRoot.contentItems[0].addChild(newContentItem()); break; case "left": case "right": default: @@ -236,23 +240,23 @@ export class CollectionDockingView extends CollectionSubView() { const collayout = glayRoot.contentItems[0]; const newRow = collayout.layoutManager.createContentItem({ type: "row" }, instance._goldenLayout); + const newItem = newContentItem(); instance._goldenLayout.saveScrollTops(collayout.element); collayout.parent.replaceChild(collayout, newRow); if (pullSide === "left") { newRow.addChild(collayout, undefined, true); - newRow.addChild(newContentItem, 0, true); + newRow.addChild(newItem, 0, true); } else { - newRow.addChild(newContentItem, undefined, true); + newRow.addChild(newItem, undefined, true); newRow.addChild(collayout, 0, true); } instance._goldenLayout.restoreScrollTops(collayout.element); collayout.config.width = 50; - newContentItem.config.width = 50; + newItem.config.width = 50; } } instance._ignoreStateChange = JSON.stringify(instance._goldenLayout.toConfig()); - newContentItem.callDownwards('_$init'); } return instance.layoutChanged(); -- cgit v1.2.3-70-g09d2 From 1688af3de54419029773fb85b78bca4500f7f0de Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 10 Jun 2022 15:41:38 -0400 Subject: big bug problems with recording. some reason interacting with the canvas is making the recording inactive - i think :/ --- src/client/util/RecordingApi.ts | 153 +++++++++++++-------- .../views/nodes/RecordingBox/ProgressBar.tsx | 8 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 12 +- .../views/nodes/RecordingBox/RecordingView.tsx | 99 ++++++++----- 4 files changed, 166 insertions(+), 106 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 021feee9a..ab6935e3b 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -3,8 +3,6 @@ import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; import { Doc } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; -import { scaleDiverging } from "d3-scale"; -import { Transform } from "./Transform"; type Movement = { time: number, @@ -13,17 +11,17 @@ type Movement = { scale: number, } -type Presentation = { - movements: Array | null - meta: Object, +export type Presentation = { + movements: Movement[] | null, + totalTime: number, + meta: any, } export class RecordingApi { - private static NULL_PRESENTATION: Presentation = { - movements: null, - meta: {}, - } + private static get NULL_PRESENTATION(): Presentation { + return { movements: null, meta: {}, totalTime: -1,} + } // instance variables private currentPresentation: Presentation; @@ -50,6 +48,8 @@ export class RecordingApi { // for now, set playFFView this.playFFView = null; this.timers = null; + + // put a pointerdown event on the doucment to see what the target } // little helper :) @@ -59,8 +59,9 @@ export class RecordingApi { public start = (meta?: Object): Error | undefined => { // check if already init a presentation - if (!this.isInitPresenation) { - console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') + if (!this.isInitPresenation) { + console.log(this.currentPresentation) + console.trace('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') return new Error('[recordingApi.ts] start()') } @@ -80,50 +81,61 @@ export class RecordingApi { this.isRecording = true } - public clear = (): Error | Presentation => { + /* stops the video and returns the presentatation; if no presentation, returns undefined */ + public getPresentation = (): undefined | Presentation => { // TODO: maybe archive the data? - if (this.isRecording) { - console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first') - return new Error('[recordingApi.ts] clear()') - } - - // update the presentation mode - Doc.UserDoc().presentationMode = 'none' - // set the previus recording view to the play view - this.playFFView = this.recordingFFView - - const presCopy = { ...this.currentPresentation } - - // clear presenation data - this.currentPresentation = RecordingApi.NULL_PRESENTATION - // clear isRecording - this.isRecording = false - // clear absoluteStart - this.absoluteStart = -1 - // clear the disposeFunc - this.removeRecordingFFView() - - return presCopy; + if (this.isRecording) console.warn('[recordingApi.ts] getPresentation() : currently recording presentation.'); + + // update the presentation mode + Doc.UserDoc().presentationMode = 'none'; + // set the previus recording view to the play view + this.playFFView = this.recordingFFView; + + // ensure we add the endTime now that they are done recording + return { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; + } + + public stop = (): void => { + // make is recording false + this.isRecording = false + } + + public clear = (): void => { + // clear presenation data + this.currentPresentation = RecordingApi.NULL_PRESENTATION + // clear isRecording + this.isRecording = false + // clear absoluteStart + this.absoluteStart = -1 + // clear the disposeFunc + this.removeRecordingFFView() + } + + + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; } - public pause = (): Error | undefined => { - if (this.isInitPresenation) { - console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - return new Error('[recordingApi.ts] pause(): no presentation') - } - // don't allow track movments - this.isRecording = false - - // set adjust absoluteStart to add the time difference - const timestamp = new Date().getTime() - this.absoluteStart = timestamp - this.absoluteStart - } + // public pause = (): Error | undefined => { + // if (this.isInitPresenation) { + // console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + // return new Error('[recordingApi.ts] pause(): no presentation') + // } + // // don't allow track movments + // this.isRecording = false + + // // set adjust absoluteStart to add the time difference + // const timestamp = new Date().getTime() + // this.absoluteStart = timestamp - this.absoluteStart + // } - public resume = () => { - this.isRecording = true - // set absoluteStart to the difference in time - this.absoluteStart = new Date().getTime() - this.absoluteStart - } + // public resume = () => { + // this.isRecording = true + // // set absoluteStart to the difference in time + // this.absoluteStart = new Date().getTime() - this.absoluteStart + // } private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { // ensure we are recording @@ -134,6 +146,8 @@ export class RecordingApi { if (this.isInitPresenation) { return new Error('[recordingApi.ts] trackMovements(): no presentation') } + + console.log('track movment') // get the time const time = new Date().getTime() - this.absoluteStart @@ -164,12 +178,6 @@ export class RecordingApi { this.recordingFFView = view; } - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; - } - // TODO: extract this into different class with pause and resume recording // TODO: store the FFview with the movements private playFFView: CollectionFreeFormView | null; @@ -250,6 +258,37 @@ export class RecordingApi { }, timeDiff) }) } + + // make a public method that concatenates the movements of the an array of presentations into one array + // TODO: consider the meta data of the presentations + public concatPresentations = (presentations: Presentation[]): Presentation => { + console.info(presentations); + if (presentations.length === 0) return RecordingApi.NULL_PRESENTATION; + const firstPresentation = presentations[0]; + + let sumTime = presentations[0].totalTime; + let combinedPresentations = { ...firstPresentation } + presentations.forEach((presentation, i) => { + // already consider the first presentation + if (i === 0) return; + + const { movements, totalTime } = presentation; + if (movements === null) return; + + // add the summed time to the movements + const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } }); + // concat the movements already in the combined presentation with these new ones + const newMovements = [...combinedPresentations.movements || [], ...addedTimeMovements]; + + combinedPresentations = { ...combinedPresentations, movements: newMovements } + + // update the totalTime + sumTime += totalTime; + }); + + // return the combined presentation with the updated total summed time + return { ...combinedPresentations, totalTime: sumTime }; + } // Unfinished code for tracing multiple free form views // export let pres: Map = new Map() diff --git a/src/client/views/nodes/RecordingBox/ProgressBar.tsx b/src/client/views/nodes/RecordingBox/ProgressBar.tsx index 493069394..1bb2b7c84 100644 --- a/src/client/views/nodes/RecordingBox/ProgressBar.tsx +++ b/src/client/views/nodes/RecordingBox/ProgressBar.tsx @@ -161,12 +161,12 @@ export function ProgressBar(props: ProgressBarProps) { } // pointerdown event for the progress bar - const onPointerDown = (e: React.PointerEvent) => { - // don't move the videobox element - e.stopPropagation() + const onPointerDown = (e: React.PointerEvent) => { + // don't move the videobox element + e.stopPropagation(); // if recording, do nothing - if (props.recording) return; + if (props.recording) return; // get the segment the user clicked on to be dragged const clickedSegment = e.target as HTMLDivElement & EventTarget diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 5e97e3eb5..6fe67b6db 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -8,8 +8,8 @@ import { FieldView } from "../FieldView"; import { VideoBox } from "../VideoBox"; import { RecordingView } from './RecordingView'; import { DocumentType } from "../../../documents/DocumentTypes"; -import { RecordingApi } from "../../../util/RecordingApi"; -import { Doc, FieldsSym } from "../../../../fields/Doc"; +import { Presentation } from "../../../util/RecordingApi"; +import { Doc } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; @@ -21,7 +21,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { private _ref: React.RefObject = React.createRef(); constructor(props: any) { - super(props); + super(props); } componentDidMount() { @@ -38,7 +38,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { } @action - setResult = (info: Upload.AccessPathInfo, trackScreen: boolean) => { + setResult = (info: Upload.AccessPathInfo, presentation?: Presentation) => { this.result = info this.dataDoc.type = DocumentType.VID; this.dataDoc[this.fieldKey + "-duration"] = this.videoDuration; @@ -47,9 +47,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client); this.dataDoc[this.fieldKey + "-recorded"] = true; // stringify the presenation and store it - if (trackScreen) { - this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(RecordingApi.Instance.clear()); - } + presentation?.movements && (this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(presentation)); } render() { diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 138e72274..208eaf45a 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -8,16 +8,17 @@ import { IconContext } from "react-icons"; import { Networking } from '../../../Network'; import { Upload } from '../../../../server/SharedMediaTypes'; import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; -import { RecordingApi } from '../../../util/RecordingApi'; +import { Presentation, RecordingApi } from '../../../util/RecordingApi'; export interface MediaSegment { videoChunks: any[], endTime: number, - startTime: number + startTime: number, + presenation?: Presentation, } interface IRecordingViewProps { - setResult: (info: Upload.AccessPathInfo, trackScreen: boolean) => void + setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void setDuration: (seconds: number) => void id: string } @@ -58,13 +59,18 @@ export function RecordingView(props: IRecordingViewProps) { sampleRate: 44100 } } + + useEffect(() => console.info('progess', progress), [progress]) useEffect(() => { - if (finished) { + if (finished) { + // make the total presentation that'll match the concatted video + const concatPres = trackScreen ? RecordingApi.Instance.concatPresentations(videos.map(v => v.presenation as Presentation)) : undefined; + // this async function uses the server to create the concatted video and then sets the result to it's accessPaths (async () => { - const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); + const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); // upload the segments to the server and get their server access paths const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles)) @@ -72,7 +78,7 @@ export function RecordingView(props: IRecordingViewProps) { // concat the segments together using post call const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths); - !(result instanceof Error) ? props.setResult(result, trackScreen) : console.error("video conversion failed"); + !(result instanceof Error) ? props.setResult(result, concatPres) : console.error("video conversion failed"); })(); } }, [videos]) @@ -133,53 +139,70 @@ export function RecordingView(props: IRecordingViewProps) { trackScreen && RecordingApi.Instance.start(); } - videoRecorder.current.onstop = () => { + videoRecorder.current.onstop = () => { + RecordingApi.Instance.stop(); // if we have a last portion - if (videoChunks.length > 1) { + if (videoChunks.length > 1) { // append the current portion to the video pieces - setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }]) + setVideos(videos => [...videos, { + videoChunks: videoChunks, + endTime: recordingTimerRef.current, + startTime: videos?.lastElement()?.endTime || 0, + // RecordingApi.stop() will return undefined if no track screen + presenation: RecordingApi.Instance.getPresentation() + }]) + // now that we got the presentation data, we can clear for the next segment to be recorded + RecordingApi.Instance.clear(); } - // reset the temporary chunks - videoChunks = [] - setRecording(false); - trackScreen && RecordingApi.Instance.pause(); + // reset the temporary chunks + videoChunks = [] + setRecording(false); } videoRecorder.current.start(200) } - const stop = (e: React.PointerEvent) => { - e.stopPropagation(); - if (videoRecorder.current) { - if (videoRecorder.current.state !== "inactive") { - videoRecorder.current.stop(); - } - - // this will call upon progessbar to update videos to be in the correct order - setFinished(true); - - // end the streams (audio/video) to remove recording icon - const stream = videoElementRef.current!.srcObject; - stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); - } + const finish = (e: React.PointerEvent) => { + e.stopPropagation(); + console.log('finish', videoRecorder.current) + // if inactive, then we're done recording all the segments + if (videoRecorder.current && videoRecorder.current.state !== "inactive") { + console.log('stopping recorder', videoRecorder.current?.state) + + + + // call stop on the video recorder + videoRecorder.current?.stop(); + + // end the streams (audio/video) to remove recording icon + const stream = videoElementRef.current!.srcObject; + stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); + + // clear the recoringApi - this is done in the stop method + // RecordingApi.Instance.clear(); + + // this will call upon progessbar to update videos to be in the correct order + console.log('setFinished to true', finished) + setFinished(true); + } } const pause = (e: React.PointerEvent) => { - e.stopPropagation() - if (videoRecorder.current) { - if (videoRecorder.current.state === "recording") { - videoRecorder.current.stop(); - } - } + e.stopPropagation() + // if recording, then this is just a new segment + videoRecorder.current?.state === "recording" && videoRecorder.current.stop(); } - const startOrResume = (e: React.PointerEvent) => { + const start = (e: React.PointerEvent) => { // the code to start or resume does not get triggered if we start dragging the button setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { - (!videoRecorder.current || videoRecorder.current.state === "inactive") && record(); - return true; // cancels propagation to documentView to avoid selecting it. + if (!videoRecorder.current || videoRecorder.current.state === "inactive") { + record(); + // trackScreen && RecordingApi.Instance.start(); + } + return true; // cancels propagation to documentView to avoid selecting it. }, false, false); } @@ -218,7 +241,7 @@ export function RecordingView(props: IRecordingViewProps) {
    {recording ?
    @@ -229,7 +252,7 @@ export function RecordingView(props: IRecordingViewProps) { - +
    -- cgit v1.2.3-70-g09d2 From daeed5592522c01ea2c38a438c24d914f117530b Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 13 Jun 2022 12:18:55 -0400 Subject: small fix on recordingApi to work with segments. now tracks accurately --- src/client/util/RecordingApi.ts | 53 ++++++++++++---------- .../views/nodes/RecordingBox/RecordingView.tsx | 27 ++++++----- 2 files changed, 44 insertions(+), 36 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index ab6935e3b..009652f6e 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -25,7 +25,7 @@ export class RecordingApi { // instance variables private currentPresentation: Presentation; - private isRecording: boolean; + private tracking: boolean; private absoluteStart: number; @@ -38,7 +38,7 @@ export class RecordingApi { // init the instance variables this.currentPresentation = RecordingApi.NULL_PRESENTATION - this.isRecording = false; + this.tracking = false; this.absoluteStart = -1; // used for tracking movements in the view frame @@ -57,58 +57,61 @@ export class RecordingApi { return this.currentPresentation.movements === null } - public start = (meta?: Object): Error | undefined => { + public start = (meta?: Object) => { // check if already init a presentation if (!this.isInitPresenation) { console.log(this.currentPresentation) - console.trace('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') - return new Error('[recordingApi.ts] start()') + console.trace('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') } // update the presentation mode - Doc.UserDoc().presentationMode = 'recording' + Doc.UserDoc().presentationMode = 'recording'; // (1a) get start date for presenation - const startDate = new Date() + const startDate = new Date(); // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime() + this.absoluteStart = startDate.getTime(); // (2) assign meta content if it exists this.currentPresentation.meta = meta || {} // (3) assign start date to currentPresenation this.currentPresentation.movements = [] - // (4) set isRecording true to allow trackMovements - this.isRecording = true + // (4) set tracking true to allow trackMovements + this.tracking = true } /* stops the video and returns the presentatation; if no presentation, returns undefined */ - public getPresentation = (): undefined | Presentation => { + public* yieldPresentation(clearData: boolean = true): Generator { // TODO: maybe archive the data? - if (this.isRecording) console.warn('[recordingApi.ts] getPresentation() : currently recording presentation.'); + // if (this.tracking) console.warn('[recordingApi.ts] getPresentation() : currently recording presentation.'); // update the presentation mode - Doc.UserDoc().presentationMode = 'none'; + // Doc.UserDoc().presentationMode = 'none'; // set the previus recording view to the play view this.playFFView = this.recordingFFView; // ensure we add the endTime now that they are done recording - return { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; + yield { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; + + // reset the current presentation + clearData && this.clear(); } public stop = (): void => { - // make is recording false - this.isRecording = false + // make is tracking false + this.tracking = false } public clear = (): void => { + // clear the disposeFunc if we are done (not recording) + if (!this.tracking) + this.removeRecordingFFView() // clear presenation data this.currentPresentation = RecordingApi.NULL_PRESENTATION // clear isRecording - this.isRecording = false + // this.tracking = false // clear absoluteStart this.absoluteStart = -1 - // clear the disposeFunc - this.removeRecordingFFView() } @@ -124,7 +127,7 @@ export class RecordingApi { // return new Error('[recordingApi.ts] pause(): no presentation') // } // // don't allow track movments - // this.isRecording = false + // this.tracking = false // // set adjust absoluteStart to add the time difference // const timestamp = new Date().getTime() @@ -132,14 +135,14 @@ export class RecordingApi { // } // public resume = () => { - // this.isRecording = true + // this.tracking = true // // set absoluteStart to the difference in time // this.absoluteStart = new Date().getTime() - this.absoluteStart // } private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { // ensure we are recording - if (!this.isRecording) { + if (!this.tracking) { return new Error('[recordingApi.ts] trackMovements()') } // check to see if the presetation is init @@ -171,7 +174,7 @@ export class RecordingApi { // set the reaction to track the movements this.disposeFunc = reaction( () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), - (res) => (res.x !== -1 && res.y !== -1 && this.isRecording) && this.trackMovements(res.x, res.y, res.scale) + (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) ) // for now, set the most recent recordingFFView to the playFFView @@ -262,11 +265,11 @@ export class RecordingApi { // make a public method that concatenates the movements of the an array of presentations into one array // TODO: consider the meta data of the presentations public concatPresentations = (presentations: Presentation[]): Presentation => { - console.info(presentations); + console.table(presentations); if (presentations.length === 0) return RecordingApi.NULL_PRESENTATION; const firstPresentation = presentations[0]; - let sumTime = presentations[0].totalTime; + let sumTime = firstPresentation.totalTime; let combinedPresentations = { ...firstPresentation } presentations.forEach((presentation, i) => { // already consider the first presentation diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 380ac050d..be9f342bb 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -14,7 +14,7 @@ export interface MediaSegment { videoChunks: any[], endTime: number, startTime: number, - presenation?: Presentation, + presentation?: Presentation, } interface IRecordingViewProps { @@ -60,13 +60,14 @@ export function RecordingView(props: IRecordingViewProps) { } } - useEffect(() => console.info('progess', progress), [progress]) + // useEffect(() => console.info('progress', progress), [progress]) useEffect(() => { if (finished) { // make the total presentation that'll match the concatted video - const concatPres = trackScreen ? RecordingApi.Instance.concatPresentations(videos.map(v => v.presenation as Presentation)) : undefined; + const concatPres = RecordingApi.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); + console.log('concatPres', concatPres); // this async function uses the server to create the concatted video and then sets the result to it's accessPaths (async () => { @@ -91,7 +92,8 @@ export function RecordingView(props: IRecordingViewProps) { // check if the browser supports media devices on first load useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, []) - useEffect(() => { + useEffect(() => { + console.log('recording useEffect', recording) let interval: any = null; if (recording) { interval = setInterval(() => { @@ -136,23 +138,26 @@ export function RecordingView(props: IRecordingViewProps) { videoRecorder.current.onstart = (event: any) => { setRecording(true); + // trackScreen && RecordingApi.Instance.start(); trackScreen && RecordingApi.Instance.start(); } videoRecorder.current.onstop = () => { - RecordingApi.Instance.stop(); + // RecordingApi.Instance.stop(); // if we have a last portion - if (videoChunks.length > 1) { + if (videoChunks.length > 1) { + const presentation = RecordingApi.Instance.yieldPresentation().next().value || undefined + console.log('presenation yield', JSON.parse(JSON.stringify(presentation))) // append the current portion to the video pieces setVideos(videos => [...videos, { videoChunks: videoChunks, endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0, // RecordingApi.stop() will return undefined if no track screen - presenation: RecordingApi.Instance.getPresentation() + presentation }]) // now that we got the presentation data, we can clear for the next segment to be recorded - RecordingApi.Instance.clear(); + // RecordingApi.Instance.clear(); } // reset the temporary chunks @@ -193,7 +198,7 @@ export function RecordingView(props: IRecordingViewProps) { setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { if (!videoRecorder.current || videoRecorder.current.state === "inactive") { record(); - // trackScreen && RecordingApi.Instance.start(); + // trackScreen && } return true; // cancels propagation to documentView to avoid selecting it. }, false, false); @@ -204,7 +209,7 @@ export function RecordingView(props: IRecordingViewProps) { setDoUndo(prev => !prev); } - const handleOnTimeUpdate = () => playing && setVideoProgressHelper(videoElementRef.current!.currentTime); + const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); }; const millisecondToMinuteSecond = (milliseconds: number) => { const toTwoDigit = (digit: number) => { @@ -221,7 +226,7 @@ export function RecordingView(props: IRecordingViewProps) {
    -- cgit v1.2.3-70-g09d2 From a3faf09f5a59f1500894c629cb0052e2cf7413e4 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 13 Jun 2022 13:00:33 -0400 Subject: support for not checking the track screen movements --- src/client/util/RecordingApi.ts | 70 ++-- .../views/nodes/RecordingBox/RecordingView.tsx | 442 ++++++++++----------- 2 files changed, 248 insertions(+), 264 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 009652f6e..12c1654a2 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -58,13 +58,7 @@ export class RecordingApi { } public start = (meta?: Object) => { - // check if already init a presentation - if (!this.isInitPresenation) { - console.log(this.currentPresentation) - console.trace('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.') - } - - // update the presentation mode + // update the presentation mode Doc.UserDoc().presentationMode = 'recording'; // (1a) get start date for presenation @@ -81,12 +75,10 @@ export class RecordingApi { } /* stops the video and returns the presentatation; if no presentation, returns undefined */ - public* yieldPresentation(clearData: boolean = true): Generator { - // TODO: maybe archive the data? - // if (this.tracking) console.warn('[recordingApi.ts] getPresentation() : currently recording presentation.'); + public * yieldPresentation(clearData: boolean = true): Generator { + // if no presentation or done tracking, return null + if (!this.isInitPresenation || !this.tracking) return null; - // update the presentation mode - // Doc.UserDoc().presentationMode = 'none'; // set the previus recording view to the play view this.playFFView = this.recordingFFView; @@ -103,9 +95,12 @@ export class RecordingApi { } public clear = (): void => { - // clear the disposeFunc if we are done (not recording) - if (!this.tracking) - this.removeRecordingFFView() + // clear the disposeFunc if we are done (not tracking) + if (!this.tracking) { + this.removeRecordingFFView(); + // update the presentation mode now that we are done tracking + Doc.UserDoc().presentationMode = 'none'; + } // clear presenation data this.currentPresentation = RecordingApi.NULL_PRESENTATION // clear isRecording @@ -114,7 +109,6 @@ export class RecordingApi { this.absoluteStart = -1 } - // call on dispose function to stop tracking movements public removeRecordingFFView = (): void => { this.disposeFunc?.(); @@ -150,7 +144,8 @@ export class RecordingApi { return new Error('[recordingApi.ts] trackMovements(): no presentation') } - console.log('track movment') + // TO FIX: bob + // console.debug('track movment') // get the time const time = new Date().getTime() - this.absoluteStart @@ -262,35 +257,30 @@ export class RecordingApi { }) } - // make a public method that concatenates the movements of the an array of presentations into one array - // TODO: consider the meta data of the presentations + // method that concatenates an array of presentatations into one public concatPresentations = (presentations: Presentation[]): Presentation => { - console.table(presentations); - if (presentations.length === 0) return RecordingApi.NULL_PRESENTATION; - const firstPresentation = presentations[0]; - - let sumTime = firstPresentation.totalTime; - let combinedPresentations = { ...firstPresentation } - presentations.forEach((presentation, i) => { - // already consider the first presentation - if (i === 0) return; - - const { movements, totalTime } = presentation; - if (movements === null) return; - - // add the summed time to the movements - const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } }); - // concat the movements already in the combined presentation with these new ones - const newMovements = [...combinedPresentations.movements || [], ...addedTimeMovements]; - - combinedPresentations = { ...combinedPresentations, movements: newMovements } - + // these three will lead to the combined presentation + let combinedMovements: Movement[] = []; + let sumTime = 0; + let combinedMetas: any[] = []; + + presentations.forEach((presentation) => { + const { movements, totalTime, meta } = presentation; + // update movements if they had one + if (movements) { + // add the summed time to the movements + const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } }); + // concat the movements already in the combined presentation with these new ones + combinedMovements.push(...addedTimeMovements); + } // update the totalTime sumTime += totalTime; + // concatenate the metas + combinedMetas.push(...meta); }); // return the combined presentation with the updated total summed time - return { ...combinedPresentations, totalTime: sumTime }; + return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; } // Unfinished code for tracing multiple free form views diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index be9f342bb..35a6aa07e 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -11,270 +11,264 @@ import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; import { Presentation, RecordingApi } from '../../../util/RecordingApi'; export interface MediaSegment { - videoChunks: any[], - endTime: number, + videoChunks: any[], + endTime: number, startTime: number, presentation?: Presentation, } interface IRecordingViewProps { - setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void - setDuration: (seconds: number) => void - id: string + setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void + setDuration: (seconds: number) => void + id: string } const MAXTIME = 100000; export function RecordingView(props: IRecordingViewProps) { - const [recording, setRecording] = useState(false); - const recordingTimerRef = useRef(0); - const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second - const [playing, setPlaying] = useState(false); - const [progress, setProgress] = useState(0); - - // acts as a "refresh state" to tell progressBar when to undo - const [doUndo, setDoUndo] = useState(false); - // whether an undo can occur or not - const [canUndo, setCanUndo] = useState(false); - - const [videos, setVideos] = useState([]); - const [orderVideos, setOrderVideos] = useState(false); - const videoRecorder = useRef(null); - const videoElementRef = useRef(null); - - const [finished, setFinished] = useState(false) - const [trackScreen, setTrackScreen] = useState(true) - - - - const DEFAULT_MEDIA_CONSTRAINTS = { - video: { - width: 1280, - height: 720, - }, - audio: { - echoCancellation: true, - noiseSuppression: true, - sampleRate: 44100 - } - } - - // useEffect(() => console.info('progress', progress), [progress]) + const [recording, setRecording] = useState(false); + const recordingTimerRef = useRef(0); + const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second + const [playing, setPlaying] = useState(false); + const [progress, setProgress] = useState(0); + // acts as a "refresh state" to tell progressBar when to undo + const [doUndo, setDoUndo] = useState(false); + // whether an undo can occur or not + const [canUndo, setCanUndo] = useState(false); - useEffect(() => { - if (finished) { - // make the total presentation that'll match the concatted video - const concatPres = RecordingApi.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); - console.log('concatPres', concatPres); + const [videos, setVideos] = useState([]); + const [orderVideos, setOrderVideos] = useState(false); + const videoRecorder = useRef(null); + const videoElementRef = useRef(null); - // this async function uses the server to create the concatted video and then sets the result to it's accessPaths - (async () => { - const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); + const [finished, setFinished] = useState(false) + const [trackScreen, setTrackScreen] = useState(true) - // upload the segments to the server and get their server access paths - const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles)) - .map(res => (res.result instanceof Error) ? '' : res.result.accessPaths.agnostic.server) - // concat the segments together using post call - const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths); - !(result instanceof Error) ? props.setResult(result, concatPres) : console.error("video conversion failed"); - })(); - } - }, [videos]) - // this will call upon the progress bar to edit videos to be in the correct order - useEffect(() => { - finished && setOrderVideos(true); - }, [finished]) + const DEFAULT_MEDIA_CONSTRAINTS = { + video: { + width: 1280, + height: 720, + }, + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 44100 + } + } - // check if the browser supports media devices on first load - useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, []) + // useEffect(() => console.debug('progress', progress), [progress]) useEffect(() => { - console.log('recording useEffect', recording) - let interval: any = null; - if (recording) { - interval = setInterval(() => { - setRecordingTimer(unit => unit + 1); - }, 10); - } else if (!recording && recordingTimer !== 0) { - clearInterval(interval); - } - return () => clearInterval(interval); - }, [recording]) - - useEffect(() => { - setVideoProgressHelper(recordingTimer) - recordingTimerRef.current = recordingTimer; - }, [recordingTimer]) - - const setVideoProgressHelper = (progress: number) => { - const newProgress = (progress / MAXTIME) * 100; - setProgress(newProgress) + if (finished) { + // make the total presentation that'll match the concatted video + let concatPres = trackScreen && RecordingApi.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); + + // this async function uses the server to create the concatted video and then sets the result to it's accessPaths + (async () => { + const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); + + // upload the segments to the server and get their server access paths + const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles)) + .map(res => (res.result instanceof Error) ? '' : res.result.accessPaths.agnostic.server) + + // concat the segments together using post call + const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths); + !(result instanceof Error) ? props.setResult(result, concatPres || undefined) : console.error("video conversion failed"); + })(); } + }, [videos]) - const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => { - const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints) + // this will call upon the progress bar to edit videos to be in the correct order + useEffect(() => { + finished && setOrderVideos(true); + }, [finished]) - videoElementRef.current!.src = "" - videoElementRef.current!.srcObject = stream - videoElementRef.current!.muted = true + // check if the browser supports media devices on first load + useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, []) - return stream + useEffect(() => { + let interval: any = null; + if (recording) { + interval = setInterval(() => { + setRecordingTimer(unit => unit + 1); + }, 10); + } else if (!recording && recordingTimer !== 0) { + clearInterval(interval); } + return () => clearInterval(interval); + }, [recording]) - const record = async () => { - // don't need to start a new stream every time we start recording a new segment - if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream()) - - // temporary chunks of video - let videoChunks: any = [] - - videoRecorder.current.ondataavailable = (event: any) => { - if (event.data.size > 0) videoChunks.push(event.data) - } - - videoRecorder.current.onstart = (event: any) => { - setRecording(true); - // trackScreen && RecordingApi.Instance.start(); - trackScreen && RecordingApi.Instance.start(); - } - - videoRecorder.current.onstop = () => { - // RecordingApi.Instance.stop(); - // if we have a last portion - if (videoChunks.length > 1) { - const presentation = RecordingApi.Instance.yieldPresentation().next().value || undefined - console.log('presenation yield', JSON.parse(JSON.stringify(presentation))) - // append the current portion to the video pieces - setVideos(videos => [...videos, { - videoChunks: videoChunks, - endTime: recordingTimerRef.current, - startTime: videos?.lastElement()?.endTime || 0, - // RecordingApi.stop() will return undefined if no track screen - presentation - }]) - // now that we got the presentation data, we can clear for the next segment to be recorded - // RecordingApi.Instance.clear(); - } - - // reset the temporary chunks - videoChunks = [] - setRecording(false); - } - - videoRecorder.current.start(200) - } + useEffect(() => { + setVideoProgressHelper(recordingTimer) + recordingTimerRef.current = recordingTimer; + }, [recordingTimer]) + const setVideoProgressHelper = (progress: number) => { + const newProgress = (progress / MAXTIME) * 100; + setProgress(newProgress) + } - // if this is called, then we're done recording all the segments - const finish = (e: React.PointerEvent) => { - e.stopPropagation(); + const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => { + const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints) - // call stop on the video recorder if active - videoRecorder.current?.state !== "inactive" && videoRecorder.current?.stop(); + videoElementRef.current!.src = "" + videoElementRef.current!.srcObject = stream + videoElementRef.current!.muted = true - // end the streams (audio/video) to remove recording icon - const stream = videoElementRef.current!.srcObject; - stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); - - // clear the recoringApi - RecordingApi.Instance.clear(); + return stream + } - // this will call upon progessbar to update videos to be in the correct order - setFinished(true); - } + const record = async () => { + // don't need to start a new stream every time we start recording a new segment + if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream()) + + // temporary chunks of video + let videoChunks: any = [] - const pause = (e: React.PointerEvent) => { - e.stopPropagation() - // if recording, then this is just a new segment - videoRecorder.current?.state === "recording" && videoRecorder.current.stop(); + videoRecorder.current.ondataavailable = (event: any) => { + if (event.data.size > 0) videoChunks.push(event.data) } - const start = (e: React.PointerEvent) => { - // the code to start or resume does not get triggered if we start dragging the button - setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { - if (!videoRecorder.current || videoRecorder.current.state === "inactive") { - record(); - // trackScreen && - } - return true; // cancels propagation to documentView to avoid selecting it. - }, false, false); + videoRecorder.current.onstart = (event: any) => { + setRecording(true); + // start the recording api when the video recorder starts + trackScreen && RecordingApi.Instance.start(); } - const undoPrevious = (e: React.PointerEvent) => { - e.stopPropagation(); - setDoUndo(prev => !prev); + videoRecorder.current.onstop = () => { + // RecordingApi.Instance.stop(); + // if we have a last portion + if (videoChunks.length > 1) { + // append the current portion to the video pieces + const nextVideo = { + videoChunks, + endTime: recordingTimerRef.current, + startTime: videos?.lastElement()?.endTime || 0 + }; + // depending on if a presenation exists, add it to the video + const { done: presError, value: presentation } = RecordingApi.Instance.yieldPresentation().next(); + setVideos(videos => [...videos, (!presError && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); + } + + // reset the temporary chunks + videoChunks = [] + setRecording(false); } + videoRecorder.current.start(200) + } + + + // if this is called, then we're done recording all the segments + const finish = (e: React.PointerEvent) => { + e.stopPropagation(); + + // call stop on the video recorder if active + videoRecorder.current?.state !== "inactive" && videoRecorder.current?.stop(); + + // end the streams (audio/video) to remove recording icon + const stream = videoElementRef.current!.srcObject; + stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); + + // clear the recoringApi + RecordingApi.Instance.clear(); + + // this will call upon progessbar to update videos to be in the correct order + setFinished(true); + } + + const pause = (e: React.PointerEvent) => { + e.stopPropagation() + // if recording, then this is just a new segment + videoRecorder.current?.state === "recording" && videoRecorder.current.stop(); + } + + const start = (e: React.PointerEvent) => { + // the code to start or resume does not get triggered if we start dragging the button + setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { + if (!videoRecorder.current || videoRecorder.current.state === "inactive") { + record(); + // trackScreen && + } + return true; // cancels propagation to documentView to avoid selecting it. + }, false, false); + } + + const undoPrevious = (e: React.PointerEvent) => { + e.stopPropagation(); + setDoUndo(prev => !prev); + } + const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); }; - const millisecondToMinuteSecond = (milliseconds: number) => { - const toTwoDigit = (digit: number) => { - return String(digit).length == 1 ? "0" + digit : digit - } - const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); - return toTwoDigit(minutes) + " : " + toTwoDigit(seconds); + const millisecondToMinuteSecond = (milliseconds: number) => { + const toTwoDigit = (digit: number) => { + return String(digit).length == 1 ? "0" + digit : digit } - - return ( -
    -
    -
    ) } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From d91ff1f937e9588b88258b07738eea56bac5fcca Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 13 Jun 2022 13:21:38 -0400 Subject: fix bugs with selecting tracking. very stable now. --- src/client/util/RecordingApi.ts | 408 ++++++++++----------- .../views/nodes/RecordingBox/RecordingBox.tsx | 2 +- .../views/nodes/RecordingBox/RecordingView.tsx | 18 +- 3 files changed, 208 insertions(+), 220 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 12c1654a2..d56093cee 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -5,10 +5,10 @@ import { Doc } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; type Movement = { - time: number, - panX: number, - panY: number, - scale: number, + time: number, + panX: number, + panY: number, + scale: number, } export type Presentation = { @@ -20,78 +20,82 @@ export type Presentation = { export class RecordingApi { private static get NULL_PRESENTATION(): Presentation { - return { movements: null, meta: {}, totalTime: -1,} + return { movements: null, meta: {}, totalTime: -1, } } - // instance variables - private currentPresentation: Presentation; - private tracking: boolean; - private absoluteStart: number; - - - // create static instance and getter for global use - @observable static _instance: RecordingApi; - public static get Instance(): RecordingApi { return RecordingApi._instance } - public constructor() { - // init the global instance - RecordingApi._instance = this; - - // init the instance variables - this.currentPresentation = RecordingApi.NULL_PRESENTATION - this.tracking = false; - this.absoluteStart = -1; - - // used for tracking movements in the view frame - this.disposeFunc = null; - this.recordingFFView = null; - - // for now, set playFFView - this.playFFView = null; - this.timers = null; - - // put a pointerdown event on the doucment to see what the target - } + // instance variables + private currentPresentation: Presentation; + private tracking: boolean; + private absoluteStart: number; - // little helper :) - private get isInitPresenation(): boolean { - return this.currentPresentation.movements === null - } - public start = (meta?: Object) => { - // update the presentation mode - Doc.UserDoc().presentationMode = 'recording'; - - // (1a) get start date for presenation - const startDate = new Date(); - // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime(); - - // (2) assign meta content if it exists - this.currentPresentation.meta = meta || {} - // (3) assign start date to currentPresenation - this.currentPresentation.movements = [] - // (4) set tracking true to allow trackMovements - this.tracking = true - } + // create static instance and getter for global use + @observable static _instance: RecordingApi; + public static get Instance(): RecordingApi { return RecordingApi._instance } + public constructor() { + // init the global instance + RecordingApi._instance = this; + + // init the instance variables + this.currentPresentation = RecordingApi.NULL_PRESENTATION + this.tracking = false; + this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; + this.recordingFFView = null; - /* stops the video and returns the presentatation; if no presentation, returns undefined */ - public * yieldPresentation(clearData: boolean = true): Generator { + // for now, set playFFView + this.playFFView = null; + this.timers = null; + + // put a pointerdown event on the doucment to see what the target + } + + // little helper :) + private get nullPresentation(): boolean { + return this.currentPresentation.movements === null + } + + public start = (meta?: Object) => { + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording'; + + // (1a) get start date for presenation + const startDate = new Date(); + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime(); + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {} + // (3) assign start date to currentPresenation + this.currentPresentation.movements = [] + // (4) set tracking true to allow trackMovements + this.tracking = true + } + + /* stops the video and returns the presentatation; if no presentation, returns undefined */ + public yieldPresentation(clearData: boolean = true): Presentation | null { // if no presentation or done tracking, return null - if (!this.isInitPresenation || !this.tracking) return null; + if (this.nullPresentation || !this.tracking) return null; // set the previus recording view to the play view this.playFFView = this.recordingFFView; // ensure we add the endTime now that they are done recording - yield { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; + const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; // reset the current presentation clearData && this.clear(); + + return cpy; } - public stop = (): void => { + public finish = (): void => { // make is tracking false this.tracking = false + // reset the RecordingApi instance + this.clear(); } public clear = (): void => { @@ -108,155 +112,138 @@ export class RecordingApi { // clear absoluteStart this.absoluteStart = -1 } - - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; - } - // public pause = (): Error | undefined => { - // if (this.isInitPresenation) { - // console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - // return new Error('[recordingApi.ts] pause(): no presentation') - // } - // // don't allow track movments - // this.tracking = false - - // // set adjust absoluteStart to add the time difference - // const timestamp = new Date().getTime() - // this.absoluteStart = timestamp - this.absoluteStart - // } - - // public resume = () => { - // this.tracking = true - // // set absoluteStart to the difference in time - // this.absoluteStart = new Date().getTime() - this.absoluteStart - // } - - private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { - // ensure we are recording - if (!this.tracking) { - return new Error('[recordingApi.ts] trackMovements()') - } - // check to see if the presetation is init - if (this.isInitPresenation) { - return new Error('[recordingApi.ts] trackMovements(): no presentation') - } - - // TO FIX: bob - // console.debug('track movment') - - // get the time - const time = new Date().getTime() - this.absoluteStart - // make new movement object - const movement: Movement = { time, panX, panY, scale } - - // add that movement to the current presentation data's movement array - this.currentPresentation.movements && this.currentPresentation.movements.push(movement) - } + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; + } - // instance variable for the FFView - private disposeFunc: IReactionDisposer | null; - private recordingFFView: CollectionFreeFormView | null; - - // set the FFView that will be used in a reaction to track the movements - public setRecordingFFView = (view: CollectionFreeFormView): void => { - // set the view to the current view - if (view === this.recordingFFView || view == null) return; - - // this.recordingFFView = view; - // set the reaction to track the movements - this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), - (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) - ) - - // for now, set the most recent recordingFFView to the playFFView - this.recordingFFView = view; + private trackMovements = (panX: number, panY: number, scale: number = 0) => { + // ensure we are recording + if (!this.tracking) { + console.error('[recordingApi.ts] trackMovements(): tracking is false') + return; } + // check to see if the presetation is init + if (this.nullPresentation) { + console.error('[recordingApi.ts] trackMovements(): no presentation') + return; + } + + // TO FIX: bob + // console.debug('track movment') + + // get the time + const time = new Date().getTime() - this.absoluteStart + // make new movement object + const movement: Movement = { time, panX, panY, scale } + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements && this.currentPresentation.movements.push(movement) + } - // TODO: extract this into different class with pause and resume recording - // TODO: store the FFview with the movements - private playFFView: CollectionFreeFormView | null; - private timers: NodeJS.Timeout[] | null; + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; - public setPlayFFView = (view: CollectionFreeFormView): void => { - this.playFFView = view + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === this.recordingFFView || view == null) return; + + // this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), + (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) + ) + + // for now, set the most recent recordingFFView to the playFFView + this.recordingFFView = view; + } + + // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements + private playFFView: CollectionFreeFormView | null; + private timers: NodeJS.Timeout[] | null; + + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view + } + + // pausing movements will dispose all timers that are planned to replay the movements + // play movemvents will recreate them when the user resumes the presentation + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view') } - // pausing movements will dispose all timers that are planned to replay the movements - // play movemvents will recreate them when the user resumes the presentation - public pauseMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] pauseMovements() failed: no view') - } - - if (!this._isPlaying) { - //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') - return - } - this._isPlaying = false - // TODO: set userdoc presentMode to browsing - this.timers?.map(timer => clearTimeout(timer)) - - // this.videoBox = null; + if (!this._isPlaying) { + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return } - - private videoBox: VideoBox | null = null; + this._isPlaying = false + // TODO: set userdoc presentMode to browsing + this.timers?.map(timer => clearTimeout(timer)) + + // this.videoBox = null; + } - // by calling pause on the VideoBox, the pauseMovements will be called - public pauseVideoAndMovements = (): boolean => { - this.videoBox?.Pause() + private videoBox: VideoBox | null = null; + + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause() + + this.pauseMovements() + return this.videoBox == null + } - this.pauseMovements() - return this.videoBox == null + public _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } + if (this._isPlaying) return; + + this._isPlaying = true; + Doc.UserDoc().presentationMode = 'watching'; + + // TODO: consider this bug at the end of the clip on seek + this.videoBox = videoBox || null; - public _isPlaying = false; - - public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { - if (presentation.movements === null || this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - } - if(this._isPlaying) return; - - this._isPlaying = true; - Doc.UserDoc().presentationMode = 'watching'; - - // TODO: consider this bug at the end of the clip on seek - this.videoBox = videoBox || null; - - // only get the movements that are remaining in the video time left - const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) - - // helper to replay a movement - const document = this.playFFView - let preScale = -1; - const zoomAndPan = (movement: Movement) => { - const { panX, panY, scale } = movement; - (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); - document.Document._panX = panX; - document.Document._panY = panY; - - preScale = scale; - } - - // set the first frame to be at the start of the pres - zoomAndPan(filteredMovements[0]); - - // make timers that will execute each movement at the correct replay time - this.timers = filteredMovements.map(movement => { - const timeDiff = movement.time - timeViewed*1000 - return setTimeout(() => { - // replay the movement - zoomAndPan(movement) - // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; - }, timeDiff) - }) + // only get the movements that are remaining in the video time left + const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) + + // helper to replay a movement + const document = this.playFFView + let preScale = -1; + const zoomAndPan = (movement: Movement) => { + const { panX, panY, scale } = movement; + (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + preScale = scale; } - + + // set the first frame to be at the start of the pres + zoomAndPan(filteredMovements[0]); + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed * 1000 + return setTimeout(() => { + // replay the movement + zoomAndPan(movement) + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + }, timeDiff) + }) + } + // method that concatenates an array of presentatations into one public concatPresentations = (presentations: Presentation[]): Presentation => { // these three will lead to the combined presentation @@ -266,6 +253,7 @@ export class RecordingApi { presentations.forEach((presentation) => { const { movements, totalTime, meta } = presentation; + // update movements if they had one if (movements) { // add the summed time to the movements @@ -273,8 +261,10 @@ export class RecordingApi { // concat the movements already in the combined presentation with these new ones combinedMovements.push(...addedTimeMovements); } + // update the totalTime sumTime += totalTime; + // concatenate the metas combinedMetas.push(...meta); }); @@ -283,19 +273,19 @@ export class RecordingApi { return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; } - // Unfinished code for tracing multiple free form views - // export let pres: Map = new Map() - - // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { - // pres.set(ffView, - // reaction(() => ({ x: ffView.panX, y: ffView.panY }), - // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) - // ) - // } - - // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { - // const disposer = pres.get(ffView); - // disposer?.(); - // pres.delete(ffView) - // } + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } } diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 6fe67b6db..a28677525 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -46,7 +46,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client); this.dataDoc[this.fieldKey + "-recorded"] = true; - // stringify the presenation and store it + // stringify the presentation and store it presentation?.movements && (this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(presentation)); } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 35a6aa07e..15883632a 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -140,7 +140,6 @@ export function RecordingView(props: IRecordingViewProps) { } videoRecorder.current.onstop = () => { - // RecordingApi.Instance.stop(); // if we have a last portion if (videoChunks.length > 1) { // append the current portion to the video pieces @@ -149,9 +148,10 @@ export function RecordingView(props: IRecordingViewProps) { endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }; + // depending on if a presenation exists, add it to the video - const { done: presError, value: presentation } = RecordingApi.Instance.yieldPresentation().next(); - setVideos(videos => [...videos, (!presError && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); + const presentation = RecordingApi.Instance.yieldPresentation(); + setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); } // reset the temporary chunks @@ -174,8 +174,8 @@ export function RecordingView(props: IRecordingViewProps) { const stream = videoElementRef.current!.srcObject; stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); - // clear the recoringApi - RecordingApi.Instance.clear(); + // finish/clear the recoringApi + RecordingApi.Instance.finish(); // this will call upon progessbar to update videos to be in the correct order setFinished(true); @@ -188,12 +188,10 @@ export function RecordingView(props: IRecordingViewProps) { } const start = (e: React.PointerEvent) => { - // the code to start or resume does not get triggered if we start dragging the button setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { - if (!videoRecorder.current || videoRecorder.current.state === "inactive") { - record(); - // trackScreen && - } + // start recording if not already recording + if (!videoRecorder.current || videoRecorder.current.state === "inactive") record(); + return true; // cancels propagation to documentView to avoid selecting it. }, false, false); } -- cgit v1.2.3-70-g09d2 From 1dfb694ffa8f34a834387eb073ffb9bd3a678039 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 13 Jun 2022 14:09:46 -0400 Subject: debug w bob --- src/client/util/RecordingApi.ts | 2 -- src/client/views/nodes/RecordingBox/RecordingView.tsx | 11 +++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index d56093cee..bcd21a48a 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -48,8 +48,6 @@ export class RecordingApi { // for now, set playFFView this.playFFView = null; this.timers = null; - - // put a pointerdown event on the doucment to see what the target } // little helper :) diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 15883632a..d4d19f3d8 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -60,7 +60,7 @@ export function RecordingView(props: IRecordingViewProps) { } } - // useEffect(() => console.debug('progress', progress), [progress]) + useEffect(() => console.log('progress', progress), [progress]) useEffect(() => { if (finished) { @@ -93,7 +93,8 @@ export function RecordingView(props: IRecordingViewProps) { useEffect(() => { let interval: any = null; if (recording) { - interval = setInterval(() => { + interval = setTimeout(() => { + console.log('update interval') setRecordingTimer(unit => unit + 1); }, 10); } else if (!recording && recordingTimer !== 0) { @@ -201,7 +202,9 @@ export function RecordingView(props: IRecordingViewProps) { setDoUndo(prev => !prev); } - const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); }; + const handleOnTimeUpdate = () => { + playing && setVideoProgressHelper(videoElementRef.current!.currentTime); + }; const millisecondToMinuteSecond = (milliseconds: number) => { const toTwoDigit = (digit: number) => { @@ -218,7 +221,7 @@ export function RecordingView(props: IRecordingViewProps) {
    -- cgit v1.2.3-70-g09d2 From f8bfda0b0139db5f8bf1de602ce211a091d5d9ef Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Mon, 13 Jun 2022 14:56:03 -0400 Subject: revert to previous stable commit. lmao get ratioed by a mouse --- src/client/util/RecordingApi.ts | 383 ++++++++++++--------- .../views/nodes/RecordingBox/RecordingBox.tsx | 2 +- .../views/nodes/RecordingBox/RecordingView.tsx | 18 +- 3 files changed, 223 insertions(+), 180 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index bcd21a48a..4c8f55ce5 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -5,10 +5,10 @@ import { Doc } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; type Movement = { - time: number, - panX: number, - panY: number, - scale: number, + time: number, + panX: number, + panY: number, + scale: number, } export type Presentation = { @@ -20,9 +20,10 @@ export type Presentation = { export class RecordingApi { private static get NULL_PRESENTATION(): Presentation { - return { movements: null, meta: {}, totalTime: -1, } + return { movements: null, meta: {}, totalTime: -1,} } +<<<<<<< HEAD // instance variables private currentPresentation: Presentation; private tracking: boolean; @@ -49,51 +50,77 @@ export class RecordingApi { this.playFFView = null; this.timers = null; } +======= + // instance variables + private currentPresentation: Presentation; + private tracking: boolean; + private absoluteStart: number; + + + // create static instance and getter for global use + @observable static _instance: RecordingApi; + public static get Instance(): RecordingApi { return RecordingApi._instance } + public constructor() { + // init the global instance + RecordingApi._instance = this; + + // init the instance variables + this.currentPresentation = RecordingApi.NULL_PRESENTATION + this.tracking = false; + this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; + this.recordingFFView = null; + + // for now, set playFFView + this.playFFView = null; + this.timers = null; + + // put a pointerdown event on the doucment to see what the target + } +>>>>>>> parent of d91ff1f93 (fix bugs with selecting tracking. very stable now.) - // little helper :) - private get nullPresentation(): boolean { - return this.currentPresentation.movements === null - } + // little helper :) + private get isInitPresenation(): boolean { + return this.currentPresentation.movements === null + } - public start = (meta?: Object) => { - // update the presentation mode - Doc.UserDoc().presentationMode = 'recording'; - - // (1a) get start date for presenation - const startDate = new Date(); - // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime(); - - // (2) assign meta content if it exists - this.currentPresentation.meta = meta || {} - // (3) assign start date to currentPresenation - this.currentPresentation.movements = [] - // (4) set tracking true to allow trackMovements - this.tracking = true - } + public start = (meta?: Object) => { + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording'; + + // (1a) get start date for presenation + const startDate = new Date(); + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime(); + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {} + // (3) assign start date to currentPresenation + this.currentPresentation.movements = [] + // (4) set tracking true to allow trackMovements + this.tracking = true + } - /* stops the video and returns the presentatation; if no presentation, returns undefined */ - public yieldPresentation(clearData: boolean = true): Presentation | null { + /* stops the video and returns the presentatation; if no presentation, returns undefined */ + public * yieldPresentation(clearData: boolean = true): Generator { // if no presentation or done tracking, return null - if (this.nullPresentation || !this.tracking) return null; + if (!this.isInitPresenation || !this.tracking) return null; // set the previus recording view to the play view this.playFFView = this.recordingFFView; // ensure we add the endTime now that they are done recording - const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; + yield { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; // reset the current presentation clearData && this.clear(); - - return cpy; } - public finish = (): void => { + public stop = (): void => { // make is tracking false this.tracking = false - // reset the RecordingApi instance - this.clear(); } public clear = (): void => { @@ -110,138 +137,155 @@ export class RecordingApi { // clear absoluteStart this.absoluteStart = -1 } - - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; - } - - private trackMovements = (panX: number, panY: number, scale: number = 0) => { - // ensure we are recording - if (!this.tracking) { - console.error('[recordingApi.ts] trackMovements(): tracking is false') - return; - } - // check to see if the presetation is init - if (this.nullPresentation) { - console.error('[recordingApi.ts] trackMovements(): no presentation') - return; + + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; } - // TO FIX: bob - // console.debug('track movment') - - // get the time - const time = new Date().getTime() - this.absoluteStart - // make new movement object - const movement: Movement = { time, panX, panY, scale } - - // add that movement to the current presentation data's movement array - this.currentPresentation.movements && this.currentPresentation.movements.push(movement) - } - - // instance variable for the FFView - private disposeFunc: IReactionDisposer | null; - private recordingFFView: CollectionFreeFormView | null; - - // set the FFView that will be used in a reaction to track the movements - public setRecordingFFView = (view: CollectionFreeFormView): void => { - // set the view to the current view - if (view === this.recordingFFView || view == null) return; - - // this.recordingFFView = view; - // set the reaction to track the movements - this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), - (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) - ) - - // for now, set the most recent recordingFFView to the playFFView - this.recordingFFView = view; - } - - // TODO: extract this into different class with pause and resume recording - // TODO: store the FFview with the movements - private playFFView: CollectionFreeFormView | null; - private timers: NodeJS.Timeout[] | null; - - public setPlayFFView = (view: CollectionFreeFormView): void => { - this.playFFView = view - } - - // pausing movements will dispose all timers that are planned to replay the movements - // play movemvents will recreate them when the user resumes the presentation - public pauseMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] pauseMovements() failed: no view') + // public pause = (): Error | undefined => { + // if (this.isInitPresenation) { + // console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') + // return new Error('[recordingApi.ts] pause(): no presentation') + // } + // // don't allow track movments + // this.tracking = false + + // // set adjust absoluteStart to add the time difference + // const timestamp = new Date().getTime() + // this.absoluteStart = timestamp - this.absoluteStart + // } + + // public resume = () => { + // this.tracking = true + // // set absoluteStart to the difference in time + // this.absoluteStart = new Date().getTime() - this.absoluteStart + // } + + private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { + // ensure we are recording + if (!this.tracking) { + return new Error('[recordingApi.ts] trackMovements()') + } + // check to see if the presetation is init + if (this.isInitPresenation) { + return new Error('[recordingApi.ts] trackMovements(): no presentation') + } + + // TO FIX: bob + // console.debug('track movment') + + // get the time + const time = new Date().getTime() - this.absoluteStart + // make new movement object + const movement: Movement = { time, panX, panY, scale } + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements && this.currentPresentation.movements.push(movement) } - if (!this._isPlaying) { - //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') - return + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === this.recordingFFView || view == null) return; + + // this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), + (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) + ) + + // for now, set the most recent recordingFFView to the playFFView + this.recordingFFView = view; } - this._isPlaying = false - // TODO: set userdoc presentMode to browsing - this.timers?.map(timer => clearTimeout(timer)) - - // this.videoBox = null; - } - - private videoBox: VideoBox | null = null; - - // by calling pause on the VideoBox, the pauseMovements will be called - public pauseVideoAndMovements = (): boolean => { - this.videoBox?.Pause() - - this.pauseMovements() - return this.videoBox == null - } - public _isPlaying = false; + // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements + private playFFView: CollectionFreeFormView | null; + private timers: NodeJS.Timeout[] | null; - public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { - if (presentation.movements === null || this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view } - if (this._isPlaying) return; - this._isPlaying = true; - Doc.UserDoc().presentationMode = 'watching'; - - // TODO: consider this bug at the end of the clip on seek - this.videoBox = videoBox || null; - - // only get the movements that are remaining in the video time left - const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) + // pausing movements will dispose all timers that are planned to replay the movements + // play movemvents will recreate them when the user resumes the presentation + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view') + } + + if (!this._isPlaying) { + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return + } + this._isPlaying = false + // TODO: set userdoc presentMode to browsing + this.timers?.map(timer => clearTimeout(timer)) + + // this.videoBox = null; + } + + private videoBox: VideoBox | null = null; - // helper to replay a movement - const document = this.playFFView - let preScale = -1; - const zoomAndPan = (movement: Movement) => { - const { panX, panY, scale } = movement; - (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); - document.Document._panX = panX; - document.Document._panY = panY; + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause() - preScale = scale; + this.pauseMovements() + return this.videoBox == null } - // set the first frame to be at the start of the pres - zoomAndPan(filteredMovements[0]); - - // make timers that will execute each movement at the correct replay time - this.timers = filteredMovements.map(movement => { - const timeDiff = movement.time - timeViewed * 1000 - return setTimeout(() => { - // replay the movement - zoomAndPan(movement) - // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; - }, timeDiff) - }) - } - + public _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + } + if(this._isPlaying) return; + + this._isPlaying = true; + Doc.UserDoc().presentationMode = 'watching'; + + // TODO: consider this bug at the end of the clip on seek + this.videoBox = videoBox || null; + + // only get the movements that are remaining in the video time left + const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) + + // helper to replay a movement + const document = this.playFFView + let preScale = -1; + const zoomAndPan = (movement: Movement) => { + const { panX, panY, scale } = movement; + (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + preScale = scale; + } + + // set the first frame to be at the start of the pres + zoomAndPan(filteredMovements[0]); + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed*1000 + return setTimeout(() => { + // replay the movement + zoomAndPan(movement) + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + }, timeDiff) + }) + } + // method that concatenates an array of presentatations into one public concatPresentations = (presentations: Presentation[]): Presentation => { // these three will lead to the combined presentation @@ -251,7 +295,6 @@ export class RecordingApi { presentations.forEach((presentation) => { const { movements, totalTime, meta } = presentation; - // update movements if they had one if (movements) { // add the summed time to the movements @@ -259,10 +302,8 @@ export class RecordingApi { // concat the movements already in the combined presentation with these new ones combinedMovements.push(...addedTimeMovements); } - // update the totalTime sumTime += totalTime; - // concatenate the metas combinedMetas.push(...meta); }); @@ -271,19 +312,19 @@ export class RecordingApi { return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; } - // Unfinished code for tracing multiple free form views - // export let pres: Map = new Map() - - // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { - // pres.set(ffView, - // reaction(() => ({ x: ffView.panX, y: ffView.panY }), - // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) - // ) - // } - - // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { - // const disposer = pres.get(ffView); - // disposer?.(); - // pres.delete(ffView) - // } + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } } diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index a28677525..6fe67b6db 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -46,7 +46,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client); this.dataDoc[this.fieldKey + "-recorded"] = true; - // stringify the presentation and store it + // stringify the presenation and store it presentation?.movements && (this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(presentation)); } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index d4d19f3d8..ba9479f41 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -141,6 +141,7 @@ export function RecordingView(props: IRecordingViewProps) { } videoRecorder.current.onstop = () => { + // RecordingApi.Instance.stop(); // if we have a last portion if (videoChunks.length > 1) { // append the current portion to the video pieces @@ -149,10 +150,9 @@ export function RecordingView(props: IRecordingViewProps) { endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }; - // depending on if a presenation exists, add it to the video - const presentation = RecordingApi.Instance.yieldPresentation(); - setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); + const { done: presError, value: presentation } = RecordingApi.Instance.yieldPresentation().next(); + setVideos(videos => [...videos, (!presError && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); } // reset the temporary chunks @@ -175,8 +175,8 @@ export function RecordingView(props: IRecordingViewProps) { const stream = videoElementRef.current!.srcObject; stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); - // finish/clear the recoringApi - RecordingApi.Instance.finish(); + // clear the recoringApi + RecordingApi.Instance.clear(); // this will call upon progessbar to update videos to be in the correct order setFinished(true); @@ -189,10 +189,12 @@ export function RecordingView(props: IRecordingViewProps) { } const start = (e: React.PointerEvent) => { + // the code to start or resume does not get triggered if we start dragging the button setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { - // start recording if not already recording - if (!videoRecorder.current || videoRecorder.current.state === "inactive") record(); - + if (!videoRecorder.current || videoRecorder.current.state === "inactive") { + record(); + // trackScreen && + } return true; // cancels propagation to documentView to avoid selecting it. }, false, false); } -- cgit v1.2.3-70-g09d2 From c7e4bf8c4e19669666cce610020969cf0d865e49 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Mon, 13 Jun 2022 14:57:34 -0400 Subject: Revert "debug w bob" This reverts commit 1dfb694ffa8f34a834387eb073ffb9bd3a678039. return to stable version without printing the logs. --- src/client/util/RecordingApi.ts | 385 +++++++++------------ .../views/nodes/RecordingBox/RecordingBox.tsx | 2 +- .../views/nodes/RecordingBox/RecordingView.tsx | 29 +- 3 files changed, 186 insertions(+), 230 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 4c8f55ce5..d56093cee 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -5,10 +5,10 @@ import { Doc } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; type Movement = { - time: number, - panX: number, - panY: number, - scale: number, + time: number, + panX: number, + panY: number, + scale: number, } export type Presentation = { @@ -20,10 +20,9 @@ export type Presentation = { export class RecordingApi { private static get NULL_PRESENTATION(): Presentation { - return { movements: null, meta: {}, totalTime: -1,} + return { movements: null, meta: {}, totalTime: -1, } } -<<<<<<< HEAD // instance variables private currentPresentation: Presentation; private tracking: boolean; @@ -49,78 +48,54 @@ export class RecordingApi { // for now, set playFFView this.playFFView = null; this.timers = null; + + // put a pointerdown event on the doucment to see what the target } -======= - // instance variables - private currentPresentation: Presentation; - private tracking: boolean; - private absoluteStart: number; - - - // create static instance and getter for global use - @observable static _instance: RecordingApi; - public static get Instance(): RecordingApi { return RecordingApi._instance } - public constructor() { - // init the global instance - RecordingApi._instance = this; - - // init the instance variables - this.currentPresentation = RecordingApi.NULL_PRESENTATION - this.tracking = false; - this.absoluteStart = -1; - - // used for tracking movements in the view frame - this.disposeFunc = null; - this.recordingFFView = null; - - // for now, set playFFView - this.playFFView = null; - this.timers = null; - - // put a pointerdown event on the doucment to see what the target - } ->>>>>>> parent of d91ff1f93 (fix bugs with selecting tracking. very stable now.) - // little helper :) - private get isInitPresenation(): boolean { - return this.currentPresentation.movements === null - } + // little helper :) + private get nullPresentation(): boolean { + return this.currentPresentation.movements === null + } - public start = (meta?: Object) => { - // update the presentation mode - Doc.UserDoc().presentationMode = 'recording'; - - // (1a) get start date for presenation - const startDate = new Date(); - // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime(); - - // (2) assign meta content if it exists - this.currentPresentation.meta = meta || {} - // (3) assign start date to currentPresenation - this.currentPresentation.movements = [] - // (4) set tracking true to allow trackMovements - this.tracking = true - } + public start = (meta?: Object) => { + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording'; + + // (1a) get start date for presenation + const startDate = new Date(); + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime(); + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {} + // (3) assign start date to currentPresenation + this.currentPresentation.movements = [] + // (4) set tracking true to allow trackMovements + this.tracking = true + } - /* stops the video and returns the presentatation; if no presentation, returns undefined */ - public * yieldPresentation(clearData: boolean = true): Generator { + /* stops the video and returns the presentatation; if no presentation, returns undefined */ + public yieldPresentation(clearData: boolean = true): Presentation | null { // if no presentation or done tracking, return null - if (!this.isInitPresenation || !this.tracking) return null; + if (this.nullPresentation || !this.tracking) return null; // set the previus recording view to the play view this.playFFView = this.recordingFFView; // ensure we add the endTime now that they are done recording - yield { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; + const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; // reset the current presentation clearData && this.clear(); + + return cpy; } - public stop = (): void => { + public finish = (): void => { // make is tracking false this.tracking = false + // reset the RecordingApi instance + this.clear(); } public clear = (): void => { @@ -137,155 +112,138 @@ export class RecordingApi { // clear absoluteStart this.absoluteStart = -1 } - - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; - } - // public pause = (): Error | undefined => { - // if (this.isInitPresenation) { - // console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first') - // return new Error('[recordingApi.ts] pause(): no presentation') - // } - // // don't allow track movments - // this.tracking = false - - // // set adjust absoluteStart to add the time difference - // const timestamp = new Date().getTime() - // this.absoluteStart = timestamp - this.absoluteStart - // } - - // public resume = () => { - // this.tracking = true - // // set absoluteStart to the difference in time - // this.absoluteStart = new Date().getTime() - this.absoluteStart - // } - - private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { - // ensure we are recording - if (!this.tracking) { - return new Error('[recordingApi.ts] trackMovements()') - } - // check to see if the presetation is init - if (this.isInitPresenation) { - return new Error('[recordingApi.ts] trackMovements(): no presentation') - } - - // TO FIX: bob - // console.debug('track movment') - - // get the time - const time = new Date().getTime() - this.absoluteStart - // make new movement object - const movement: Movement = { time, panX, panY, scale } - - // add that movement to the current presentation data's movement array - this.currentPresentation.movements && this.currentPresentation.movements.push(movement) - } + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; + } - // instance variable for the FFView - private disposeFunc: IReactionDisposer | null; - private recordingFFView: CollectionFreeFormView | null; - - // set the FFView that will be used in a reaction to track the movements - public setRecordingFFView = (view: CollectionFreeFormView): void => { - // set the view to the current view - if (view === this.recordingFFView || view == null) return; - - // this.recordingFFView = view; - // set the reaction to track the movements - this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), - (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) - ) - - // for now, set the most recent recordingFFView to the playFFView - this.recordingFFView = view; + private trackMovements = (panX: number, panY: number, scale: number = 0) => { + // ensure we are recording + if (!this.tracking) { + console.error('[recordingApi.ts] trackMovements(): tracking is false') + return; + } + // check to see if the presetation is init + if (this.nullPresentation) { + console.error('[recordingApi.ts] trackMovements(): no presentation') + return; } - // TODO: extract this into different class with pause and resume recording - // TODO: store the FFview with the movements - private playFFView: CollectionFreeFormView | null; - private timers: NodeJS.Timeout[] | null; + // TO FIX: bob + // console.debug('track movment') + + // get the time + const time = new Date().getTime() - this.absoluteStart + // make new movement object + const movement: Movement = { time, panX, panY, scale } + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements && this.currentPresentation.movements.push(movement) + } + + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === this.recordingFFView || view == null) return; + + // this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), + (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) + ) + + // for now, set the most recent recordingFFView to the playFFView + this.recordingFFView = view; + } + + // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements + private playFFView: CollectionFreeFormView | null; + private timers: NodeJS.Timeout[] | null; - public setPlayFFView = (view: CollectionFreeFormView): void => { - this.playFFView = view + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view + } + + // pausing movements will dispose all timers that are planned to replay the movements + // play movemvents will recreate them when the user resumes the presentation + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view') } - // pausing movements will dispose all timers that are planned to replay the movements - // play movemvents will recreate them when the user resumes the presentation - public pauseMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] pauseMovements() failed: no view') - } - - if (!this._isPlaying) { - //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') - return - } - this._isPlaying = false - // TODO: set userdoc presentMode to browsing - this.timers?.map(timer => clearTimeout(timer)) - - // this.videoBox = null; + if (!this._isPlaying) { + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return } - - private videoBox: VideoBox | null = null; + this._isPlaying = false + // TODO: set userdoc presentMode to browsing + this.timers?.map(timer => clearTimeout(timer)) + + // this.videoBox = null; + } + + private videoBox: VideoBox | null = null; + + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause() + + this.pauseMovements() + return this.videoBox == null + } - // by calling pause on the VideoBox, the pauseMovements will be called - public pauseVideoAndMovements = (): boolean => { - this.videoBox?.Pause() + public _isPlaying = false; - this.pauseMovements() - return this.videoBox == null + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } + if (this._isPlaying) return; - public _isPlaying = false; - - public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { - if (presentation.movements === null || this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') - } - if(this._isPlaying) return; - - this._isPlaying = true; - Doc.UserDoc().presentationMode = 'watching'; - - // TODO: consider this bug at the end of the clip on seek - this.videoBox = videoBox || null; - - // only get the movements that are remaining in the video time left - const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) - - // helper to replay a movement - const document = this.playFFView - let preScale = -1; - const zoomAndPan = (movement: Movement) => { - const { panX, panY, scale } = movement; - (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); - document.Document._panX = panX; - document.Document._panY = panY; - - preScale = scale; - } - - // set the first frame to be at the start of the pres - zoomAndPan(filteredMovements[0]); - - // make timers that will execute each movement at the correct replay time - this.timers = filteredMovements.map(movement => { - const timeDiff = movement.time - timeViewed*1000 - return setTimeout(() => { - // replay the movement - zoomAndPan(movement) - // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; - }, timeDiff) - }) + this._isPlaying = true; + Doc.UserDoc().presentationMode = 'watching'; + + // TODO: consider this bug at the end of the clip on seek + this.videoBox = videoBox || null; + + // only get the movements that are remaining in the video time left + const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) + + // helper to replay a movement + const document = this.playFFView + let preScale = -1; + const zoomAndPan = (movement: Movement) => { + const { panX, panY, scale } = movement; + (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + preScale = scale; } - + + // set the first frame to be at the start of the pres + zoomAndPan(filteredMovements[0]); + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed * 1000 + return setTimeout(() => { + // replay the movement + zoomAndPan(movement) + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + }, timeDiff) + }) + } + // method that concatenates an array of presentatations into one public concatPresentations = (presentations: Presentation[]): Presentation => { // these three will lead to the combined presentation @@ -295,6 +253,7 @@ export class RecordingApi { presentations.forEach((presentation) => { const { movements, totalTime, meta } = presentation; + // update movements if they had one if (movements) { // add the summed time to the movements @@ -302,8 +261,10 @@ export class RecordingApi { // concat the movements already in the combined presentation with these new ones combinedMovements.push(...addedTimeMovements); } + // update the totalTime sumTime += totalTime; + // concatenate the metas combinedMetas.push(...meta); }); @@ -312,19 +273,19 @@ export class RecordingApi { return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; } - // Unfinished code for tracing multiple free form views - // export let pres: Map = new Map() - - // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { - // pres.set(ffView, - // reaction(() => ({ x: ffView.panX, y: ffView.panY }), - // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) - // ) - // } - - // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { - // const disposer = pres.get(ffView); - // disposer?.(); - // pres.delete(ffView) - // } + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } } diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 6fe67b6db..a28677525 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -46,7 +46,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client); this.dataDoc[this.fieldKey + "-recorded"] = true; - // stringify the presenation and store it + // stringify the presentation and store it presentation?.movements && (this.dataDoc[this.fieldKey + "-presentation"] = JSON.stringify(presentation)); } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index ba9479f41..15883632a 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -60,7 +60,7 @@ export function RecordingView(props: IRecordingViewProps) { } } - useEffect(() => console.log('progress', progress), [progress]) + // useEffect(() => console.debug('progress', progress), [progress]) useEffect(() => { if (finished) { @@ -93,8 +93,7 @@ export function RecordingView(props: IRecordingViewProps) { useEffect(() => { let interval: any = null; if (recording) { - interval = setTimeout(() => { - console.log('update interval') + interval = setInterval(() => { setRecordingTimer(unit => unit + 1); }, 10); } else if (!recording && recordingTimer !== 0) { @@ -141,7 +140,6 @@ export function RecordingView(props: IRecordingViewProps) { } videoRecorder.current.onstop = () => { - // RecordingApi.Instance.stop(); // if we have a last portion if (videoChunks.length > 1) { // append the current portion to the video pieces @@ -150,9 +148,10 @@ export function RecordingView(props: IRecordingViewProps) { endTime: recordingTimerRef.current, startTime: videos?.lastElement()?.endTime || 0 }; + // depending on if a presenation exists, add it to the video - const { done: presError, value: presentation } = RecordingApi.Instance.yieldPresentation().next(); - setVideos(videos => [...videos, (!presError && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); + const presentation = RecordingApi.Instance.yieldPresentation(); + setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); } // reset the temporary chunks @@ -175,8 +174,8 @@ export function RecordingView(props: IRecordingViewProps) { const stream = videoElementRef.current!.srcObject; stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); - // clear the recoringApi - RecordingApi.Instance.clear(); + // finish/clear the recoringApi + RecordingApi.Instance.finish(); // this will call upon progessbar to update videos to be in the correct order setFinished(true); @@ -189,12 +188,10 @@ export function RecordingView(props: IRecordingViewProps) { } const start = (e: React.PointerEvent) => { - // the code to start or resume does not get triggered if we start dragging the button setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { - if (!videoRecorder.current || videoRecorder.current.state === "inactive") { - record(); - // trackScreen && - } + // start recording if not already recording + if (!videoRecorder.current || videoRecorder.current.state === "inactive") record(); + return true; // cancels propagation to documentView to avoid selecting it. }, false, false); } @@ -204,9 +201,7 @@ export function RecordingView(props: IRecordingViewProps) { setDoUndo(prev => !prev); } - const handleOnTimeUpdate = () => { - playing && setVideoProgressHelper(videoElementRef.current!.currentTime); - }; + const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); }; const millisecondToMinuteSecond = (milliseconds: number) => { const toTwoDigit = (digit: number) => { @@ -223,7 +218,7 @@ export function RecordingView(props: IRecordingViewProps) {
    -- cgit v1.2.3-70-g09d2 From d3bf3086d95ff6f895614d0942b84752662c1373 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Mon, 13 Jun 2022 15:10:22 -0400 Subject: formatting and removing logs --- src/client/util/RecordingApi.ts | 506 ++++++++++----------- .../views/nodes/RecordingBox/RecordingView.tsx | 443 +++++++++--------- 2 files changed, 472 insertions(+), 477 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index d56093cee..12ca07d67 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -3,289 +3,285 @@ import { IReactionDisposer, observable, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; import { Doc } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; +import { isArray } from "lodash"; type Movement = { - time: number, - panX: number, - panY: number, - scale: number, + time: number, + panX: number, + panY: number, + scale: number, } export type Presentation = { - movements: Movement[] | null, - totalTime: number, - meta: any, + movements: Movement[] | null, + totalTime: number, + meta: Object | Object[], } export class RecordingApi { - private static get NULL_PRESENTATION(): Presentation { - return { movements: null, meta: {}, totalTime: -1, } - } - - // instance variables - private currentPresentation: Presentation; - private tracking: boolean; - private absoluteStart: number; - - - // create static instance and getter for global use - @observable static _instance: RecordingApi; - public static get Instance(): RecordingApi { return RecordingApi._instance } - public constructor() { - // init the global instance - RecordingApi._instance = this; - - // init the instance variables - this.currentPresentation = RecordingApi.NULL_PRESENTATION - this.tracking = false; - this.absoluteStart = -1; - - // used for tracking movements in the view frame - this.disposeFunc = null; - this.recordingFFView = null; - - // for now, set playFFView - this.playFFView = null; - this.timers = null; - - // put a pointerdown event on the doucment to see what the target - } - - // little helper :) - private get nullPresentation(): boolean { - return this.currentPresentation.movements === null - } - - public start = (meta?: Object) => { - // update the presentation mode - Doc.UserDoc().presentationMode = 'recording'; - - // (1a) get start date for presenation - const startDate = new Date(); - // (1b) set start timestamp to absolute timestamp - this.absoluteStart = startDate.getTime(); - - // (2) assign meta content if it exists - this.currentPresentation.meta = meta || {} - // (3) assign start date to currentPresenation - this.currentPresentation.movements = [] - // (4) set tracking true to allow trackMovements - this.tracking = true - } - - /* stops the video and returns the presentatation; if no presentation, returns undefined */ - public yieldPresentation(clearData: boolean = true): Presentation | null { - // if no presentation or done tracking, return null - if (this.nullPresentation || !this.tracking) return null; - - // set the previus recording view to the play view - this.playFFView = this.recordingFFView; - - // ensure we add the endTime now that they are done recording - const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; - - // reset the current presentation - clearData && this.clear(); - - return cpy; - } - - public finish = (): void => { - // make is tracking false - this.tracking = false - // reset the RecordingApi instance - this.clear(); - } - - public clear = (): void => { - // clear the disposeFunc if we are done (not tracking) - if (!this.tracking) { - this.removeRecordingFFView(); - // update the presentation mode now that we are done tracking - Doc.UserDoc().presentationMode = 'none'; + private static get NULL_PRESENTATION(): Presentation { + return { movements: null, meta: {}, totalTime: -1, } } - // clear presenation data - this.currentPresentation = RecordingApi.NULL_PRESENTATION - // clear isRecording - // this.tracking = false - // clear absoluteStart - this.absoluteStart = -1 - } - - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; - } - - private trackMovements = (panX: number, panY: number, scale: number = 0) => { - // ensure we are recording - if (!this.tracking) { - console.error('[recordingApi.ts] trackMovements(): tracking is false') - return; + + // instance variables + private currentPresentation: Presentation; + private tracking: boolean; + private absoluteStart: number; + + + // create static instance and getter for global use + @observable static _instance: RecordingApi; + public static get Instance(): RecordingApi { return RecordingApi._instance } + public constructor() { + // init the global instance + RecordingApi._instance = this; + + // init the instance variables + this.currentPresentation = RecordingApi.NULL_PRESENTATION + this.tracking = false; + this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; + this.recordingFFView = null; + + // for now, set playFFView + this.playFFView = null; + this.timers = null; + } + + // little helper :) + private get nullPresentation(): boolean { + return this.currentPresentation.movements === null + } + + public start = (meta?: Object) => { + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording'; + + // (1a) get start date for presenation + const startDate = new Date(); + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime(); + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {} + // (3) assign start date to currentPresenation + this.currentPresentation.movements = [] + // (4) set tracking true to allow trackMovements + this.tracking = true + } + + /* stops the video and returns the presentatation; if no presentation, returns undefined */ + public yieldPresentation(clearData: boolean = true): Presentation | null { + // if no presentation or done tracking, return null + if (this.nullPresentation || !this.tracking) return null; + + // set the previus recording view to the play view + this.playFFView = this.recordingFFView; + + // ensure we add the endTime now that they are done recording + const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; + + // reset the current presentation + clearData && this.clear(); + + return cpy; + } + + public finish = (): void => { + // make is tracking false + this.tracking = false + // reset the RecordingApi instance + this.clear(); } - // check to see if the presetation is init - if (this.nullPresentation) { - console.error('[recordingApi.ts] trackMovements(): no presentation') - return; + + public clear = (): void => { + // clear the disposeFunc if we are done (not tracking) + if (!this.tracking) { + this.removeRecordingFFView(); + // update the presentation mode now that we are done tracking + Doc.UserDoc().presentationMode = 'none'; + } + // clear presenation data + this.currentPresentation = RecordingApi.NULL_PRESENTATION + // clear isRecording + // this.tracking = false + // clear absoluteStart + this.absoluteStart = -1 } - // TO FIX: bob - // console.debug('track movment') - - // get the time - const time = new Date().getTime() - this.absoluteStart - // make new movement object - const movement: Movement = { time, panX, panY, scale } - - // add that movement to the current presentation data's movement array - this.currentPresentation.movements && this.currentPresentation.movements.push(movement) - } - - // instance variable for the FFView - private disposeFunc: IReactionDisposer | null; - private recordingFFView: CollectionFreeFormView | null; - - // set the FFView that will be used in a reaction to track the movements - public setRecordingFFView = (view: CollectionFreeFormView): void => { - // set the view to the current view - if (view === this.recordingFFView || view == null) return; - - // this.recordingFFView = view; - // set the reaction to track the movements - this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), - (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) - ) - - // for now, set the most recent recordingFFView to the playFFView - this.recordingFFView = view; - } - - // TODO: extract this into different class with pause and resume recording - // TODO: store the FFview with the movements - private playFFView: CollectionFreeFormView | null; - private timers: NodeJS.Timeout[] | null; - - public setPlayFFView = (view: CollectionFreeFormView): void => { - this.playFFView = view - } - - // pausing movements will dispose all timers that are planned to replay the movements - // play movemvents will recreate them when the user resumes the presentation - public pauseMovements = (): undefined | Error => { - if (this.playFFView === null) { - return new Error('[recordingApi.ts] pauseMovements() failed: no view') + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; } - if (!this._isPlaying) { - //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') - return + private trackMovements = (panX: number, panY: number, scale: number = 0) => { + // ensure we are recording + if (!this.tracking) { + console.error('[recordingApi.ts] trackMovements(): tracking is false') + return; + } + // check to see if the presetation is init + if (this.nullPresentation) { + console.error('[recordingApi.ts] trackMovements(): no presentation') + return; + } + + // get the time + const time = new Date().getTime() - this.absoluteStart + // make new movement object + const movement: Movement = { time, panX, panY, scale } + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements && this.currentPresentation.movements.push(movement) } - this._isPlaying = false - // TODO: set userdoc presentMode to browsing - this.timers?.map(timer => clearTimeout(timer)) - // this.videoBox = null; - } + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === this.recordingFFView || view == null) return; - private videoBox: VideoBox | null = null; + // this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), + (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) + ) - // by calling pause on the VideoBox, the pauseMovements will be called - public pauseVideoAndMovements = (): boolean => { - this.videoBox?.Pause() + // for now, set the most recent recordingFFView to the playFFView + this.recordingFFView = view; + } - this.pauseMovements() - return this.videoBox == null - } + // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements + private playFFView: CollectionFreeFormView | null; + private timers: NodeJS.Timeout[] | null; - public _isPlaying = false; + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view + } - public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { - if (presentation.movements === null || this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + // pausing movements will dispose all timers that are planned to replay the movements + // play movemvents will recreate them when the user resumes the presentation + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view') + } + + if (!this._isPlaying) { + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return + } + this._isPlaying = false + // TODO: set userdoc presentMode to browsing + this.timers?.map(timer => clearTimeout(timer)) + + // this.videoBox = null; } - if (this._isPlaying) return; - this._isPlaying = true; - Doc.UserDoc().presentationMode = 'watching'; + private videoBox: VideoBox | null = null; - // TODO: consider this bug at the end of the clip on seek - this.videoBox = videoBox || null; + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause() - // only get the movements that are remaining in the video time left - const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) + this.pauseMovements() + return this.videoBox == null + } - // helper to replay a movement - const document = this.playFFView - let preScale = -1; - const zoomAndPan = (movement: Movement) => { - const { panX, panY, scale } = movement; - (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); - document.Document._panX = panX; - document.Document._panY = panY; + public _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + } + if (this._isPlaying) return; + + this._isPlaying = true; + Doc.UserDoc().presentationMode = 'watching'; + + // TODO: consider this bug at the end of the clip on seek + this.videoBox = videoBox || null; + + // only get the movements that are remaining in the video time left + const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) + + // helper to replay a movement + const document = this.playFFView + let preScale = -1; + const zoomAndPan = (movement: Movement) => { + const { panX, panY, scale } = movement; + (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + preScale = scale; + } + + // set the first frame to be at the start of the pres + zoomAndPan(filteredMovements[0]); + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed * 1000 + return setTimeout(() => { + // replay the movement + zoomAndPan(movement) + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + }, timeDiff) + }) + } - preScale = scale; + // method that concatenates an array of presentatations into one + public concatPresentations = (presentations: Presentation[]): Presentation => { + // these three will lead to the combined presentation + let combinedMovements: Movement[] = []; + let sumTime = 0; + let combinedMetas: any[] = []; + + presentations.forEach((presentation) => { + const { movements, totalTime, meta } = presentation; + + // update movements if they had one + if (movements) { + // add the summed time to the movements + const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } }); + // concat the movements already in the combined presentation with these new ones + combinedMovements.push(...addedTimeMovements); + } + + // update the totalTime + sumTime += totalTime; + + // concatenate the metas + combinedMetas.push(meta); + }); + + // return the combined presentation with the updated total summed time + return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; } - // set the first frame to be at the start of the pres - zoomAndPan(filteredMovements[0]); - - // make timers that will execute each movement at the correct replay time - this.timers = filteredMovements.map(movement => { - const timeDiff = movement.time - timeViewed * 1000 - return setTimeout(() => { - // replay the movement - zoomAndPan(movement) - // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; - }, timeDiff) - }) - } - - // method that concatenates an array of presentatations into one - public concatPresentations = (presentations: Presentation[]): Presentation => { - // these three will lead to the combined presentation - let combinedMovements: Movement[] = []; - let sumTime = 0; - let combinedMetas: any[] = []; - - presentations.forEach((presentation) => { - const { movements, totalTime, meta } = presentation; - - // update movements if they had one - if (movements) { - // add the summed time to the movements - const addedTimeMovements = movements.map(move => { return { ...move, time: move.time + sumTime } }); - // concat the movements already in the combined presentation with these new ones - combinedMovements.push(...addedTimeMovements); - } - - // update the totalTime - sumTime += totalTime; - - // concatenate the metas - combinedMetas.push(...meta); - }); - - // return the combined presentation with the updated total summed time - return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; - } - - // Unfinished code for tracing multiple free form views - // export let pres: Map = new Map() - - // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { - // pres.set(ffView, - // reaction(() => ({ x: ffView.panX, y: ffView.panY }), - // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) - // ) - // } - - // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { - // const disposer = pres.get(ffView); - // disposer?.(); - // pres.delete(ffView) - // } + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 15883632a..ec9838bdd 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -11,262 +11,261 @@ import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; import { Presentation, RecordingApi } from '../../../util/RecordingApi'; export interface MediaSegment { - videoChunks: any[], - endTime: number, - startTime: number, - presentation?: Presentation, + videoChunks: any[], + endTime: number, + startTime: number, + presentation?: Presentation, } interface IRecordingViewProps { - setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void - setDuration: (seconds: number) => void - id: string + setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void + setDuration: (seconds: number) => void + id: string } const MAXTIME = 100000; export function RecordingView(props: IRecordingViewProps) { - const [recording, setRecording] = useState(false); - const recordingTimerRef = useRef(0); - const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second - const [playing, setPlaying] = useState(false); - const [progress, setProgress] = useState(0); - - // acts as a "refresh state" to tell progressBar when to undo - const [doUndo, setDoUndo] = useState(false); - // whether an undo can occur or not - const [canUndo, setCanUndo] = useState(false); - - const [videos, setVideos] = useState([]); - const [orderVideos, setOrderVideos] = useState(false); - const videoRecorder = useRef(null); - const videoElementRef = useRef(null); - - const [finished, setFinished] = useState(false) - const [trackScreen, setTrackScreen] = useState(true) + const [recording, setRecording] = useState(false); + const recordingTimerRef = useRef(0); + const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second + const [playing, setPlaying] = useState(false); + const [progress, setProgress] = useState(0); + + // acts as a "refresh state" to tell progressBar when to undo + const [doUndo, setDoUndo] = useState(false); + // whether an undo can occur or not + const [canUndo, setCanUndo] = useState(false); + + const [videos, setVideos] = useState([]); + const [orderVideos, setOrderVideos] = useState(false); + const videoRecorder = useRef(null); + const videoElementRef = useRef(null); + + const [finished, setFinished] = useState(false) + const [trackScreen, setTrackScreen] = useState(true) + + + + const DEFAULT_MEDIA_CONSTRAINTS = { + video: { + width: 1280, + height: 720, + + }, + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 44100 + } + }; + + useEffect(() => { + if (finished) { + // make the total presentation that'll match the concatted video + let concatPres = trackScreen && RecordingApi.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); + + // this async function uses the server to create the concatted video and then sets the result to it's accessPaths + (async () => { + const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); + + // upload the segments to the server and get their server access paths + const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles)) + .map(res => (res.result instanceof Error) ? '' : res.result.accessPaths.agnostic.server) + + // concat the segments together using post call + const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths); + !(result instanceof Error) ? props.setResult(result, concatPres || undefined) : console.error("video conversion failed"); + })(); + } + }, [videos]); + + // this will call upon the progress bar to edit videos to be in the correct order + useEffect(() => { + finished && setOrderVideos(true); + }, [finished]); + + // check if the browser supports media devices on first load + useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, []); + + useEffect(() => { + let interval: any = null; + if (recording) { + interval = setInterval(() => { + setRecordingTimer(unit => unit + 1); + }, 10); + } else if (!recording && recordingTimer !== 0) { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [recording]); + + useEffect(() => { + setVideoProgressHelper(recordingTimer) + recordingTimerRef.current = recordingTimer; + }, [recordingTimer]); + + const setVideoProgressHelper = (progress: number) => { + const newProgress = (progress / MAXTIME) * 100; + setProgress(newProgress); + } + const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => { + const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints); + videoElementRef.current!.src = ""; + videoElementRef.current!.srcObject = stream; + videoElementRef.current!.muted = true; - const DEFAULT_MEDIA_CONSTRAINTS = { - video: { - width: 1280, - height: 720, - }, - audio: { - echoCancellation: true, - noiseSuppression: true, - sampleRate: 44100 + return stream; } - } - // useEffect(() => console.debug('progress', progress), [progress]) + const record = async () => { + // don't need to start a new stream every time we start recording a new segment + if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream()); - useEffect(() => { - if (finished) { - // make the total presentation that'll match the concatted video - let concatPres = trackScreen && RecordingApi.Instance.concatPresentations(videos.map(v => v.presentation as Presentation)); + // temporary chunks of video + let videoChunks: any = []; - // this async function uses the server to create the concatted video and then sets the result to it's accessPaths - (async () => { - const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); + videoRecorder.current.ondataavailable = (event: any) => { + if (event.data.size > 0) videoChunks.push(event.data); + }; - // upload the segments to the server and get their server access paths - const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles)) - .map(res => (res.result instanceof Error) ? '' : res.result.accessPaths.agnostic.server) + videoRecorder.current.onstart = (event: any) => { + setRecording(true); + // start the recording api when the video recorder starts + trackScreen && RecordingApi.Instance.start(); + }; - // concat the segments together using post call - const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths); - !(result instanceof Error) ? props.setResult(result, concatPres || undefined) : console.error("video conversion failed"); - })(); + videoRecorder.current.onstop = () => { + // if we have a last portion + if (videoChunks.length > 1) { + // append the current portion to the video pieces + const nextVideo = { + videoChunks, + endTime: recordingTimerRef.current, + startTime: videos?.lastElement()?.endTime || 0 + }; + + // depending on if a presenation exists, add it to the video + const presentation = RecordingApi.Instance.yieldPresentation(); + setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); + } + + // reset the temporary chunks + videoChunks = []; + setRecording(false); + } + + videoRecorder.current.start(200); } - }, [videos]) - - // this will call upon the progress bar to edit videos to be in the correct order - useEffect(() => { - finished && setOrderVideos(true); - }, [finished]) - - // check if the browser supports media devices on first load - useEffect(() => { if (!navigator.mediaDevices) alert('This browser does not support getUserMedia.'); }, []) - - useEffect(() => { - let interval: any = null; - if (recording) { - interval = setInterval(() => { - setRecordingTimer(unit => unit + 1); - }, 10); - } else if (!recording && recordingTimer !== 0) { - clearInterval(interval); - } - return () => clearInterval(interval); - }, [recording]) - - useEffect(() => { - setVideoProgressHelper(recordingTimer) - recordingTimerRef.current = recordingTimer; - }, [recordingTimer]) - - const setVideoProgressHelper = (progress: number) => { - const newProgress = (progress / MAXTIME) * 100; - setProgress(newProgress) - } - const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => { - const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints) - videoElementRef.current!.src = "" - videoElementRef.current!.srcObject = stream - videoElementRef.current!.muted = true + // if this is called, then we're done recording all the segments + const finish = (e: React.PointerEvent) => { + e.stopPropagation(); - return stream - } + // call stop on the video recorder if active + videoRecorder.current?.state !== "inactive" && videoRecorder.current?.stop(); - const record = async () => { - // don't need to start a new stream every time we start recording a new segment - if (!videoRecorder.current) videoRecorder.current = new MediaRecorder(await startShowingStream()) + // end the streams (audio/video) to remove recording icon + const stream = videoElementRef.current!.srcObject; + stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); - // temporary chunks of video - let videoChunks: any = [] + // finish/clear the recoringApi + RecordingApi.Instance.finish(); - videoRecorder.current.ondataavailable = (event: any) => { - if (event.data.size > 0) videoChunks.push(event.data) + // this will call upon progessbar to update videos to be in the correct order + setFinished(true); } - videoRecorder.current.onstart = (event: any) => { - setRecording(true); - // start the recording api when the video recorder starts - trackScreen && RecordingApi.Instance.start(); + const pause = (e: React.PointerEvent) => { + e.stopPropagation(); + // if recording, then this is just a new segment + videoRecorder.current?.state === "recording" && videoRecorder.current.stop(); } - videoRecorder.current.onstop = () => { - // if we have a last portion - if (videoChunks.length > 1) { - // append the current portion to the video pieces - const nextVideo = { - videoChunks, - endTime: recordingTimerRef.current, - startTime: videos?.lastElement()?.endTime || 0 - }; - - // depending on if a presenation exists, add it to the video - const presentation = RecordingApi.Instance.yieldPresentation(); - setVideos(videos => [...videos, (presentation != null && trackScreen) ? { ...nextVideo, presentation } : nextVideo]); - } + const start = (e: React.PointerEvent) => { + setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { + // start recording if not already recording + if (!videoRecorder.current || videoRecorder.current.state === "inactive") record(); - // reset the temporary chunks - videoChunks = [] - setRecording(false); + return true; // cancels propagation to documentView to avoid selecting it. + }, false, false); } - videoRecorder.current.start(200) - } - - - // if this is called, then we're done recording all the segments - const finish = (e: React.PointerEvent) => { - e.stopPropagation(); - - // call stop on the video recorder if active - videoRecorder.current?.state !== "inactive" && videoRecorder.current?.stop(); - - // end the streams (audio/video) to remove recording icon - const stream = videoElementRef.current!.srcObject; - stream instanceof MediaStream && stream.getTracks().forEach(track => track.stop()); - - // finish/clear the recoringApi - RecordingApi.Instance.finish(); - - // this will call upon progessbar to update videos to be in the correct order - setFinished(true); - } - - const pause = (e: React.PointerEvent) => { - e.stopPropagation() - // if recording, then this is just a new segment - videoRecorder.current?.state === "recording" && videoRecorder.current.stop(); - } - - const start = (e: React.PointerEvent) => { - setupMoveUpEvents({}, e, returnTrue, returnFalse, e => { - // start recording if not already recording - if (!videoRecorder.current || videoRecorder.current.state === "inactive") record(); - - return true; // cancels propagation to documentView to avoid selecting it. - }, false, false); - } - - const undoPrevious = (e: React.PointerEvent) => { - e.stopPropagation(); - setDoUndo(prev => !prev); - } + const undoPrevious = (e: React.PointerEvent) => { + e.stopPropagation(); + setDoUndo(prev => !prev); + } - const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); }; + const handleOnTimeUpdate = () => { playing && setVideoProgressHelper(videoElementRef.current!.currentTime); }; - const millisecondToMinuteSecond = (milliseconds: number) => { - const toTwoDigit = (digit: number) => { - return String(digit).length == 1 ? "0" + digit : digit + const millisecondToMinuteSecond = (milliseconds: number) => { + const toTwoDigit = (digit: number) => { + return String(digit).length == 1 ? "0" + digit : digit + } + const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); + return toTwoDigit(minutes) + " : " + toTwoDigit(seconds); } - const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); - return toTwoDigit(minutes) + " : " + toTwoDigit(seconds); - } - - return ( -
    -
    -
    -
    ) + return ( +
    +
    +
    +
    ) } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 526fb50ba814d91cf9c69d75cb5b95a91c2dd924 Mon Sep 17 00:00:00 2001 From: Jenny Yu Date: Mon, 13 Jun 2022 21:27:33 -0700 Subject: feat: pop up modal for naming dashboard --- src/client/util/CurrentUserUtils.ts | 9 +++-- src/client/views/DashboardView.scss | 2 ++ src/client/views/DashboardView.tsx | 71 ++++++++++++++++++++++++------------- 3 files changed, 55 insertions(+), 27 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 60bfc165b..aa7c2a39b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1222,7 +1222,8 @@ export class CurrentUserUtils { Doc.UserDoc().activeDashboard = undefined; } - public static createNewDashboard = async (userDoc: Doc, id?: string) => { + public static createNewDashboard = async (userDoc: Doc, id?: string, name?: string) => { + console.log(name) const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; const dashboardCount = DocListCast(dashboards.data).length + 1; @@ -1235,8 +1236,9 @@ export class CurrentUserUtils { _backgroundGridShow: true, title: `Untitled Tab 1`, }; + const title = name ? name : `Dashboard ${dashboardCount}` const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); + const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, "row"); freeformDoc.context = dashboardDoc; // switching the tabs from the datadoc to the regular doc @@ -1247,7 +1249,8 @@ export class CurrentUserUtils { userDoc.activePresentation = presentation; Doc.AddDocToList(dashboards, "data", dashboardDoc); - // CurrentUserUtils.openDashboard(userDoc, dashboardDoc); + // open this new dashboard + // Doc.UserDoc().activeDashboard = dashboardDoc; } public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss index 67587dd2b..06f331c13 100644 --- a/src/client/views/DashboardView.scss +++ b/src/client/views/DashboardView.scss @@ -2,6 +2,8 @@ padding: 50px; display: flex; flex-direction: row; + width: 100%; + position: absolute; .left-menu { display: flex; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 5aacec61f..fa8a98402 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,4 +1,4 @@ -import { action, observable } from "mobx"; +import { action, computed, observable } from "mobx"; import { extname } from 'path'; import { observer } from "mobx-react"; import * as React from 'react'; @@ -8,6 +8,7 @@ import { Cast, ImageCast, StrCast } from "../../fields/Types"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { UndoManager } from "../util/UndoManager"; import "./DashboardView.scss" +import { MainViewModal } from "./MainViewModal"; enum DashboardGroup { MyDashboards, SharedDashboards @@ -18,20 +19,17 @@ export class DashboardView extends React.Component { //TODO: delete dashboard, share dashboard, etc. - @observable - private selectedDashboardGroup = DashboardGroup.MyDashboards; + @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; + + @observable private newDashboardName: string | undefined = undefined; + @action abortCreateNewDashboard = () => { this.newDashboardName = undefined } + @action setNewDashboardName(name: string) { this.newDashboardName = name} @action selectDashboardGroup = (group: DashboardGroup) => { this.selectedDashboardGroup = group } - newDashboard = async () => { - const batch = UndoManager.StartBatch("new dash"); - await CurrentUserUtils.createNewDashboard(Doc.UserDoc()); - batch.end(); - } - clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => { if (e.detail === 2) { Doc.UserDoc().activeDashboard = dashboard; @@ -48,24 +46,49 @@ export class DashboardView extends React.Component { } } + createNewDashboard = async (name: string) => { + const batch = UndoManager.StartBatch("new dash"); + await CurrentUserUtils.createNewDashboard(Doc.UserDoc(), undefined, name); + batch.end(); + this.abortCreateNewDashboard(); + } + + @computed + get namingInterface() { + return
    + this.setNewDashboardName((e.target as any).value)} /> + + +
    ; + } + render() { - return
    -
    -
    New
    -
    this.selectDashboardGroup(DashboardGroup.MyDashboards) }>My Dashboards
    -
    this.selectDashboardGroup(DashboardGroup.SharedDashboards) }>Shared Dashboards
    -
    -
    - {this.getDashboards().map((dashboard) => { - const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; - return
    this.clickDashboard(e, dashboard)}> - -
    {StrCast(dashboard.title)}
    -
    + return <> +
    +
    +
    { this.setNewDashboardName("") }}>New
    +
    this.selectDashboardGroup(DashboardGroup.MyDashboards)}>My Dashboards
    +
    this.selectDashboardGroup(DashboardGroup.SharedDashboards)}>Shared Dashboards
    +
    +
    + {this.getDashboards().map((dashboard) => { + const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; + return
    this.clickDashboard(e, dashboard)}> + +
    {StrCast(dashboard.title)}
    +
    - })} + })} +
    -
    + ; + + } } -- cgit v1.2.3-70-g09d2 From fbdddaeba99f5dbffffa4668e53b27006b7895f6 Mon Sep 17 00:00:00 2001 From: Jenny Yu Date: Mon, 13 Jun 2022 22:09:38 -0700 Subject: feat: automatically open dashboard on create --- src/client/util/CurrentUserUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index aa7c2a39b..1538062bb 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1250,7 +1250,8 @@ export class CurrentUserUtils { Doc.AddDocToList(dashboards, "data", dashboardDoc); // open this new dashboard - // Doc.UserDoc().activeDashboard = dashboardDoc; + Doc.UserDoc().activeDashboard = dashboardDoc; + Doc.UserDoc().activePage = "dashboard"; } public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { -- cgit v1.2.3-70-g09d2 From da6283875ec7f59af602e14ed19fcab93533f377 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 14 Jun 2022 13:00:27 -0400 Subject: made shared with me doc count decrement when a document is viewed (double-clicked, dragged out) --- src/client/documents/Documents.ts | 4 ++-- src/client/util/CurrentUserUtils.ts | 12 ++++++---- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/button/FontIconBadge.tsx | 30 ++++++++++++------------- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/fields/Doc.ts | 1 + 6 files changed, 28 insertions(+), 23 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b290b2d58..d2937b83f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -198,7 +198,6 @@ export class DocumentOptions { hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; - watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List; hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) @@ -251,6 +250,7 @@ export class DocumentOptions { numBtnMax?: number; numBtnMin?: number; switchToggle?: boolean; + badgeValue?: ScriptField; //LINEAR VIEW linearViewIsExpanded?: boolean; // is linear view expanded @@ -294,7 +294,7 @@ export class DocumentOptions { treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically - + treeViewChildDoubleClick?: ScriptField; // // Action Button buttonMenu?: boolean; // whether a action button should be displayed buttonMenuDoc?: Doc; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index aa7c2a39b..bb78fe4e7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -23,6 +23,7 @@ import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; +import { DocumentView } from "../views/nodes/DocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; import { DragManager } from "./DragManager"; @@ -354,6 +355,7 @@ export class CurrentUserUtils { } static menuBtnDescriptions(doc: Doc) { + const badgeScript = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, @@ -361,7 +363,7 @@ export class CurrentUserUtils { { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, + { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue: badgeScript}, { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, ]; @@ -370,7 +372,7 @@ export class CurrentUserUtils { static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.menuStack === undefined) { await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) => + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => Docs.Create.FontIconDocument({ icon, btnType: ButtonType.MenuButton, @@ -387,7 +389,7 @@ export class CurrentUserUtils { _removeDropProperties: new List(["dropAction", "_stayInCollection"]), _width: 60, _height: 60, - watchedDocuments, + badgeValue, onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) }) ); @@ -918,11 +920,12 @@ export class CurrentUserUtils { // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document if (doc.mySharedDocs === undefined) { let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); + const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) if (!sharedDocs) { sharedDocs = Docs.Create.TreeDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - _chromeHidden: true, boxShadow: "0 0", + _chromeHidden: true, boxShadow: "0 0", treeViewChildDoubleClick: dblClkScript, dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" }, sharingDocumentId + "outer", sharingDocumentId); (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; @@ -1069,6 +1072,7 @@ export class CurrentUserUtils { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } + return doc; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8824750a3..59dc5671b 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -613,7 +613,7 @@ export class TreeView extends React.Component { return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } - onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null)); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx index cf86b5e07..3b5aac221 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -7,30 +7,30 @@ import { DragManager } from "../../../util/DragManager"; import "./FontIconBadge.scss"; interface FontIconBadgeProps { - collection: Doc | undefined; + value: string | undefined; } @observer export class FontIconBadge extends React.Component { _notifsRef = React.createRef(); - onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - (e: PointerEvent) => { - const dragData = new DragManager.DocumentDragData([this.props.collection!]); - DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); - return true; - }, - returnFalse, emptyFunction, false); - } + // onPointerDown = (e: React.PointerEvent) => { + // setupMoveUpEvents(this, e, + // (e: PointerEvent) => { + // const dragData = new DragManager.DocumentDragData([this.props.collection!]); + // DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); + // return true; + // }, + // returnFalse, emptyFunction, false); + // } render() { - if (!(this.props.collection instanceof Doc)) return (null); - const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read + if (this.props.value === undefined) return (null); return
    -
    0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} +
    + {this.props.value}
    ; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index a1b9023f3..97e6eddfe 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
    {this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
    ); break; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6abc27b23..7b72787d1 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1436,6 +1436,7 @@ ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.cop ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); -- cgit v1.2.3-70-g09d2 From 7c9001006c1c7ffc10ab0bf0fe5a8b4af85f987f Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 14 Jun 2022 13:06:13 -0400 Subject: got init views based on the current tabs open - able to remove my code in collection free form view. --- package-lock.json | 864 ++++++++++----------- src/client/util/RecordingApi.ts | 81 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 17 +- 3 files changed, 488 insertions(+), 474 deletions(-) (limited to 'src/client/util') diff --git a/package-lock.json b/package-lock.json index 243c36880..7d92dea62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10922,7 +10922,7 @@ "dependencies": { "JSONStream": { "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "resolved": false, "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", "requires": { "jsonparse": "^1.2.0", @@ -10931,12 +10931,12 @@ }, "abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "resolved": false, "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "agent-base": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "resolved": false, "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "requires": { "es6-promisify": "^5.0.0" @@ -10944,7 +10944,7 @@ }, "agentkeepalive": { "version": "3.5.2", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz", + "resolved": false, "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", "requires": { "humanize-ms": "^1.2.1" @@ -10952,7 +10952,7 @@ }, "ansi-align": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "resolved": false, "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { "string-width": "^2.0.0" @@ -10960,12 +10960,12 @@ }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "resolved": false, "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" @@ -10973,27 +10973,27 @@ }, "ansicolors": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "resolved": false, "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=" }, "ansistyles": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz", + "resolved": false, "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=" }, "aproba": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "resolved": false, "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "archy": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "resolved": false, "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" }, "are-we-there-yet": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "resolved": false, "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "requires": { "delegates": "^1.0.0", @@ -11002,7 +11002,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11016,7 +11016,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -11026,12 +11026,12 @@ }, "asap": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "resolved": false, "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "resolved": false, "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "requires": { "safer-buffer": "~2.1.0" @@ -11039,32 +11039,32 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "resolved": false, "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "resolved": false, "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sign2": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "resolved": false, "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "resolved": false, "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "balanced-match": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "resolved": false, "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { @@ -11073,7 +11073,7 @@ }, "bin-links": { "version": "1.1.8", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-1.1.8.tgz", + "resolved": false, "integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==", "requires": { "bluebird": "^3.5.3", @@ -11086,12 +11086,12 @@ }, "bluebird": { "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "resolved": false, "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" }, "boxen": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "resolved": false, "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "requires": { "ansi-align": "^2.0.0", @@ -11105,7 +11105,7 @@ }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", @@ -11114,27 +11114,27 @@ }, "buffer-from": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "resolved": false, "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" }, "builtins": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "resolved": false, "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" }, "byline": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "resolved": false, "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" }, "byte-size": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-5.0.1.tgz", + "resolved": false, "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==" }, "cacache": { "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "resolved": false, "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "requires": { "bluebird": "^3.5.5", @@ -11156,27 +11156,27 @@ }, "call-limit": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.1.tgz", + "resolved": false, "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==" }, "camelcase": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "resolved": false, "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, "capture-stack-trace": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", + "resolved": false, "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=" }, "caseless": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "resolved": false, "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chalk": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "resolved": false, "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "requires": { "ansi-styles": "^3.2.1", @@ -11186,17 +11186,17 @@ }, "chownr": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "resolved": false, "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "ci-info": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "resolved": false, "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "cidr-regex": { "version": "2.0.10", - "resolved": "https://registry.npmjs.org/cidr-regex/-/cidr-regex-2.0.10.tgz", + "resolved": false, "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", "requires": { "ip-regex": "^2.1.0" @@ -11204,12 +11204,12 @@ }, "cli-boxes": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "resolved": false, "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, "cli-columns": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cli-columns/-/cli-columns-3.1.2.tgz", + "resolved": false, "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", "requires": { "string-width": "^2.0.0", @@ -11218,7 +11218,7 @@ }, "cli-table3": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", + "resolved": false, "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "requires": { "colors": "^1.1.2", @@ -11228,7 +11228,7 @@ }, "cliui": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "resolved": false, "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "requires": { "string-width": "^3.1.0", @@ -11243,12 +11243,12 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "resolved": false, "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", @@ -11258,7 +11258,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "resolved": false, "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" @@ -11268,12 +11268,12 @@ }, "clone": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "resolved": false, "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "cmd-shim": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz", + "resolved": false, "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", "requires": { "graceful-fs": "^4.1.2", @@ -11282,12 +11282,12 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "color-convert": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "resolved": false, "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "requires": { "color-name": "^1.1.1" @@ -11295,18 +11295,18 @@ }, "color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "resolved": false, "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colors": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "resolved": false, "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", "optional": true }, "columnify": { "version": "1.5.4", - "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", + "resolved": false, "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", "requires": { "strip-ansi": "^3.0.0", @@ -11315,7 +11315,7 @@ }, "combined-stream": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "resolved": false, "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { "delayed-stream": "~1.0.0" @@ -11323,12 +11323,12 @@ }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "resolved": false, "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { "buffer-from": "^1.0.0", @@ -11339,7 +11339,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11353,7 +11353,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -11363,7 +11363,7 @@ }, "config-chain": { "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", + "resolved": false, "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", "requires": { "ini": "^1.3.4", @@ -11372,7 +11372,7 @@ }, "configstore": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.5.tgz", + "resolved": false, "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==", "requires": { "dot-prop": "^4.2.1", @@ -11385,12 +11385,12 @@ }, "console-control-strings": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "copy-concurrently": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "resolved": false, "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "requires": { "aproba": "^1.1.1", @@ -11403,24 +11403,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "iferr": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "resolved": false, "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" } } }, "core-util-is": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "create-error-class": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "resolved": false, "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "requires": { "capture-stack-trace": "^1.0.0" @@ -11428,7 +11428,7 @@ }, "cross-spawn": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "resolved": false, "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { "lru-cache": "^4.0.1", @@ -11438,7 +11438,7 @@ "dependencies": { "lru-cache": { "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "resolved": false, "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "requires": { "pseudomap": "^1.0.2", @@ -11447,24 +11447,24 @@ }, "yallist": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "resolved": false, "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, "crypto-random-string": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "resolved": false, "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, "cyclist": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "resolved": false, "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" }, "dashdash": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "resolved": false, "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" @@ -11472,7 +11472,7 @@ }, "debug": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "resolved": false, "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { "ms": "2.0.0" @@ -11480,34 +11480,34 @@ "dependencies": { "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": false, "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "debuglog": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "resolved": false, "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" }, "decamelize": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "resolved": false, "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "resolved": false, "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "resolved": false, "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "defaults": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "resolved": false, "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "requires": { "clone": "^1.0.2" @@ -11515,7 +11515,7 @@ }, "define-properties": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "resolved": false, "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "requires": { "object-keys": "^1.0.12" @@ -11523,27 +11523,27 @@ }, "delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "resolved": false, "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "resolved": false, "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-indent": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "resolved": false, "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" }, "detect-newline": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "resolved": false, "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" }, "dezalgo": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "resolved": false, "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", "requires": { "asap": "^2.0.0", @@ -11552,7 +11552,7 @@ }, "dot-prop": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz", + "resolved": false, "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", "requires": { "is-obj": "^1.0.0" @@ -11560,17 +11560,17 @@ }, "dotenv": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", + "resolved": false, "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==" }, "duplexer3": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "resolved": false, "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "duplexify": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", + "resolved": false, "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "requires": { "end-of-stream": "^1.0.0", @@ -11581,7 +11581,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11595,7 +11595,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -11605,7 +11605,7 @@ }, "ecc-jsbn": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "resolved": false, "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { @@ -11615,17 +11615,17 @@ }, "editor": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", + "resolved": false, "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=" }, "emoji-regex": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "resolved": false, "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" }, "encoding": { "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "resolved": false, "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { "iconv-lite": "~0.4.13" @@ -11633,7 +11633,7 @@ }, "end-of-stream": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "resolved": false, "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { "once": "^1.4.0" @@ -11641,17 +11641,17 @@ }, "env-paths": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "resolved": false, "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" }, "err-code": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "resolved": false, "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" }, "errno": { "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "resolved": false, "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "requires": { "prr": "~1.0.1" @@ -11659,7 +11659,7 @@ }, "es-abstract": { "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "resolved": false, "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "requires": { "es-to-primitive": "^1.1.1", @@ -11671,7 +11671,7 @@ }, "es-to-primitive": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "resolved": false, "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", "requires": { "is-callable": "^1.1.4", @@ -11681,12 +11681,12 @@ }, "es6-promise": { "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "resolved": false, "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": false, "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "requires": { "es6-promise": "^4.0.3" @@ -11694,12 +11694,12 @@ }, "escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "resolved": false, "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "execa": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "resolved": false, "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { "cross-spawn": "^5.0.1", @@ -11713,39 +11713,39 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": false, "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" } } }, "extend": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "resolved": false, "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "resolved": false, "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-json-stable-stringify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "resolved": false, "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "figgy-pudding": { "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "resolved": false, "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" }, "find-npm-prefix": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz", + "resolved": false, "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==" }, "flush-write-stream": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "resolved": false, "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "requires": { "inherits": "^2.0.1", @@ -11754,7 +11754,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11768,7 +11768,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -11778,12 +11778,12 @@ }, "forever-agent": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "resolved": false, "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "resolved": false, "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { "asynckit": "^0.4.0", @@ -11793,7 +11793,7 @@ }, "from2": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "resolved": false, "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "requires": { "inherits": "^2.0.1", @@ -11802,7 +11802,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11816,7 +11816,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -11826,7 +11826,7 @@ }, "fs-minipass": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "resolved": false, "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "requires": { "minipass": "^2.6.0" @@ -11834,7 +11834,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -11845,7 +11845,7 @@ }, "fs-vacuum": { "version": "1.2.10", - "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz", + "resolved": false, "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", "requires": { "graceful-fs": "^4.1.2", @@ -11855,7 +11855,7 @@ }, "fs-write-stream-atomic": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "resolved": false, "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "requires": { "graceful-fs": "^4.1.2", @@ -11866,12 +11866,12 @@ "dependencies": { "iferr": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "resolved": false, "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -11885,7 +11885,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -11895,17 +11895,17 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "resolved": false, "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "resolved": false, "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gauge": { "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "resolved": false, "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", @@ -11920,12 +11920,12 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", @@ -11937,12 +11937,12 @@ }, "genfun": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz", + "resolved": false, "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==" }, "gentle-fs": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/gentle-fs/-/gentle-fs-2.3.1.tgz", + "resolved": false, "integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==", "requires": { "aproba": "^1.1.2", @@ -11960,24 +11960,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "iferr": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "resolved": false, "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" } } }, "get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "resolved": false, "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-stream": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "resolved": false, "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "requires": { "pump": "^3.0.0" @@ -11985,7 +11985,7 @@ }, "getpass": { "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "resolved": false, "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" @@ -11993,7 +11993,7 @@ }, "glob": { "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "resolved": false, "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", @@ -12006,7 +12006,7 @@ }, "global-dirs": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "resolved": false, "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "requires": { "ini": "^1.3.4" @@ -12014,7 +12014,7 @@ }, "got": { "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "resolved": false, "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { "create-error-class": "^3.0.0", @@ -12032,24 +12032,24 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": false, "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" } } }, "graceful-fs": { "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "resolved": false, "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "har-schema": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "resolved": false, "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "resolved": false, "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { "ajv": "^6.12.3", @@ -12058,7 +12058,7 @@ "dependencies": { "ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "resolved": false, "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", @@ -12069,19 +12069,19 @@ }, "fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "resolved": false, "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "resolved": false, "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" } } }, "has": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "resolved": false, "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { "function-bind": "^1.1.1" @@ -12089,32 +12089,32 @@ }, "has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "resolved": false, "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "resolved": false, "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "resolved": false, "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "hosted-git-info": { "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "resolved": false, "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "http-cache-semantics": { "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "resolved": false, "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" }, "http-proxy-agent": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", + "resolved": false, "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "requires": { "agent-base": "4", @@ -12123,7 +12123,7 @@ }, "http-signature": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "resolved": false, "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { "assert-plus": "^1.0.0", @@ -12133,7 +12133,7 @@ }, "https-proxy-agent": { "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "resolved": false, "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "requires": { "agent-base": "^4.3.0", @@ -12142,7 +12142,7 @@ }, "humanize-ms": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "resolved": false, "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", "requires": { "ms": "^2.0.0" @@ -12150,7 +12150,7 @@ }, "iconv-lite": { "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "resolved": false, "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -12158,12 +12158,12 @@ }, "iferr": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz", + "resolved": false, "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==" }, "ignore-walk": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "resolved": false, "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "requires": { "minimatch": "^3.0.4" @@ -12171,22 +12171,22 @@ }, "import-lazy": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "resolved": false, "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" }, "imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "resolved": false, "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "infer-owner": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "resolved": false, "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "resolved": false, "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", @@ -12195,17 +12195,17 @@ }, "inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "resolved": false, "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "resolved": false, "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "init-package-json": { "version": "1.10.3", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", + "resolved": false, "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", "requires": { "glob": "^7.1.1", @@ -12220,22 +12220,22 @@ }, "ip": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "resolved": false, "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "ip-regex": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "resolved": false, "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" }, "is-callable": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "resolved": false, "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-ci": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", + "resolved": false, "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", "requires": { "ci-info": "^1.5.0" @@ -12243,14 +12243,14 @@ "dependencies": { "ci-info": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", + "resolved": false, "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==" } } }, "is-cidr": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-cidr/-/is-cidr-3.0.0.tgz", + "resolved": false, "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", "requires": { "cidr-regex": "^2.0.10" @@ -12258,12 +12258,12 @@ }, "is-date-object": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "resolved": false, "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" @@ -12271,7 +12271,7 @@ }, "is-installed-globally": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "resolved": false, "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "requires": { "global-dirs": "^0.1.0", @@ -12280,17 +12280,17 @@ }, "is-npm": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "resolved": false, "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" }, "is-obj": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": false, "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-inside": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "resolved": false, "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "requires": { "path-is-inside": "^1.0.1" @@ -12298,12 +12298,12 @@ }, "is-redirect": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "resolved": false, "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" }, "is-regex": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "resolved": false, "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "requires": { "has": "^1.0.1" @@ -12311,17 +12311,17 @@ }, "is-retry-allowed": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "resolved": false, "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" }, "is-stream": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "resolved": false, "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "resolved": false, "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", "requires": { "has-symbols": "^1.0.0" @@ -12329,53 +12329,53 @@ }, "is-typedarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "resolved": false, "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "resolved": false, "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "resolved": false, "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "resolved": false, "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "jsbn": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "resolved": false, "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "optional": true }, "json-parse-better-errors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "resolved": false, "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" }, "json-schema": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "resolved": false, "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" }, "json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "resolved": false, "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsonparse": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "resolved": false, "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, "jsprim": { "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "resolved": false, "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "requires": { "assert-plus": "1.0.0", @@ -12386,7 +12386,7 @@ }, "latest-version": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "resolved": false, "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "requires": { "package-json": "^4.0.0" @@ -12394,12 +12394,12 @@ }, "lazy-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz", + "resolved": false, "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=" }, "libcipm": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/libcipm/-/libcipm-4.0.8.tgz", + "resolved": false, "integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==", "requires": { "bin-links": "^1.1.2", @@ -12421,7 +12421,7 @@ }, "libnpm": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/libnpm/-/libnpm-3.0.1.tgz", + "resolved": false, "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", "requires": { "bin-links": "^1.1.2", @@ -12448,7 +12448,7 @@ }, "libnpmaccess": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-3.0.2.tgz", + "resolved": false, "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", "requires": { "aproba": "^2.0.0", @@ -12459,7 +12459,7 @@ }, "libnpmconfig": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/libnpmconfig/-/libnpmconfig-1.2.1.tgz", + "resolved": false, "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", "requires": { "figgy-pudding": "^3.5.1", @@ -12469,7 +12469,7 @@ "dependencies": { "find-up": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "resolved": false, "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { "locate-path": "^3.0.0" @@ -12477,7 +12477,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "resolved": false, "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { "p-locate": "^3.0.0", @@ -12486,7 +12486,7 @@ }, "p-limit": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "resolved": false, "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", "requires": { "p-try": "^2.0.0" @@ -12494,7 +12494,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "resolved": false, "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { "p-limit": "^2.0.0" @@ -12502,14 +12502,14 @@ }, "p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "resolved": false, "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" } } }, "libnpmhook": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/libnpmhook/-/libnpmhook-5.0.3.tgz", + "resolved": false, "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", "requires": { "aproba": "^2.0.0", @@ -12520,7 +12520,7 @@ }, "libnpmorg": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/libnpmorg/-/libnpmorg-1.0.1.tgz", + "resolved": false, "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", "requires": { "aproba": "^2.0.0", @@ -12531,7 +12531,7 @@ }, "libnpmpublish": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-1.1.2.tgz", + "resolved": false, "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", "requires": { "aproba": "^2.0.0", @@ -12547,7 +12547,7 @@ }, "libnpmsearch": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/libnpmsearch/-/libnpmsearch-2.0.2.tgz", + "resolved": false, "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", "requires": { "figgy-pudding": "^3.5.1", @@ -12557,7 +12557,7 @@ }, "libnpmteam": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/libnpmteam/-/libnpmteam-1.0.2.tgz", + "resolved": false, "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", "requires": { "aproba": "^2.0.0", @@ -12568,7 +12568,7 @@ }, "libnpx": { "version": "10.2.4", - "resolved": "https://registry.npmjs.org/libnpx/-/libnpx-10.2.4.tgz", + "resolved": false, "integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==", "requires": { "dotenv": "^5.0.1", @@ -12583,7 +12583,7 @@ }, "lock-verify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz", + "resolved": false, "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", "requires": { "npm-package-arg": "^6.1.0", @@ -12592,7 +12592,7 @@ }, "lockfile": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", + "resolved": false, "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", "requires": { "signal-exit": "^3.0.2" @@ -12600,12 +12600,12 @@ }, "lodash._baseindexof": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", + "resolved": false, "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=" }, "lodash._baseuniq": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "resolved": false, "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", "requires": { "lodash._createset": "~4.0.0", @@ -12614,17 +12614,17 @@ }, "lodash._bindcallback": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "resolved": false, "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" }, "lodash._cacheindexof": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", + "resolved": false, "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=" }, "lodash._createcache": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", + "resolved": false, "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", "requires": { "lodash._getnative": "^3.0.0" @@ -12632,52 +12632,52 @@ }, "lodash._createset": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "resolved": false, "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" }, "lodash._getnative": { "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "resolved": false, "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" }, "lodash._root": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "resolved": false, "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" }, "lodash.clonedeep": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "resolved": false, "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.restparam": { "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "resolved": false, "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" }, "lodash.union": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "resolved": false, "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "lodash.uniq": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "resolved": false, "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, "lodash.without": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz", + "resolved": false, "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=" }, "lowercase-keys": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "resolved": false, "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "resolved": false, "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { "yallist": "^3.0.2" @@ -12685,7 +12685,7 @@ }, "make-dir": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "resolved": false, "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "requires": { "pify": "^3.0.0" @@ -12693,7 +12693,7 @@ }, "make-fetch-happen": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz", + "resolved": false, "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", "requires": { "agentkeepalive": "^3.4.1", @@ -12711,17 +12711,17 @@ }, "meant": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/meant/-/meant-1.0.2.tgz", + "resolved": false, "integrity": "sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg==" }, "mime-db": { "version": "1.35.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "resolved": false, "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==" }, "mime-types": { "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "resolved": false, "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "requires": { "mime-db": "~1.35.0" @@ -12729,7 +12729,7 @@ }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" @@ -12742,7 +12742,7 @@ }, "minizlib": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "resolved": false, "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "requires": { "minipass": "^2.9.0" @@ -12750,7 +12750,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -12761,7 +12761,7 @@ }, "mississippi": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "resolved": false, "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", "requires": { "concat-stream": "^1.5.0", @@ -12778,7 +12778,7 @@ }, "mkdirp": { "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "resolved": false, "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "requires": { "minimist": "^1.2.5" @@ -12793,7 +12793,7 @@ }, "move-concurrently": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "resolved": false, "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "requires": { "aproba": "^1.1.1", @@ -12806,24 +12806,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" } } }, "ms": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "resolved": false, "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "mute-stream": { "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "resolved": false, "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" }, "node-fetch-npm": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", + "resolved": false, "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", "requires": { "encoding": "^0.1.11", @@ -12833,7 +12833,7 @@ }, "node-gyp": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-5.1.0.tgz", + "resolved": false, "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", "requires": { "env-paths": "^2.2.0", @@ -12851,7 +12851,7 @@ }, "nopt": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "resolved": false, "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "requires": { "abbrev": "1", @@ -12860,7 +12860,7 @@ }, "normalize-package-data": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "resolved": false, "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "requires": { "hosted-git-info": "^2.1.4", @@ -12871,7 +12871,7 @@ "dependencies": { "resolve": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "resolved": false, "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "requires": { "path-parse": "^1.0.6" @@ -12881,7 +12881,7 @@ }, "npm-audit-report": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/npm-audit-report/-/npm-audit-report-1.3.3.tgz", + "resolved": false, "integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==", "requires": { "cli-table3": "^0.5.0", @@ -12890,7 +12890,7 @@ }, "npm-bundled": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "resolved": false, "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "requires": { "npm-normalize-package-bin": "^1.0.1" @@ -12898,12 +12898,12 @@ }, "npm-cache-filename": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz", + "resolved": false, "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=" }, "npm-install-checks": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.2.tgz", + "resolved": false, "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", "requires": { "semver": "^2.3.0 || 3.x || 4 || 5" @@ -12911,7 +12911,7 @@ }, "npm-lifecycle": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-3.1.5.tgz", + "resolved": false, "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==", "requires": { "byline": "^5.0.0", @@ -12926,17 +12926,17 @@ }, "npm-logical-tree": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz", + "resolved": false, "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==" }, "npm-normalize-package-bin": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "resolved": false, "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-package-arg": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "resolved": false, "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", "requires": { "hosted-git-info": "^2.7.1", @@ -12947,7 +12947,7 @@ }, "npm-packlist": { "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "resolved": false, "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "requires": { "ignore-walk": "^3.0.1", @@ -12957,7 +12957,7 @@ }, "npm-pick-manifest": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", + "resolved": false, "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", "requires": { "figgy-pudding": "^3.5.1", @@ -12967,7 +12967,7 @@ }, "npm-profile": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/npm-profile/-/npm-profile-4.0.4.tgz", + "resolved": false, "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", "requires": { "aproba": "^1.1.2 || 2", @@ -12977,7 +12977,7 @@ }, "npm-registry-fetch": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.7.tgz", + "resolved": false, "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==", "requires": { "JSONStream": "^1.3.4", @@ -12991,14 +12991,14 @@ "dependencies": { "safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "resolved": false, "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, "npm-run-path": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "resolved": false, "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { "path-key": "^2.0.0" @@ -13006,12 +13006,12 @@ }, "npm-user-validate": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.1.tgz", + "resolved": false, "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==" }, "npmlog": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "resolved": false, "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "~1.1.2", @@ -13022,27 +13022,27 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "resolved": false, "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" }, "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "resolved": false, "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-keys": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "resolved": false, "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" }, "object.getownpropertydescriptors": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "resolved": false, "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "requires": { "define-properties": "^1.1.2", @@ -13051,7 +13051,7 @@ }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" @@ -13059,22 +13059,22 @@ }, "opener": { "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "resolved": false, "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" }, "os-homedir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": false, "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "resolved": false, "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", @@ -13083,12 +13083,12 @@ }, "p-finally": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "resolved": false, "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "package-json": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "resolved": false, "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "^6.7.1", @@ -13099,7 +13099,7 @@ }, "pacote": { "version": "9.5.12", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.5.12.tgz", + "resolved": false, "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", "requires": { "bluebird": "^3.5.3", @@ -13136,7 +13136,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -13147,7 +13147,7 @@ }, "parallel-transform": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "resolved": false, "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "requires": { "cyclist": "~0.2.2", @@ -13157,7 +13157,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -13171,7 +13171,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -13181,57 +13181,57 @@ }, "path-exists": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "resolved": false, "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": false, "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "resolved": false, "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "resolved": false, "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "resolved": false, "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "performance-now": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "resolved": false, "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "resolved": false, "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" }, "prepend-http": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "resolved": false, "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "process-nextick-args": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "resolved": false, "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "promise-inflight": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "resolved": false, "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, "promise-retry": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "resolved": false, "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", "requires": { "err-code": "^1.0.0", @@ -13240,14 +13240,14 @@ "dependencies": { "retry": { "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "resolved": false, "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" } } }, "promzard": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", + "resolved": false, "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", "requires": { "read": "1" @@ -13255,12 +13255,12 @@ }, "proto-list": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "resolved": false, "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, "protoduck": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.1.tgz", + "resolved": false, "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", "requires": { "genfun": "^5.0.0" @@ -13268,22 +13268,22 @@ }, "prr": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "resolved": false, "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, "pseudomap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "resolved": false, "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "resolved": false, "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" }, "pump": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "resolved": false, "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "requires": { "end-of-stream": "^1.1.0", @@ -13292,7 +13292,7 @@ }, "pumpify": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "resolved": false, "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "requires": { "duplexify": "^3.6.0", @@ -13302,7 +13302,7 @@ "dependencies": { "pump": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "resolved": false, "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "requires": { "end-of-stream": "^1.1.0", @@ -13313,22 +13313,22 @@ }, "punycode": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "resolved": false, "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qrcode-terminal": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "resolved": false, "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==" }, "qs": { "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "resolved": false, "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { "version": "6.8.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", + "resolved": false, "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", "requires": { "decode-uri-component": "^0.2.0", @@ -13338,12 +13338,12 @@ }, "qw": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/qw/-/qw-1.0.1.tgz", + "resolved": false, "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=" }, "rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "resolved": false, "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { "deep-extend": "^0.6.0", @@ -13354,7 +13354,7 @@ }, "read": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "resolved": false, "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", "requires": { "mute-stream": "~0.0.4" @@ -13362,7 +13362,7 @@ }, "read-cmd-shim": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.5.tgz", + "resolved": false, "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", "requires": { "graceful-fs": "^4.1.2" @@ -13370,7 +13370,7 @@ }, "read-installed": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "resolved": false, "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", "requires": { "debuglog": "^1.0.1", @@ -13384,7 +13384,7 @@ }, "read-package-json": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.1.tgz", + "resolved": false, "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", "requires": { "glob": "^7.1.1", @@ -13396,7 +13396,7 @@ }, "read-package-tree": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.3.1.tgz", + "resolved": false, "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", "requires": { "read-package-json": "^2.0.0", @@ -13406,7 +13406,7 @@ }, "readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "resolved": false, "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { "inherits": "^2.0.3", @@ -13416,7 +13416,7 @@ }, "readdir-scoped-modules": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "resolved": false, "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", "requires": { "debuglog": "^1.0.1", @@ -13427,7 +13427,7 @@ }, "registry-auth-token": { "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", + "resolved": false, "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", "requires": { "rc": "^1.1.6", @@ -13436,7 +13436,7 @@ }, "registry-url": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "resolved": false, "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { "rc": "^1.0.1" @@ -13444,7 +13444,7 @@ }, "request": { "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "resolved": false, "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { "aws-sign2": "~0.7.0", @@ -13471,27 +13471,27 @@ }, "require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "resolved": false, "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "resolved": false, "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": false, "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "retry": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "resolved": false, "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, "rimraf": { "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "resolved": false, "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "requires": { "glob": "^7.1.3" @@ -13499,7 +13499,7 @@ }, "run-queue": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "resolved": false, "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "requires": { "aproba": "^1.1.1" @@ -13507,29 +13507,29 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "resolved": false, "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" } } }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "resolved": false, "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "resolved": false, "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-diff": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "resolved": false, "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { "semver": "^5.0.3" @@ -13537,12 +13537,12 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "resolved": false, "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "sha": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/sha/-/sha-3.0.0.tgz", + "resolved": false, "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", "requires": { "graceful-fs": "^4.1.2" @@ -13550,7 +13550,7 @@ }, "shebang-command": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "resolved": false, "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { "shebang-regex": "^1.0.0" @@ -13558,27 +13558,27 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "resolved": false, "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "signal-exit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "resolved": false, "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "slide": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "resolved": false, "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" }, "smart-buffer": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz", + "resolved": false, "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" }, "socks": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.3.3.tgz", + "resolved": false, "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", "requires": { "ip": "1.1.5", @@ -13587,7 +13587,7 @@ }, "socks-proxy-agent": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz", + "resolved": false, "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", "requires": { "agent-base": "~4.2.1", @@ -13596,7 +13596,7 @@ "dependencies": { "agent-base": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "resolved": false, "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "requires": { "es6-promisify": "^5.0.0" @@ -13606,12 +13606,12 @@ }, "sorted-object": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz", + "resolved": false, "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=" }, "sorted-union-stream": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz", + "resolved": false, "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", "requires": { "from2": "^1.3.0", @@ -13620,7 +13620,7 @@ "dependencies": { "from2": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz", + "resolved": false, "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", "requires": { "inherits": "~2.0.1", @@ -13629,12 +13629,12 @@ }, "isarray": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "resolved": false, "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "readable-stream": { "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": false, "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", @@ -13645,14 +13645,14 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": false, "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" } } }, "spdx-correct": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "resolved": false, "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "requires": { "spdx-expression-parse": "^3.0.0", @@ -13661,12 +13661,12 @@ }, "spdx-exceptions": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "resolved": false, "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" }, "spdx-expression-parse": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "resolved": false, "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "requires": { "spdx-exceptions": "^2.1.0", @@ -13675,17 +13675,17 @@ }, "spdx-license-ids": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "resolved": false, "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, "split-on-first": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "resolved": false, "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" }, "sshpk": { "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "resolved": false, "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "requires": { "asn1": "~0.2.3", @@ -13701,7 +13701,7 @@ }, "ssri": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "resolved": false, "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" @@ -13709,7 +13709,7 @@ }, "stream-each": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", + "resolved": false, "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", "requires": { "end-of-stream": "^1.1.0", @@ -13718,7 +13718,7 @@ }, "stream-iterate": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz", + "resolved": false, "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", "requires": { "readable-stream": "^2.1.5", @@ -13727,7 +13727,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -13741,7 +13741,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -13751,17 +13751,17 @@ }, "stream-shift": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "resolved": false, "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, "strict-uri-encode": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "resolved": false, "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-width": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "resolved": false, "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -13770,17 +13770,17 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "resolved": false, "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "strip-ansi": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "resolved": false, "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { "ansi-regex": "^3.0.0" @@ -13790,7 +13790,7 @@ }, "string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "resolved": false, "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" @@ -13798,19 +13798,19 @@ "dependencies": { "safe-buffer": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "resolved": false, "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" } } }, "stringify-package": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", + "resolved": false, "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==" }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -13818,17 +13818,17 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": false, "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "resolved": false, "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "supports-color": { "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "resolved": false, "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { "has-flag": "^3.0.0" @@ -13836,7 +13836,7 @@ }, "tar": { "version": "4.4.19", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "resolved": false, "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", "requires": { "chownr": "^1.1.4", @@ -13850,7 +13850,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "resolved": false, "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", @@ -13859,19 +13859,19 @@ }, "safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "resolved": false, "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "yallist": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "resolved": false, "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, "term-size": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "resolved": false, "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "requires": { "execa": "^0.7.0" @@ -13879,17 +13879,17 @@ }, "text-table": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "resolved": false, "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": false, "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "resolved": false, "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "requires": { "readable-stream": "^2.1.5", @@ -13898,7 +13898,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": false, "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", @@ -13912,7 +13912,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": false, "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" @@ -13922,17 +13922,17 @@ }, "timed-out": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "resolved": false, "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, "tiny-relative-date": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz", + "resolved": false, "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==" }, "tough-cookie": { "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "resolved": false, "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "requires": { "psl": "^1.1.24", @@ -13941,7 +13941,7 @@ }, "tunnel-agent": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "resolved": false, "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { "safe-buffer": "^5.0.1" @@ -13949,28 +13949,28 @@ }, "tweetnacl": { "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "resolved": false, "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "optional": true }, "typedarray": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "resolved": false, "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uid-number": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "resolved": false, "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" }, "umask": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", + "resolved": false, "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=" }, "unique-filename": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "resolved": false, "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", "requires": { "unique-slug": "^2.0.0" @@ -13978,7 +13978,7 @@ }, "unique-slug": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", + "resolved": false, "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", "requires": { "imurmurhash": "^0.1.4" @@ -13986,7 +13986,7 @@ }, "unique-string": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "resolved": false, "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "requires": { "crypto-random-string": "^1.0.0" @@ -13994,17 +13994,17 @@ }, "unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "resolved": false, "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unzip-response": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "resolved": false, "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" }, "update-notifier": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "resolved": false, "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "requires": { "boxen": "^1.2.1", @@ -14021,7 +14021,7 @@ }, "uri-js": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "resolved": false, "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "requires": { "punycode": "^2.1.0" @@ -14029,14 +14029,14 @@ "dependencies": { "punycode": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "resolved": false, "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, "url-parse-lax": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "resolved": false, "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "requires": { "prepend-http": "^1.0.1" @@ -14044,17 +14044,17 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "resolved": false, "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util-extend": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "resolved": false, "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=" }, "util-promisify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-2.1.0.tgz", + "resolved": false, "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", "requires": { "object.getownpropertydescriptors": "^2.0.3" @@ -14062,12 +14062,12 @@ }, "uuid": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "resolved": false, "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "validate-npm-package-license": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "resolved": false, "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { "spdx-correct": "^3.0.0", @@ -14076,7 +14076,7 @@ }, "validate-npm-package-name": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "resolved": false, "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", "requires": { "builtins": "^1.0.3" @@ -14084,7 +14084,7 @@ }, "verror": { "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "resolved": false, "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { "assert-plus": "^1.0.0", @@ -14094,7 +14094,7 @@ }, "wcwidth": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "resolved": false, "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", "requires": { "defaults": "^1.0.3" @@ -14102,7 +14102,7 @@ }, "which": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "resolved": false, "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" @@ -14110,12 +14110,12 @@ }, "which-module": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "resolved": false, "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "resolved": false, "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "requires": { "string-width": "^1.0.2" @@ -14123,7 +14123,7 @@ "dependencies": { "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", @@ -14135,7 +14135,7 @@ }, "widest-line": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", + "resolved": false, "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", "requires": { "string-width": "^2.1.1" @@ -14143,7 +14143,7 @@ }, "worker-farm": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "resolved": false, "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", "requires": { "errno": "~0.1.7" @@ -14151,7 +14151,7 @@ }, "wrap-ansi": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "resolved": false, "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "requires": { "ansi-styles": "^3.2.0", @@ -14166,12 +14166,12 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "string-width": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "resolved": false, "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", @@ -14181,7 +14181,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "resolved": false, "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" @@ -14191,12 +14191,12 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "resolved": false, "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", "requires": { "graceful-fs": "^4.1.11", @@ -14206,27 +14206,27 @@ }, "xdg-basedir": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "resolved": false, "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" }, "xtend": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "resolved": false, "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" }, "y18n": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "resolved": false, "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" }, "yallist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" }, "yargs": { "version": "14.2.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "resolved": false, "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", "requires": { "cliui": "^5.0.0", @@ -14244,12 +14244,12 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "resolved": false, "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "find-up": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "resolved": false, "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { "locate-path": "^3.0.0" @@ -14257,12 +14257,12 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "resolved": false, "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "locate-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "resolved": false, "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { "p-locate": "^3.0.0", @@ -14271,7 +14271,7 @@ }, "p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "resolved": false, "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "requires": { "p-try": "^2.0.0" @@ -14279,7 +14279,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "resolved": false, "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { "p-limit": "^2.0.0" @@ -14287,12 +14287,12 @@ }, "p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "resolved": false, "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "string-width": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "resolved": false, "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", @@ -14302,7 +14302,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "resolved": false, "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" @@ -14312,7 +14312,7 @@ }, "yargs-parser": { "version": "15.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "resolved": false, "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", "requires": { "camelcase": "^5.0.0", @@ -14321,7 +14321,7 @@ "dependencies": { "camelcase": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "resolved": false, "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" } } diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 12ca07d67..a8740a5bd 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -4,14 +4,20 @@ import { NumCast } from "../../fields/Types"; import { Doc } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; import { isArray } from "lodash"; +import { SelectionManager } from "./SelectionManager"; +import { DocumentDecorations } from "../views/DocumentDecorations"; +import { DocumentManager } from "./DocumentManager"; +import { CollectionDockingView } from "../views/collections/CollectionDockingView"; type Movement = { time: number, panX: number, panY: number, scale: number, + docId: String, } + export type Presentation = { movements: Movement[] | null, totalTime: number, @@ -28,6 +34,8 @@ export class RecordingApi { private currentPresentation: Presentation; private tracking: boolean; private absoluteStart: number; + // instance variable for holding the FFViews and their disposers + private recordingFFViews: Map | null; // create static instance and getter for global use @@ -43,8 +51,7 @@ export class RecordingApi { this.absoluteStart = -1; // used for tracking movements in the view frame - this.disposeFunc = null; - this.recordingFFView = null; + this.recordingFFViews = null; // for now, set playFFView this.playFFView = null; @@ -56,7 +63,31 @@ export class RecordingApi { return this.currentPresentation.movements === null } + private addRecordingFFView(doc: Doc, docId: String): void { + const disposeFunc = reaction( + () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0)}), + (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, docId, res.scale), + ); + + console.log('adding dispose func : docId', docId, 'doc', doc); + this.recordingFFViews?.set(docId, disposeFunc); + } + public start = (meta?: Object) => { + // init the dispose funcs + this.recordingFFViews = new Map(); + // look over all open tabs and only track free form docs + for (const { contentItem, DashDoc } of CollectionDockingView.Instance.tabMap) { + if ('viewType' in DashDoc && DashDoc.viewType === 'freeform') { + const docId = contentItem.config.props.documentId; + // remove the proxy on the DashDoc by using the spread operator + this.addRecordingFFView(DashDoc, docId); + } + } + + console.log(this.recordingFFViews); + + // update the presentation mode Doc.UserDoc().presentationMode = 'recording'; @@ -79,7 +110,7 @@ export class RecordingApi { if (this.nullPresentation || !this.tracking) return null; // set the previus recording view to the play view - this.playFFView = this.recordingFFView; + // this.playFFView = this.recordingFFView; // ensure we add the endTime now that they are done recording const cpy = { ...this.currentPresentation, totalTime: new Date().getTime() - this.absoluteStart }; @@ -100,7 +131,7 @@ export class RecordingApi { public clear = (): void => { // clear the disposeFunc if we are done (not tracking) if (!this.tracking) { - this.removeRecordingFFView(); + this.removeAllRecordingFFViews(); // update the presentation mode now that we are done tracking Doc.UserDoc().presentationMode = 'none'; } @@ -112,13 +143,16 @@ export class RecordingApi { this.absoluteStart = -1 } - // call on dispose function to stop tracking movements - public removeRecordingFFView = (): void => { - this.disposeFunc?.(); - this.disposeFunc = null; + private removeAllRecordingFFViews = () => { + if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } + + for (const [id, disposeFunc] of this.recordingFFViews) { + disposeFunc(); + this.recordingFFViews.delete(id); + } } - private trackMovements = (panX: number, panY: number, scale: number = 0) => { + private trackMovements = (panX: number, panY: number, docId: String, scale: number = 0) => { // ensure we are recording if (!this.tracking) { console.error('[recordingApi.ts] trackMovements(): tracking is false') @@ -133,32 +167,12 @@ export class RecordingApi { // get the time const time = new Date().getTime() - this.absoluteStart // make new movement object - const movement: Movement = { time, panX, panY, scale } + const movement: Movement = { time, panX, panY, scale, docId } // add that movement to the current presentation data's movement array this.currentPresentation.movements && this.currentPresentation.movements.push(movement) } - // instance variable for the FFView - private disposeFunc: IReactionDisposer | null; - private recordingFFView: CollectionFreeFormView | null; - - // set the FFView that will be used in a reaction to track the movements - public setRecordingFFView = (view: CollectionFreeFormView): void => { - // set the view to the current view - if (view === this.recordingFFView || view == null) return; - - // this.recordingFFView = view; - // set the reaction to track the movements - this.disposeFunc = reaction( - () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), - (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.scale) - ) - - // for now, set the most recent recordingFFView to the playFFView - this.recordingFFView = view; - } - // TODO: extract this into different class with pause and resume recording // TODO: store the FFview with the movements private playFFView: CollectionFreeFormView | null; @@ -269,17 +283,14 @@ export class RecordingApi { return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; } - // Unfinished code for tracing multiple free form views - // export let pres: Map = new Map() - - // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // public AddRecordingFFView(ffView: Doc): void { // pres.set(ffView, // reaction(() => ({ x: ffView.panX, y: ffView.panY }), // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) // ) // } - // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // public RemoveRecordingFFView(ffView: CollectionFreeFormView): void { // const disposer = pres.get(ffView); // disposer?.(); // pres.delete(ffView) diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5534ddd35..33474bc4b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1006,14 +1006,17 @@ export class CollectionFreeFormView extends CollectionSubView Date: Tue, 14 Jun 2022 15:57:54 -0400 Subject: added code that correctly and effeicintly adds and removes reactions onto any tab the user observes --- src/client/util/RecordingApi.ts | 100 +++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 32 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index a8740a5bd..2b8d11818 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -1,13 +1,15 @@ import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; -import { IReactionDisposer, observable, reaction } from "mobx"; +import { IReactionDisposer, observable, observe, reaction } from "mobx"; import { NumCast } from "../../fields/Types"; -import { Doc } from "../../fields/Doc"; +import { Doc, DocListCast } from "../../fields/Doc"; import { VideoBox } from "../views/nodes/VideoBox"; import { isArray } from "lodash"; import { SelectionManager } from "./SelectionManager"; import { DocumentDecorations } from "../views/DocumentDecorations"; import { DocumentManager } from "./DocumentManager"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; +import { Id } from "../../fields/FieldSymbols"; +import { returnAll } from "../../Utils"; type Movement = { time: number, @@ -35,7 +37,8 @@ export class RecordingApi { private tracking: boolean; private absoluteStart: number; // instance variable for holding the FFViews and their disposers - private recordingFFViews: Map | null; + private recordingFFViews: Map | null; + private tabChangeDisposeFunc: IReactionDisposer | null; // create static instance and getter for global use @@ -52,6 +55,7 @@ export class RecordingApi { // used for tracking movements in the view frame this.recordingFFViews = null; + this.tabChangeDisposeFunc = null; // for now, set playFFView this.playFFView = null; @@ -63,30 +67,73 @@ export class RecordingApi { return this.currentPresentation.movements === null } - private addRecordingFFView(doc: Doc, docId: String): void { + private addRecordingFFView(doc: Doc, key: string = doc[Id]): void { + console.info('adding dispose func : docId', key, 'doc', doc); + + if (this.recordingFFViews === null) { console.warn('addFFView on null RecordingApi'); return; } + if (this.recordingFFViews.has(key)) { console.warn('addFFView : key already in map'); return; } + const disposeFunc = reaction( () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0)}), - (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, docId, res.scale), + (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, key, res.scale), ); - - console.log('adding dispose func : docId', docId, 'doc', doc); - this.recordingFFViews?.set(docId, disposeFunc); + this.recordingFFViews?.set(key, disposeFunc); } - public start = (meta?: Object) => { - // init the dispose funcs - this.recordingFFViews = new Map(); - // look over all open tabs and only track free form docs - for (const { contentItem, DashDoc } of CollectionDockingView.Instance.tabMap) { - if ('viewType' in DashDoc && DashDoc.viewType === 'freeform') { - const docId = contentItem.config.props.documentId; - // remove the proxy on the DashDoc by using the spread operator - this.addRecordingFFView(DashDoc, docId); + private removeRecordingFFView = (key: string) => { + console.info('removing dispose func : docId', key); + if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } + this.recordingFFViews.get(key)?.(); + this.recordingFFViews.delete(key); + } + + // in the case where only one tab was changed (updates not across dashboards), set only one to true + private updateRecordingFFViewsFromTabs = (docList: Doc[], onlyOne = false) => { + if (this.recordingFFViews === null) return; + + // so that the size comparisons are correct, we must filter to only the FFViews + const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform'; + const tabbedFFViews = new Set(); + for (const DashDoc of docList) { + if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]); + } + + + // new tab was added - need to add it + if (tabbedFFViews.size > this.recordingFFViews.size) { + for (const DashDoc of docList) { + if (!this.recordingFFViews.has(DashDoc[Id])) { + isFFView(DashDoc) && this.addRecordingFFView(DashDoc); + + // only one max change, so return + if (onlyOne) return; + } } } + // tab was removed - need to remove it from recordingFFViews + else if (tabbedFFViews.size < this.recordingFFViews.size) { + for (const [key] of this.recordingFFViews) { + if (!tabbedFFViews.has(key)) { + this.removeRecordingFFView(key); + if (onlyOne) return; + } + } + } + } - console.log(this.recordingFFViews); + public start = (meta?: Object) => { + // init the dispose funcs on the page + this.recordingFFViews = new Map(); + const docList = DocListCast(CollectionDockingView.Instance.props.Document.data); + this.updateRecordingFFViewsFromTabs(docList); + // create a reaction to monitor changes in tabs + this.tabChangeDisposeFunc = + reaction(() => CollectionDockingView.Instance.props.Document.data, + (change) => { + // TODO: consider changing between dashboards + this.updateRecordingFFViewsFromTabs(DocListCast(change), true); + }); // update the presentation mode Doc.UserDoc().presentationMode = 'recording'; @@ -118,6 +165,7 @@ export class RecordingApi { // reset the current presentation clearData && this.clear(); + console.log('yieldPresentation', cpy); return cpy; } @@ -132,6 +180,7 @@ export class RecordingApi { // clear the disposeFunc if we are done (not tracking) if (!this.tracking) { this.removeAllRecordingFFViews(); + this.tabChangeDisposeFunc?.(); // update the presentation mode now that we are done tracking Doc.UserDoc().presentationMode = 'none'; } @@ -144,7 +193,7 @@ export class RecordingApi { } private removeAllRecordingFFViews = () => { - if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } + if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; } for (const [id, disposeFunc] of this.recordingFFViews) { disposeFunc(); @@ -282,17 +331,4 @@ export class RecordingApi { // return the combined presentation with the updated total summed time return { movements: combinedMovements, totalTime: sumTime, meta: combinedMetas }; } - - // public AddRecordingFFView(ffView: Doc): void { - // pres.set(ffView, - // reaction(() => ({ x: ffView.panX, y: ffView.panY }), - // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) - // ) - // } - - // public RemoveRecordingFFView(ffView: CollectionFreeFormView): void { - // const disposer = pres.get(ffView); - // disposer?.(); - // pres.delete(ffView) - // } } -- cgit v1.2.3-70-g09d2 From 844b84bc7440e80252b4892138c47192d4cb0cc1 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 14 Jun 2022 17:03:53 -0400 Subject: got the tab to open, but can't replay the movements on the tab --- src/client/util/RecordingApi.ts | 64 +++++++++++++++------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- 2 files changed, 44 insertions(+), 22 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 2b8d11818..44dc4593c 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -10,13 +10,15 @@ import { DocumentManager } from "./DocumentManager"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { Id } from "../../fields/FieldSymbols"; import { returnAll } from "../../Utils"; +import { ContextExclusionPlugin } from "webpack"; +import { DocServer } from "../DocServer"; type Movement = { time: number, panX: number, panY: number, scale: number, - docId: String, + docId: string, } @@ -88,20 +90,20 @@ export class RecordingApi { } // in the case where only one tab was changed (updates not across dashboards), set only one to true - private updateRecordingFFViewsFromTabs = (docList: Doc[], onlyOne = false) => { + private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => { if (this.recordingFFViews === null) return; // so that the size comparisons are correct, we must filter to only the FFViews const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform'; - const tabbedFFViews = new Set(); - for (const DashDoc of docList) { + const tabbedFFViews = new Set(); + for (const DashDoc of tabbedDocs) { if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]); } // new tab was added - need to add it if (tabbedFFViews.size > this.recordingFFViews.size) { - for (const DashDoc of docList) { + for (const DashDoc of tabbedDocs) { if (!this.recordingFFViews.has(DashDoc[Id])) { isFFView(DashDoc) && this.addRecordingFFView(DashDoc); @@ -184,10 +186,9 @@ export class RecordingApi { // update the presentation mode now that we are done tracking Doc.UserDoc().presentationMode = 'none'; } + // clear presenation data this.currentPresentation = RecordingApi.NULL_PRESENTATION - // clear isRecording - // this.tracking = false // clear absoluteStart this.absoluteStart = -1 } @@ -201,7 +202,7 @@ export class RecordingApi { } } - private trackMovements = (panX: number, panY: number, docId: String, scale: number = 0) => { + private trackMovements = (panX: number, panY: number, docId: string, scale: number = 0) => { // ensure we are recording if (!this.tracking) { console.error('[recordingApi.ts] trackMovements(): tracking is false') @@ -281,25 +282,46 @@ export class RecordingApi { let preScale = -1; const zoomAndPan = (movement: Movement) => { const { panX, panY, scale } = movement; - (scale !== -1 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + (scale !== 0 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); document.Document._panX = panX; document.Document._panY = panY; preScale = scale; } - // set the first frame to be at the start of the pres - zoomAndPan(filteredMovements[0]); - - // make timers that will execute each movement at the correct replay time - this.timers = filteredMovements.map(movement => { - const timeDiff = movement.time - timeViewed * 1000 - return setTimeout(() => { - // replay the movement - zoomAndPan(movement) - // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; - }, timeDiff) + // zoomAndPan(filteredMovements[0]); + + // generate a set of all unique docIds + const docIds = new Set(filteredMovements.map(movement => movement.docId)) + // TODO: optimize only ons-first load + // TODO: make async await + // TODO: make sure the cahce still hs the id + // this will load the cache, so getCachedREfFields won't have to reach server + DocServer.GetRefFields([...docIds]).then(refFields => { + console.log('refFields', refFields) + const openTab = (docId: string) => { + const isInView = DocumentManager.Instance.getDocumentViewById(docId); + if (isInView) { return isInView; } + + const doc = DocServer.GetCachedRefField(docId); + CollectionDockingView.AddSplit(doc, 'right'); + return DocumentManager.Instance.getDocumentView(doc); + } + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed * 1000 + return setTimeout(() => { + // open tab if it is not already open + const view = openTab(movement.docId); + // const ffView = view.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + console.log('dview', view, 'ffView', view?.props) + // replay the movement + zoomAndPan(movement); + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + }, timeDiff) + }); }) } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 33474bc4b..a0e0f4f8d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1011,7 +1011,7 @@ export class CollectionFreeFormView extends CollectionSubView Date: Tue, 14 Jun 2022 17:13:36 -0400 Subject: Correctly hops between free form views. --- src/client/util/RecordingApi.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 44dc4593c..99085e658 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -280,7 +280,7 @@ export class RecordingApi { // helper to replay a movement const document = this.playFFView let preScale = -1; - const zoomAndPan = (movement: Movement) => { + const zoomAndPan = (movement: Movement, document: CollectionFreeFormView) => { const { panX, panY, scale } = movement; (scale !== 0 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); document.Document._panX = panX; @@ -315,9 +315,9 @@ export class RecordingApi { // open tab if it is not already open const view = openTab(movement.docId); // const ffView = view.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - console.log('dview', view, 'ffView', view?.props) + const collectionFFView = view?.ComponentView; // replay the movement - zoomAndPan(movement); + zoomAndPan(movement, collectionFFView as CollectionFreeFormView); // if last movement, presentation is done -> set the instance var if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; }, timeDiff) -- cgit v1.2.3-70-g09d2 From 83690c334c8d343d6d7560a6aa7f8e7be9cd3639 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 14 Jun 2022 17:48:45 -0400 Subject: done for day - get the functionality down. will make code more readable and fix some small bugs tomorrow --- src/client/util/RecordingApi.ts | 43 +++++++++++++++------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 2 +- 3 files changed, 31 insertions(+), 16 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 99085e658..4bbaa9a79 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -12,6 +12,7 @@ import { Id } from "../../fields/FieldSymbols"; import { returnAll } from "../../Utils"; import { ContextExclusionPlugin } from "webpack"; import { DocServer } from "../DocServer"; +import { DocumentView } from "../views/nodes/DocumentView"; type Movement = { time: number, @@ -105,10 +106,12 @@ export class RecordingApi { if (tabbedFFViews.size > this.recordingFFViews.size) { for (const DashDoc of tabbedDocs) { if (!this.recordingFFViews.has(DashDoc[Id])) { - isFFView(DashDoc) && this.addRecordingFFView(DashDoc); + if (isFFView(DashDoc)) { + this.addRecordingFFView(DashDoc); - // only one max change, so return - if (onlyOne) return; + // only one max change, so return + if (onlyOne) return; + } } } } @@ -134,6 +137,7 @@ export class RecordingApi { reaction(() => CollectionDockingView.Instance.props.Document.data, (change) => { // TODO: consider changing between dashboards + console.log('change in tabs', change); this.updateRecordingFFViewsFromTabs(DocListCast(change), true); }); @@ -197,20 +201,23 @@ export class RecordingApi { if (this.recordingFFViews === null) { console.warn('removeAllFFViews on null RecordingApi'); return; } for (const [id, disposeFunc] of this.recordingFFViews) { + console.log('calling dispose func : docId', id); disposeFunc(); this.recordingFFViews.delete(id); } } private trackMovements = (panX: number, panY: number, docId: string, scale: number = 0) => { - // ensure we are recording + // ensure we are recording to track if (!this.tracking) { console.error('[recordingApi.ts] trackMovements(): tracking is false') return; } - // check to see if the presetation is init + // check to see if the presetation is init - if not, we are between segments + // TODO: make this more clear - tracking should be "live tracking", not always true when the recording api being used (between start and yieldPres) + // bacuse tracking should be false inbetween segments high key if (this.nullPresentation) { - console.error('[recordingApi.ts] trackMovements(): no presentation') + console.warn('[recordingApi.ts] trackMovements(): trying to store movemetns between segments') return; } @@ -236,7 +243,7 @@ export class RecordingApi { // play movemvents will recreate them when the user resumes the presentation public pauseMovements = (): undefined | Error => { if (this.playFFView === null) { - return new Error('[recordingApi.ts] pauseMovements() failed: no view') + // return new Error('[recordingApi.ts] pauseMovements() failed: no view') } if (!this._isPlaying) { @@ -263,7 +270,7 @@ export class RecordingApi { public _isPlaying = false; public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { - if (presentation.movements === null || this.playFFView === null) { + if (presentation.movements === null) {å //|| this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') } if (this._isPlaying) return; @@ -296,14 +303,20 @@ export class RecordingApi { // TODO: optimize only ons-first load // TODO: make async await // TODO: make sure the cahce still hs the id + // TODO: if they are open, set them to their first move // this will load the cache, so getCachedREfFields won't have to reach server DocServer.GetRefFields([...docIds]).then(refFields => { console.log('refFields', refFields) - const openTab = (docId: string) => { + + const openTab = (docId: string) : DocumentView | undefined => { const isInView = DocumentManager.Instance.getDocumentViewById(docId); if (isInView) { return isInView; } - const doc = DocServer.GetCachedRefField(docId); + const doc = DocServer.GetCachedRefField(docId) as Doc; + if (doc == undefined) { + console.warn('Doc server cache did not contain docId', docId) + return undefined; + } CollectionDockingView.AddSplit(doc, 'right'); return DocumentManager.Instance.getDocumentView(doc); } @@ -314,10 +327,12 @@ export class RecordingApi { return setTimeout(() => { // open tab if it is not already open const view = openTab(movement.docId); - // const ffView = view.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const collectionFFView = view?.ComponentView; - // replay the movement - zoomAndPan(movement, collectionFFView as CollectionFreeFormView); + if (view) { + const collectionFFView = view.ComponentView; + console.log(collectionFFView instanceof CollectionFreeFormView) + // replay the movement + zoomAndPan(movement, collectionFFView as CollectionFreeFormView); + } // if last movement, presentation is done -> set the instance var if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; }, timeDiff) diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a0e0f4f8d..3c5a42b7d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1011,7 +1011,7 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(this.layoutDoc._currentTimecode), time => { !this._playing && (vref.currentTime = time); - console.log("vref time = " + vref.currentTime) + // console.log("vref time = " + vref.currentTime) }, { fireImmediately: true }); } } -- cgit v1.2.3-70-g09d2 From 57cf1f2417858c203e1018d08e1626b1094ae68d Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Tue, 14 Jun 2022 21:47:41 -0400 Subject: change to using mobx to see when videoBox is paused or played --- src/client/util/RecordingApi.ts | 47 +++++++++++++++++++++-------------- src/client/views/nodes/VideoBox.tsx | 49 +++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 39 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 4bbaa9a79..48ea12fd9 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -61,7 +61,7 @@ export class RecordingApi { this.tabChangeDisposeFunc = null; // for now, set playFFView - this.playFFView = null; + // this.playFFView = null; this.timers = null; } @@ -232,19 +232,11 @@ export class RecordingApi { // TODO: extract this into different class with pause and resume recording // TODO: store the FFview with the movements - private playFFView: CollectionFreeFormView | null; - private timers: NodeJS.Timeout[] | null; - - public setPlayFFView = (view: CollectionFreeFormView): void => { - this.playFFView = view - } + private timers: NodeJS.Timeout[] | null; // pausing movements will dispose all timers that are planned to replay the movements // play movemvents will recreate them when the user resumes the presentation public pauseMovements = (): undefined | Error => { - if (this.playFFView === null) { - // return new Error('[recordingApi.ts] pauseMovements() failed: no view') - } if (!this._isPlaying) { //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') @@ -257,21 +249,41 @@ export class RecordingApi { // this.videoBox = null; } - private videoBox: VideoBox | null = null; + private videoBoxDisposeFunc: IReactionDisposer | null = null; + + setVideoBox = (videoBox: VideoBox) => { + console.log('setVideoBox', videoBox); + if (this.videoBoxDisposeFunc !== null) { console.warn('setVideoBox on already videoBox'); this.videoBoxDisposeFunc(); } + this.videoBoxDisposeFunc = + reaction(() => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }), + ({ playing, timeViewed }) => + playing ? this.playMovements(videoBox.presentation, timeViewed) : this.pauseMovements() + ); + } + + removeVideoBox = () => { + if (this.videoBoxDisposeFunc == null) { console.warn('removeVideoBox on null videoBox'); return; } + this.videoBoxDisposeFunc(); + } + // by calling pause on the VideoBox, the pauseMovements will be called - public pauseVideoAndMovements = (): boolean => { - this.videoBox?.Pause() + public pauseVideoAndMovements = () => { + // this.videoBox?.Pause() this.pauseMovements() - return this.videoBox == null + // return this.videoBox == null } + + public _isPlaying = false; public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { - if (presentation.movements === null) {å //|| this.playFFView === null) { - return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view') + console.log('playMovements', presentation, timeViewed, videoBox); + + if (presentation.movements === null) { //|| this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data') } if (this._isPlaying) return; @@ -279,13 +291,12 @@ export class RecordingApi { Doc.UserDoc().presentationMode = 'watching'; // TODO: consider this bug at the end of the clip on seek - this.videoBox = videoBox || null; + // this.videoBox = videoBox || null; // only get the movements that are remaining in the video time left const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) // helper to replay a movement - const document = this.playFFView let preScale = -1; const zoomAndPan = (movement: Movement, document: CollectionFreeFormView) => { const { panX, panY, scale } = movement; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index cf3282d2f..ed9bcf29b 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -144,13 +144,23 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._disposers[d]?.()); + this.removeCurrentlyPlaying(); + this.Pause(); + Object.keys(this._disposers).forEach(d => this._disposers[d]?.()); + + // dispose the recordingApi's observer + if (this.presentation != null) { + RecordingApi.Instance.removeVideoBox(); + } } @@ -162,13 +172,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (this.presentation) { - console.log('VideoBox : Pause'); - const err = RecordingApi.Instance.pauseMovements(); - err && console.log(err); - } + // if (this.presentation) { + // console.log('VideoBox : Pause'); + // const err = RecordingApi.Instance.pauseMovements(); + // err && console.log(err); + // } this._playing = false; this.removeCurrentlyPlaying(); @@ -437,12 +447,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - // console.log("PLAY from CONTENT") - //this.Play() - }} + onPlay={() => this.Play()} onSeeked={this.updateTimecode} - // onPause={() => this.Pause() } + onPause={() => this.Pause() } onClick={e => e.preventDefault()}> Not supported. -- cgit v1.2.3-70-g09d2 From 7ad93167a216acfe4992eed6b3ac3263c2dfd012 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 13:06:04 -0400 Subject: refactored trackingtabs code to be used for replaying a presentation --- src/client/util/RecordingApi.ts | 77 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 8 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 48ea12fd9..b2432af6b 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -22,6 +22,8 @@ type Movement = { docId: string, } +type tabReactionFunction = (res : { x: number; y: number; scale: number; key: string; }) => void; + export type Presentation = { movements: Movement[] | null, @@ -57,7 +59,7 @@ export class RecordingApi { this.absoluteStart = -1; // used for tracking movements in the view frame - this.recordingFFViews = null; + this.recordingFFViews = new Map(); this.tabChangeDisposeFunc = null; // for now, set playFFView @@ -83,6 +85,22 @@ export class RecordingApi { this.recordingFFViews?.set(key, disposeFunc); } + private addRecordingFFViewTest(doc: Doc, reactionFunc: any): void { + console.info('adding dispose funcTest : key', doc[Id], 'reactionFunc', reactionFunc); + const key = doc[Id]; + + if (this.recordingFFViews === null) { console.warn('addFFViewTest on null RecordingApi'); return; } + console.log('addFFViewTest : key', key, 'map', this.recordingFFViews); + if (this.recordingFFViews.has(key)) { console.warn('addFFViewTest : key already in map'); return; } + + const disposeFunc = reaction( + () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0), key: doc[Id] }), + (res) => reactionFunc(res), + ); + + this.recordingFFViews?.set(key, disposeFunc); + } + private removeRecordingFFView = (key: string) => { console.info('removing dispose func : docId', key); if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } @@ -90,6 +108,42 @@ export class RecordingApi { this.recordingFFViews.delete(key); } + // in the case where only one tab was changed (updates not across dashboards), set only one to true + private updateRecordingFFViewsFromTabsTest = (tabbedDocs: Doc[], reactionFunc: any, onlyOne = false) => { + if (this.recordingFFViews === null) return; + + // so that the size comparisons are correct, we must filter to only the FFViews + const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform'; + const tabbedFFViews = new Set(); + for (const DashDoc of tabbedDocs) { + if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]); + } + + + // new tab was added - need to add it + if (tabbedFFViews.size > this.recordingFFViews.size) { + for (const DashDoc of tabbedDocs) { + if (!this.recordingFFViews.has(DashDoc[Id])) { + if (isFFView(DashDoc)) { + this.addRecordingFFViewTest(DashDoc, reactionFunc); + + // only one max change, so return + if (onlyOne) return; + } + } + } + } + // tab was removed - need to remove it from recordingFFViews + else if (tabbedFFViews.size < this.recordingFFViews.size) { + for (const [key] of this.recordingFFViews) { + if (!tabbedFFViews.has(key)) { + this.removeRecordingFFView(key); + if (onlyOne) return; + } + } + } + } + // in the case where only one tab was changed (updates not across dashboards), set only one to true private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => { if (this.recordingFFViews === null) return; @@ -126,11 +180,10 @@ export class RecordingApi { } } - public start = (meta?: Object) => { + private initTabWatcher = (reactionFunc: tabReactionFunction) => { // init the dispose funcs on the page - this.recordingFFViews = new Map(); const docList = DocListCast(CollectionDockingView.Instance.props.Document.data); - this.updateRecordingFFViewsFromTabs(docList); + this.updateRecordingFFViewsFromTabsTest(docList, reactionFunc); // create a reaction to monitor changes in tabs this.tabChangeDisposeFunc = @@ -138,8 +191,14 @@ export class RecordingApi { (change) => { // TODO: consider changing between dashboards console.log('change in tabs', change); - this.updateRecordingFFViewsFromTabs(DocListCast(change), true); - }); + this.updateRecordingFFViewsFromTabsTest(DocListCast(change), reactionFunc, true); + }); + } + + public start = (meta?: Object) => { + + const reactionFunc: tabReactionFunction = (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.key, res.scale) + this.initTabWatcher(reactionFunc); // update the presentation mode Doc.UserDoc().presentationMode = 'recording'; @@ -171,7 +230,7 @@ export class RecordingApi { // reset the current presentation clearData && this.clear(); - console.log('yieldPresentation', cpy); + console.log('yieldPresentation', cpy, 'map', this.recordingFFViews); return cpy; } @@ -290,6 +349,8 @@ export class RecordingApi { this._isPlaying = true; Doc.UserDoc().presentationMode = 'watching'; + // setup the reaction on tabs that will pause the video if the user interacts with a tab + // TODO: consider this bug at the end of the clip on seek // this.videoBox = videoBox || null; @@ -315,7 +376,7 @@ export class RecordingApi { // TODO: make async await // TODO: make sure the cahce still hs the id // TODO: if they are open, set them to their first move - // this will load the cache, so getCachedREfFields won't have to reach server + // this will load the cache, so getCachedRefFields won't have to reach server DocServer.GetRefFields([...docIds]).then(refFields => { console.log('refFields', refFields) -- cgit v1.2.3-70-g09d2 From 1192b1c92ed17cda0a82342324ec82552da579a0 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 13:37:44 -0400 Subject: not working - moving this to a private branch --- src/client/util/RecordingApi.ts | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index b2432af6b..3c375f663 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -22,7 +22,7 @@ type Movement = { docId: string, } -type tabReactionFunction = (res : { x: number; y: number; scale: number; key: string; }) => void; +type tabReactionFunction = (res : { x: number; y: number; scale: number; key: string; d: any }) => void; export type Presentation = { @@ -94,7 +94,7 @@ export class RecordingApi { if (this.recordingFFViews.has(key)) { console.warn('addFFViewTest : key already in map'); return; } const disposeFunc = reaction( - () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0), key: doc[Id] }), + () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0), key: doc[Id], d: doc }), (res) => reactionFunc(res), ); @@ -350,6 +350,7 @@ export class RecordingApi { Doc.UserDoc().presentationMode = 'watching'; // setup the reaction on tabs that will pause the video if the user interacts with a tab + // this.initTabWatcher((res) => console.log(res)); // TODO: consider this bug at the end of the clip on seek // this.videoBox = videoBox || null; @@ -377,7 +378,8 @@ export class RecordingApi { // TODO: make sure the cahce still hs the id // TODO: if they are open, set them to their first move // this will load the cache, so getCachedRefFields won't have to reach server - DocServer.GetRefFields([...docIds]).then(refFields => { + let docIds2 = [...docIds]; + DocServer.GetRefFields(docIds2).then(refFields => { console.log('refFields', refFields) const openTab = (docId: string) : DocumentView | undefined => { @@ -393,6 +395,18 @@ export class RecordingApi { return DocumentManager.Instance.getDocumentView(doc); } + const views = docIds2.map(docId => openTab(docId)); + const views2 = views.filter(view => view?.ComponentView).map(view => view?.ComponentView as CollectionFreeFormView); + console.log('all views', views, 'allFFviews', views2) + + const disposers = views2.map(ccFFView => { + return reaction(() => ({coords: ccFFView._pullCoords, drag: ccFFView.setPan}), (res) => console.log(res)); + }); + + console.log(disposers) + + + // make timers that will execute each movement at the correct replay time this.timers = filteredMovements.map(movement => { const timeDiff = movement.time - timeViewed * 1000 @@ -406,7 +420,10 @@ export class RecordingApi { zoomAndPan(movement, collectionFFView as CollectionFreeFormView); } // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + if (movement === filteredMovements[filteredMovements.length - 1]) { + RecordingApi.Instance._isPlaying = false; + this.removeAllRecordingFFViews(); + } }, timeDiff) }); }) -- cgit v1.2.3-70-g09d2 From b27d5bd35444c666e5f95c3b744ea960bcbfe2ac Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 13:38:41 -0400 Subject: Revert "not working - moving this to a private branch" This reverts commit 1192b1c92ed17cda0a82342324ec82552da579a0. mobx tracks my movements, so need to add one line of code to ccffview to get it to work --- src/client/util/RecordingApi.ts | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 3c375f663..b2432af6b 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -22,7 +22,7 @@ type Movement = { docId: string, } -type tabReactionFunction = (res : { x: number; y: number; scale: number; key: string; d: any }) => void; +type tabReactionFunction = (res : { x: number; y: number; scale: number; key: string; }) => void; export type Presentation = { @@ -94,7 +94,7 @@ export class RecordingApi { if (this.recordingFFViews.has(key)) { console.warn('addFFViewTest : key already in map'); return; } const disposeFunc = reaction( - () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0), key: doc[Id], d: doc }), + () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0), key: doc[Id] }), (res) => reactionFunc(res), ); @@ -350,7 +350,6 @@ export class RecordingApi { Doc.UserDoc().presentationMode = 'watching'; // setup the reaction on tabs that will pause the video if the user interacts with a tab - // this.initTabWatcher((res) => console.log(res)); // TODO: consider this bug at the end of the clip on seek // this.videoBox = videoBox || null; @@ -378,8 +377,7 @@ export class RecordingApi { // TODO: make sure the cahce still hs the id // TODO: if they are open, set them to their first move // this will load the cache, so getCachedRefFields won't have to reach server - let docIds2 = [...docIds]; - DocServer.GetRefFields(docIds2).then(refFields => { + DocServer.GetRefFields([...docIds]).then(refFields => { console.log('refFields', refFields) const openTab = (docId: string) : DocumentView | undefined => { @@ -395,18 +393,6 @@ export class RecordingApi { return DocumentManager.Instance.getDocumentView(doc); } - const views = docIds2.map(docId => openTab(docId)); - const views2 = views.filter(view => view?.ComponentView).map(view => view?.ComponentView as CollectionFreeFormView); - console.log('all views', views, 'allFFviews', views2) - - const disposers = views2.map(ccFFView => { - return reaction(() => ({coords: ccFFView._pullCoords, drag: ccFFView.setPan}), (res) => console.log(res)); - }); - - console.log(disposers) - - - // make timers that will execute each movement at the correct replay time this.timers = filteredMovements.map(movement => { const timeDiff = movement.time - timeViewed * 1000 @@ -420,10 +406,7 @@ export class RecordingApi { zoomAndPan(movement, collectionFFView as CollectionFreeFormView); } // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) { - RecordingApi.Instance._isPlaying = false; - this.removeAllRecordingFFViews(); - } + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; }, timeDiff) }); }) -- cgit v1.2.3-70-g09d2 From bd35489c33501ff465e6ddd3cb943a748bfe546e Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 13:39:17 -0400 Subject: Revert "refactored trackingtabs code to be used for replaying a presentation" This reverts commit 7ad93167a216acfe4992eed6b3ac3263c2dfd012. didn't need this refactor since mobx tracks my movements --- src/client/util/RecordingApi.ts | 77 +++++------------------------------------ 1 file changed, 8 insertions(+), 69 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index b2432af6b..48ea12fd9 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -22,8 +22,6 @@ type Movement = { docId: string, } -type tabReactionFunction = (res : { x: number; y: number; scale: number; key: string; }) => void; - export type Presentation = { movements: Movement[] | null, @@ -59,7 +57,7 @@ export class RecordingApi { this.absoluteStart = -1; // used for tracking movements in the view frame - this.recordingFFViews = new Map(); + this.recordingFFViews = null; this.tabChangeDisposeFunc = null; // for now, set playFFView @@ -85,22 +83,6 @@ export class RecordingApi { this.recordingFFViews?.set(key, disposeFunc); } - private addRecordingFFViewTest(doc: Doc, reactionFunc: any): void { - console.info('adding dispose funcTest : key', doc[Id], 'reactionFunc', reactionFunc); - const key = doc[Id]; - - if (this.recordingFFViews === null) { console.warn('addFFViewTest on null RecordingApi'); return; } - console.log('addFFViewTest : key', key, 'map', this.recordingFFViews); - if (this.recordingFFViews.has(key)) { console.warn('addFFViewTest : key already in map'); return; } - - const disposeFunc = reaction( - () => ({ x: NumCast(doc.panX, -1), y: NumCast(doc.panY, -1), scale: NumCast(doc.viewScale, 0), key: doc[Id] }), - (res) => reactionFunc(res), - ); - - this.recordingFFViews?.set(key, disposeFunc); - } - private removeRecordingFFView = (key: string) => { console.info('removing dispose func : docId', key); if (this.recordingFFViews === null) { console.warn('removeFFView on null RecordingApi'); return; } @@ -108,42 +90,6 @@ export class RecordingApi { this.recordingFFViews.delete(key); } - // in the case where only one tab was changed (updates not across dashboards), set only one to true - private updateRecordingFFViewsFromTabsTest = (tabbedDocs: Doc[], reactionFunc: any, onlyOne = false) => { - if (this.recordingFFViews === null) return; - - // so that the size comparisons are correct, we must filter to only the FFViews - const isFFView = (doc: Doc) => doc && 'viewType' in doc && doc.viewType === 'freeform'; - const tabbedFFViews = new Set(); - for (const DashDoc of tabbedDocs) { - if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc[Id]); - } - - - // new tab was added - need to add it - if (tabbedFFViews.size > this.recordingFFViews.size) { - for (const DashDoc of tabbedDocs) { - if (!this.recordingFFViews.has(DashDoc[Id])) { - if (isFFView(DashDoc)) { - this.addRecordingFFViewTest(DashDoc, reactionFunc); - - // only one max change, so return - if (onlyOne) return; - } - } - } - } - // tab was removed - need to remove it from recordingFFViews - else if (tabbedFFViews.size < this.recordingFFViews.size) { - for (const [key] of this.recordingFFViews) { - if (!tabbedFFViews.has(key)) { - this.removeRecordingFFView(key); - if (onlyOne) return; - } - } - } - } - // in the case where only one tab was changed (updates not across dashboards), set only one to true private updateRecordingFFViewsFromTabs = (tabbedDocs: Doc[], onlyOne = false) => { if (this.recordingFFViews === null) return; @@ -180,10 +126,11 @@ export class RecordingApi { } } - private initTabWatcher = (reactionFunc: tabReactionFunction) => { + public start = (meta?: Object) => { // init the dispose funcs on the page + this.recordingFFViews = new Map(); const docList = DocListCast(CollectionDockingView.Instance.props.Document.data); - this.updateRecordingFFViewsFromTabsTest(docList, reactionFunc); + this.updateRecordingFFViewsFromTabs(docList); // create a reaction to monitor changes in tabs this.tabChangeDisposeFunc = @@ -191,14 +138,8 @@ export class RecordingApi { (change) => { // TODO: consider changing between dashboards console.log('change in tabs', change); - this.updateRecordingFFViewsFromTabsTest(DocListCast(change), reactionFunc, true); - }); - } - - public start = (meta?: Object) => { - - const reactionFunc: tabReactionFunction = (res) => (res.x !== -1 && res.y !== -1 && this.tracking) && this.trackMovements(res.x, res.y, res.key, res.scale) - this.initTabWatcher(reactionFunc); + this.updateRecordingFFViewsFromTabs(DocListCast(change), true); + }); // update the presentation mode Doc.UserDoc().presentationMode = 'recording'; @@ -230,7 +171,7 @@ export class RecordingApi { // reset the current presentation clearData && this.clear(); - console.log('yieldPresentation', cpy, 'map', this.recordingFFViews); + console.log('yieldPresentation', cpy); return cpy; } @@ -349,8 +290,6 @@ export class RecordingApi { this._isPlaying = true; Doc.UserDoc().presentationMode = 'watching'; - // setup the reaction on tabs that will pause the video if the user interacts with a tab - // TODO: consider this bug at the end of the clip on seek // this.videoBox = videoBox || null; @@ -376,7 +315,7 @@ export class RecordingApi { // TODO: make async await // TODO: make sure the cahce still hs the id // TODO: if they are open, set them to their first move - // this will load the cache, so getCachedRefFields won't have to reach server + // this will load the cache, so getCachedREfFields won't have to reach server DocServer.GetRefFields([...docIds]).then(refFields => { console.log('refFields', refFields) -- cgit v1.2.3-70-g09d2 From 5972716e64fd9a006fa2139a40b03c21b503dd04 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 13:50:33 -0400 Subject: fix small disposer bug and reintroduce pausing on interaction --- src/client/util/RecordingApi.ts | 26 +++++++++++++--------- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +++- 2 files changed, 18 insertions(+), 12 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index 48ea12fd9..ae5431a03 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -57,7 +57,7 @@ export class RecordingApi { this.absoluteStart = -1; // used for tracking movements in the view frame - this.recordingFFViews = null; + this.recordingFFViews = new Map(); this.tabChangeDisposeFunc = null; // for now, set playFFView @@ -126,9 +126,8 @@ export class RecordingApi { } } - public start = (meta?: Object) => { + public initTabTracker = () => { // init the dispose funcs on the page - this.recordingFFViews = new Map(); const docList = DocListCast(CollectionDockingView.Instance.props.Document.data); this.updateRecordingFFViewsFromTabs(docList); @@ -140,7 +139,9 @@ export class RecordingApi { console.log('change in tabs', change); this.updateRecordingFFViewsFromTabs(DocListCast(change), true); }); + } + public start = (meta?: Object) => { // update the presentation mode Doc.UserDoc().presentationMode = 'recording'; @@ -238,10 +239,7 @@ export class RecordingApi { // play movemvents will recreate them when the user resumes the presentation public pauseMovements = (): undefined | Error => { - if (!this._isPlaying) { - //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') - return - } + if (!this._isPlaying) { console.warn('[recordingApi.ts] pauseMovements(): already on paused'); return;} this._isPlaying = false // TODO: set userdoc presentMode to browsing this.timers?.map(timer => clearTimeout(timer)) @@ -250,6 +248,7 @@ export class RecordingApi { } private videoBoxDisposeFunc: IReactionDisposer | null = null; + private videoBox: VideoBox | null = null; setVideoBox = (videoBox: VideoBox) => { console.log('setVideoBox', videoBox); @@ -258,20 +257,25 @@ export class RecordingApi { reaction(() => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }), ({ playing, timeViewed }) => playing ? this.playMovements(videoBox.presentation, timeViewed) : this.pauseMovements() - ); + ); + this.videoBox = videoBox; } removeVideoBox = () => { if (this.videoBoxDisposeFunc == null) { console.warn('removeVideoBox on null videoBox'); return; } this.videoBoxDisposeFunc(); + + this.videoBox = null; + this.videoBoxDisposeFunc = null; } // by calling pause on the VideoBox, the pauseMovements will be called - public pauseVideoAndMovements = () => { - // this.videoBox?.Pause() + public pauseFromInteraction = () => { + Doc.UserDoc().presentationMode = 'none'; + this.videoBox?.Pause(); - this.pauseMovements() + this.pauseMovements(); // return this.videoBox == null } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3c5a42b7d..7db3b1482 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1009,7 +1009,7 @@ export class CollectionFreeFormView extends CollectionSubView will talk with Bob about using mobx to do this to remove this line of code. + if (Doc.UserDoc()?.presentationMode === 'watching') RecordingApi.Instance.pauseFromInteraction(); if (!this.isAnnotationOverlay && clamp) { // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds -- cgit v1.2.3-70-g09d2 From fafc1fc678433a95240e8ecefa1b31b7912d5993 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Wed, 15 Jun 2022 15:18:14 -0400 Subject: refactor code, fix bugs, make first movement on screen the beginning of the presentation. --- src/client/util/RecordingApi.ts | 195 +++++++++++++++++++++++++----------- src/client/views/nodes/VideoBox.tsx | 150 +++++++++++++-------------- 2 files changed, 210 insertions(+), 135 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts index ae5431a03..87cb85497 100644 --- a/src/client/util/RecordingApi.ts +++ b/src/client/util/RecordingApi.ts @@ -57,7 +57,7 @@ export class RecordingApi { this.absoluteStart = -1; // used for tracking movements in the view frame - this.recordingFFViews = new Map(); + this.recordingFFViews = null; this.tabChangeDisposeFunc = null; // for now, set playFFView @@ -127,6 +127,10 @@ export class RecordingApi { } public initTabTracker = () => { + if (this.recordingFFViews === null) { + this.recordingFFViews = new Map(); + } + // init the dispose funcs on the page const docList = DocListCast(CollectionDockingView.Instance.props.Document.data); this.updateRecordingFFViewsFromTabs(docList); @@ -142,6 +146,8 @@ export class RecordingApi { } public start = (meta?: Object) => { + this.initTabTracker(); + // update the presentation mode Doc.UserDoc().presentationMode = 'recording'; @@ -190,6 +196,9 @@ export class RecordingApi { this.tabChangeDisposeFunc?.(); // update the presentation mode now that we are done tracking Doc.UserDoc().presentationMode = 'none'; + + this.recordingFFViews = null; + this.tabChangeDisposeFunc = null; } // clear presenation data @@ -250,13 +259,28 @@ export class RecordingApi { private videoBoxDisposeFunc: IReactionDisposer | null = null; private videoBox: VideoBox | null = null; - setVideoBox = (videoBox: VideoBox) => { + setVideoBox = async (videoBox: VideoBox) => { console.log('setVideoBox', videoBox); - if (this.videoBoxDisposeFunc !== null) { console.warn('setVideoBox on already videoBox'); this.videoBoxDisposeFunc(); } + if (videoBox !== null) { console.warn('setVideoBox on already videoBox'); } + if (this.videoBoxDisposeFunc !== null) { console.warn('setVideoBox on already videoBox dispose func'); this.videoBoxDisposeFunc(); } + + + const { presentation } = videoBox; + if (presentation == null) { console.warn('setVideoBox on null videoBox presentation'); return; } + + let docIdtoDoc: Map = new Map(); + try { + docIdtoDoc = await this.loadPresentation(presentation); + } catch { + console.error('[recordingApi.ts] setVideoBox(): error loading presentation - no replay movements'); + throw 'error loading docs from server'; + } + + this.videoBoxDisposeFunc = reaction(() => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }), ({ playing, timeViewed }) => - playing ? this.playMovements(videoBox.presentation, timeViewed) : this.pauseMovements() + playing ? this.playMovements(presentation, docIdtoDoc, timeViewed) : this.pauseMovements() ); this.videoBox = videoBox; } @@ -283,10 +307,81 @@ export class RecordingApi { public _isPlaying = false; - public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { - console.log('playMovements', presentation, timeViewed, videoBox); + loadPresentation = async (presentation: Presentation) => { + const { movements } = presentation; + if (movements === null) { + throw '[recordingApi.ts] followMovements() failed: no presentation data'; + } + + // generate a set of all unique docIds + const docIds = new Set(); + for (const {docId} of movements) { + if (!docIds.has(docId)) docIds.add(docId); + } + + const docIdtoDoc = new Map(); + + let refFields = await DocServer.GetRefFields([...docIds.keys()]); + for (const docId in refFields) { + if (!refFields[docId]) { + throw `one field was undefined`; + } + docIdtoDoc.set(docId, refFields[docId] as Doc); + } + console.log('loadPresentation refFields', refFields, docIdtoDoc); + + return docIdtoDoc; + } + + // returns undefined if the docView isn't open on the screen + getCollectionFFView = (docId: string) => { + const isInView = DocumentManager.Instance.getDocumentViewById(docId); + if (isInView) { return isInView.ComponentView as CollectionFreeFormView; } + } + + // will open the doc in a tab then return the CollectionFFView that holds it + openTab = (docId: string, docIdtoDoc: Map) => { + const doc = docIdtoDoc.get(docId); + if (doc == undefined) { + console.error(`docIdtoDoc did not contain docId ${docId}`) + return undefined; + } + CollectionDockingView.AddSplit(doc, 'right'); + const docView = DocumentManager.Instance.getDocumentViewById(docId); + return docView?.ComponentView as CollectionFreeFormView; + } + + // helper to replay a movement + private preScale = -1; + zoomAndPan = (movement: Movement, document: CollectionFreeFormView) => { + const { panX, panY, scale } = movement; + (scale !== 0 && this.preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + this.preScale = scale; + } + + getFirstMovements = (movements: Movement[], timeViewed: number): Map => { + if (movements === null) return new Map(); + // generate a set of all unique docIds + const docIdtoFirstMove = new Map(); + for (const move of movements) { + const { docId } = move; + if (!docIdtoFirstMove.has(docId)) docIdtoFirstMove.set(docId, move); + } + return docIdtoFirstMove; + } + + endPlayingPresentation = () => { + this.preScale = -1; + RecordingApi.Instance._isPlaying = false; + } + + public playMovements = (presentation: Presentation, docIdtoDoc: Map, timeViewed: number = 0) => { + console.log('playMovements', presentation, timeViewed, docIdtoDoc); - if (presentation.movements === null) { //|| this.playFFView === null) { + if (presentation.movements === null || presentation.movements.length === 0) { //|| this.playFFView === null) { return new Error('[recordingApi.ts] followMovements() failed: no presentation data') } if (this._isPlaying) return; @@ -294,65 +389,45 @@ export class RecordingApi { this._isPlaying = true; Doc.UserDoc().presentationMode = 'watching'; - // TODO: consider this bug at the end of the clip on seek - // this.videoBox = videoBox || null; - // only get the movements that are remaining in the video time left const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000) - // helper to replay a movement - let preScale = -1; - const zoomAndPan = (movement: Movement, document: CollectionFreeFormView) => { - const { panX, panY, scale } = movement; - (scale !== 0 && preScale !== scale) && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); - document.Document._panX = panX; - document.Document._panY = panY; - - preScale = scale; + const handleFirstMovements = () => { + // if the first movement is a closed tab, open it + const firstMovement = filteredMovements[0]; + const isClosed = this.getCollectionFFView(firstMovement.docId) === undefined; + if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc); + + // for the open tabs, set it to the first move + const docIdtoFirstMove = this.getFirstMovements(filteredMovements, timeViewed); + for (const [docId, firstMove] of docIdtoFirstMove) { + const colFFView = this.getCollectionFFView(docId); + if (colFFView) this.zoomAndPan(firstMove, colFFView); + } } - // set the first frame to be at the start of the pres - // zoomAndPan(filteredMovements[0]); - - // generate a set of all unique docIds - const docIds = new Set(filteredMovements.map(movement => movement.docId)) - // TODO: optimize only ons-first load - // TODO: make async await - // TODO: make sure the cahce still hs the id - // TODO: if they are open, set them to their first move - // this will load the cache, so getCachedREfFields won't have to reach server - DocServer.GetRefFields([...docIds]).then(refFields => { - console.log('refFields', refFields) - - const openTab = (docId: string) : DocumentView | undefined => { - const isInView = DocumentManager.Instance.getDocumentViewById(docId); - if (isInView) { return isInView; } - - const doc = DocServer.GetCachedRefField(docId) as Doc; - if (doc == undefined) { - console.warn('Doc server cache did not contain docId', docId) - return undefined; + handleFirstMovements(); + + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed * 1000 + + return setTimeout(() => { + const collectionFFView = this.getCollectionFFView(movement.docId); + if (collectionFFView) { + this.zoomAndPan(movement, collectionFFView); + } else { + // tab wasn't open - open it and play the movement + const openedColFFView = this.openTab(movement.docId, docIdtoDoc); + openedColFFView && this.zoomAndPan(movement, openedColFFView); } - CollectionDockingView.AddSplit(doc, 'right'); - return DocumentManager.Instance.getDocumentView(doc); - } - // make timers that will execute each movement at the correct replay time - this.timers = filteredMovements.map(movement => { - const timeDiff = movement.time - timeViewed * 1000 - return setTimeout(() => { - // open tab if it is not already open - const view = openTab(movement.docId); - if (view) { - const collectionFFView = view.ComponentView; - console.log(collectionFFView instanceof CollectionFreeFormView) - // replay the movement - zoomAndPan(movement, collectionFFView as CollectionFreeFormView); - } - // if last movement, presentation is done -> set the instance var - if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; - }, timeDiff) - }); - }) + // if last movement, presentation is done -> cleanup :) + if (movement === filteredMovements[filteredMovements.length - 1]) { + this.endPlayingPresentation(); + } + }, timeDiff); + }); } // method that concatenates an array of presentatations into one diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ed9bcf29b..34df03954 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -28,7 +28,7 @@ import { AnchorMenu } from "../pdf/AnchorMenu"; import { StyleProp } from "../StyleProvider"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; -import { RecordingApi } from "../../util/RecordingApi"; +import { Presentation, RecordingApi } from "../../util/RecordingApi"; import { List } from "../../../fields/List"; import { RecordingBox } from "./RecordingBox"; const path = require('path'); @@ -48,80 +48,80 @@ const path = require('path'); @observer export class VideoBox extends ViewBoxAnnotatableComponent() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } - /** - * Uploads an image buffer to the server and stores with specified filename. by default the image - * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large) - * @param imageUri the bytes of the image - * @param returnedFilename the base filename to store the image on the server - * @param nosuffix optionally suppress creating multiple resolution images - */ - public static async convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) { - try { - const posting = Utils.prepend("/uploadURI"); - const returnedUri = await rp.post(posting, { - body: { - uri: imageUri, - name: returnedFilename, - nosuffix, - replaceRootFilename - }, - json: true, - }); - return returnedUri; - - } catch (e) { - console.log("VideoBox :" + e); - } - } - - static _youtubeIframeCounter: number = 0; - static heightPercent = 80; // height of video relative to videoBox when timeline is open - private _disposers: { [name: string]: IReactionDisposer } = {}; - private _youtubePlayer: YT.Player | undefined = undefined; - private _videoRef: HTMLVideoElement | null = null; //
    -
    0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} +
    + {this.props.value}
    ; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index a1b9023f3..97e6eddfe 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
    {this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
    ); break; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f29b879b3..16a523b40 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -382,11 +382,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - // creates links between terms in a document and documents which have a matching Id + // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6abc27b23..7b72787d1 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1436,6 +1436,7 @@ ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.cop ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 7e2aa5681..bf40a0d7b 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -76,6 +76,10 @@ export function Cast(field: FieldResult, ctor: T, defaultVal return defaultVal === null ? undefined : defaultVal; } +export function DocCast(field: FieldResult, defaultVal?: Doc) { + return Cast(field, Doc, null) ?? defaultVal; +} + export function NumCast(field: FieldResult, defaultVal: number | null = 0) { return Cast(field, "number", defaultVal); } -- cgit v1.2.3-70-g09d2 From c0826fb904f5d2448635b5dae1fc4337fead939e Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 05:31:25 -0400 Subject: lots of changes to try to cleanup CurrentUserUtils so changes can be made without rebuilding the DB. --- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 1216 +++++++++----------- src/client/util/DocumentManager.ts | 3 +- src/client/util/Scripting.ts | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/MainView.tsx | 36 +- src/client/views/OverlayView.tsx | 2 +- src/client/views/PropertiesView.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 2 +- src/client/views/collections/CollectionMenu.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collectionLinear/CollectionLinearView.tsx | 4 +- src/client/views/linking/LinkPopup.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/FilterBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 12 +- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 8 +- src/fields/Doc.ts | 5 +- src/fields/util.ts | 3 + src/mobile/MobileInterface.tsx | 2 +- 22 files changed, 629 insertions(+), 711 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5f009573e..927996af2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -178,10 +178,15 @@ export class DocumentOptions { hidden?: boolean; _hidden?: boolean; pointerEvents?: string; // pointer events that the documentview should have - mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing" + mediaState?: string; // status of audio/video media document: "pendingRecording", "recording", "paused", "playing" + recording?: boolean; // whether WebCam is recording or not autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. toolTip?: string; // tooltip to display on hover + contextMenuFilters?: List; + contextMenuScripts?: List; + contextMenuLabels?: List; + contextMenuIcons?: List; dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links layout?: string | Doc; // default layout string for a document @@ -238,7 +243,7 @@ export class DocumentOptions { isPushpin?: boolean; isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document - + noteType?: string; // BACKGROUND GRID _backgroundGridShow?: boolean; @@ -286,6 +291,7 @@ export class DocumentOptions { clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop cloneFieldFilter?: List; // fields not to copy when the document is clonedclipboard?: Doc; + filterBoolean ?: string; useCors?: boolean; icon?: string; target?: Doc; // available for use in scripts as the primary target document diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b4e18a8bb..afb1442bf 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,16 +1,17 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, StrListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; +import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, DocCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types"; import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; -import { Utils } from "../../Utils"; +import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; @@ -42,17 +43,17 @@ interface Button { toolTip?: string; icon?: string; btnType?: ButtonType; - click?: string; numBtnType?: NumButtonType; numBtnMin?: number; numBtnMax?: number; switchToggle?: boolean; - script?: string; width?: number; - list?: string[]; + btnList?: List; ignoreClick?: boolean; buttonText?: string; - hidden?: string; + scripts?: { script?: string; onClick?: string; } + funcs?: { [key:string]: string }; + subMenu?: Button[]; } export let resolvedPorts: { server: number, socket: number }; @@ -73,101 +74,134 @@ export class CurrentUserUtils { @observable public static headerBarHeight: number = 0; @observable public static searchPanelWidth: number = 0; - // sets up the default User Templates - slideView, headerView - static setupExperimentalTemplateButtons(doc: Doc) { - if (doc["template-experimental-buttons"] === undefined) { - const requiredTypeNameFields = [ - { - type: "slide", icon: "address-card", template: Docs.Create.MultirowDocument( - [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) - ], - { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } - ) - }, - { - type: "mobile", icon: "mobile", template: this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, - [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) - ] - ) - }, - ]; - const requiredTypes = requiredTypeNameFields.map(({ type, icon, template }) => { - if (doc[`template-button-${type}`] === undefined) { - template.isTemplateDoc = makeTemplate(template); - doc[`template-button-${type}`] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(template) as any as Doc, - title: type, - icon, - }); + static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, functions?:{[key:string]: string}) { + scripts && Object.keys(scripts).map(key => { + if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { + doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); + } + }); + functions && Object.keys(functions).map(key => { + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + if (ScriptCast(cfield)?.script.originalScript !== functions[key] && functions[key]) { + doc[key] = ComputedField.MakeFunction(functions[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); + } + }); + return doc; + } + static AssignOpts(doc:Doc|undefined, reqdOpts:DocumentOptions, items?:Doc[]) { + if (doc) { + const compareValues = (val1:any, val2:any) => { + if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) { + return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v)); + } + return val1 === val2; + } + Object.entries(reqdOpts).forEach(pair => { + const targetDoc = pair[0].startsWith("_") ? doc : Doc.GetProto(doc as Doc); + if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/,"")) || + !compareValues(pair[1], targetDoc[pair[0]])) { + targetDoc[pair[0]] = pair[1]; } - return doc[`template-button-${type}`] as Doc; }); - - doc["template-experimental-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { - title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, - _stayInCollection: true, _hideContextMenu: true, _forceActive: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true - })); + items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), "data", item)); } - return doc["template-experimental-buttons"] as Doc; + return doc; } - // setup the different note type skins - static setupNoteTemplates(doc: Doc) { - if (doc["template-notes"] === undefined) { - const requiredTypeNameFields = [ - { type: "Note", backgroundColor: "yellow", icon: "sticky-note" }, - { type: "Idea", backgroundColor: "pink", icon: "lightbulb" }, - { type: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; - const requiredTypes = requiredTypeNameFields.map(({ type, backgroundColor, icon }) => - doc[`template-note-${type}`] as Doc ?? - (() => { - const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor, system: true, icon }); - noteView.isTemplateDoc = makeTemplate(noteView, true, type); - return doc[`template-note-${type}`] = new PrefetchProxy(noteView); - })()); - - doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument(requiredTypes, { title: "Note Layouts", _height: 75, system: true })); - } + // initializes experimental advanced template views - slideView, headerView + static setupExperimentalTemplateButtons(doc: Doc, field="template-experimental-buttons") { + const tempDocs = DocCast(doc[field]); + const requiredTypeNameFields:{opts:DocumentOptions, template:() => Doc}[] = [ + { + opts:{type: "slide", icon: "address-card"}, template: () => Docs.Create.MultirowDocument( + [ + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) + ], + { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } + ) + }, + { + opts:{type: "mobile", icon: "mobile"}, template: () => this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, + [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) + ] + ) + }, + ]; + const requiredTypes = requiredTypeNameFields.map(({ opts, template }) => { + const docType = DocListCast(tempDocs?.data)?.find(doc => doc.title === opts.type); + const reqdOpts = { + dragFactory: template(), + title: opts.type, + icon: opts.icon + }; + const reqdScripts = {onDragStart: 'copyDragFactory(this.dragFactory)'}; + const makeTemp = (doc:Doc) => { + doc.isTemplateDoc = makeTemplate(doc); + return doc; + } + return this.AssignScripts(!docType ? makeTemp(CurrentUserUtils.createToolButton(reqdOpts)) : this.AssignOpts(docType, reqdOpts)!, reqdScripts)!; + }); + + const reqdOpts = { + title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, + _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, + _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, + }; + const reqdScripts = {dropConverter : "convertToButtons(dragData)"}; + const reqdFuncs = {hidden: "IsNoviceMode()"} + return this.AssignScripts(!tempDocs ? + (doc[field] = Docs.Create.MasonryDocument(requiredTypes, reqdOpts)) : + this.AssignOpts(tempDocs, reqdOpts, requiredTypes)!, + reqdScripts, reqdFuncs); + } + + /// Initializes templates that can be applied to notes + static setupNoteTemplates(doc: Doc, field="template-notes") { + const tempNotes = DocCast(doc[field]); + const reqdTempOpts:DocumentOptions[] = [ + { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"}, + { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" }, + { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; + const reqdNoteList = reqdTempOpts.map(opts => { + const reqdOpts = {...opts, title: "text", system: true}; + const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined; + const makeTemp = (doc:Doc, noteType?:string) => { + doc.isTemplateDoc = makeTemplate(doc, true, noteType??"Note"); + return doc; + } + return this.AssignOpts(noteType, reqdOpts) ?? makeTemp(Docs.Create.TextDocument("",reqdOpts), opts.noteType); + }); - return doc["template-notes"] as Doc; + const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, system: true }; + return this.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); } - // creates Note templates, and initial "user" templates + /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc) { - if (doc.templateDocs === undefined) { - doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([ - CurrentUserUtils.setupNoteTemplates(doc), - CurrentUserUtils.setupExperimentalTemplateButtons(doc), - CurrentUserUtils.setupClickEditorTemplates(doc) - ], { - title: "template layouts", _xMargin: 0, system: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) - })); - } + const templates = [ + CurrentUserUtils.setupNoteTemplates(doc), + CurrentUserUtils.setupClickEditorTemplates(doc) + ]; + const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; + const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; + return this.AssignScripts(this.AssignOpts(DocCast(doc.templateDocs), reqdOpts, templates) ?? (doc.templateDocs = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts); } // setup templates for different document types when they are iconified from Document Decorations - static setupDefaultIconTemplates(doc: Doc) { - const templateIconsDoc = Cast(doc["template-icons"], Doc, null); + static setupDefaultIconTemplates(doc: Doc, field="template-icons") { + const templateIconsDoc = DocCast(doc[field]); const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => { const iconFieldName = "icon" + (type ? "_" + type : ""); if (!templateIconsDoc?.[iconFieldName]) { const template = MakeTemplate(iconTemplate(), true, iconFieldName, templateField); if (templateIconsDoc) { - templateIconsDoc[iconFieldName] = new PrefetchProxy(template); - Doc.AddDocToList(templateIconsDoc, "data", template); - } else { - return template; + templateIconsDoc[iconFieldName] = template; } + return template; } }; const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); @@ -182,46 +216,57 @@ export class CurrentUserUtils { const fontBox = () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() }); - if (!templateIconsDoc) { - const newIconsList = [ - makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), - makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), - makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), - makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), - makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), - makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), - makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.BUTTON, "data", fontBox), - //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now - makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })), - // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) - ].filter(d => d).map(d => d!); - - doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true }); - } + const iconTemplates = [ + makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), + makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), + makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), + makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), + makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), + makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), + makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.BUTTON, "data", fontBox), + //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now + makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })), + // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) + ].filter(d => d).map(d => d!); + + const reqdOpts = { title: "icon templates", _height: 75, system: true }; + this.AssignOpts(templateIconsDoc, reqdOpts, iconTemplates) ?? (doc[field] = Docs.Create.TreeDocument(iconTemplates, reqdOpts)); } + /// initalizes the set of default versions of most document types static creatorBtnDescriptors(doc: Doc): { - title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, - click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc + title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, + backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, }[] { const standardOps = () => ({ _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); - if (doc.emptyPresentation === undefined) { - doc.emptyPresentation = Docs.Create.PresDocument({ ...standardOps(), title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); - } - if (doc.emptyCollection === undefined) { - doc.emptyCollection = Docs.Create.FreeformDocument([], { ...standardOps(), title: "freeform", _width: 150, _height: 100 }); - } - if (doc.emptyPane === undefined) { - doc.emptyPane = Docs.Create.FreeformDocument([], { ...standardOps(), title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }); - } - if (doc.emptySlide === undefined) { - doc.emptySlide = Docs.Create.TreeDocument([], { - ...standardOps(), title: ComputedField.MakeFunction('self.text?.Text') as any, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined, - allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" - }); - } + const emptyThings:{key:string, // the field name where the empty thing will be stored + opts:DocumentOptions, // the document options that are required for the empty thing + funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing + creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist + }[] = [ + {key: "emptyPresentation", creator: Docs.Create.PresDocument, opts: { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, + {key: "emptyCollection", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "freeform", _width: 150, _height: 100 }}, + {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts : { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, + {key: "emptySlide", creator: (opts) => Docs.Create.TreeDocument([], opts), funcs: {title: 'self.text?.Text'}, opts: { + _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, + "dragFactory-count": undefined, allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" + }}, + {key: "emptyComparison", creator: Docs.Create.ComparisonDocument, opts: { title: "Comparer", _width: 300, _height: 300 }}, + {key: "emptyScript", creator: (opts) => Docs.Create.ScriptingDocument(undefined, opts), opts: { title: "script", _width: 200, _height: 250, }}, + {key: "emptyScreenshot", creator: Docs.Create.ScreenshotDocument, opts: { title: "empty screenshot", _width: 400, _height: 200 }}, + {key: "emptyWebCam", creator: (opts) => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, + {key: "emptyAudio", creator: (opts) => Docs.Create.AudioDocument(nullAudio, opts), opts: { title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, }}, + {key: "emptyNote", creator: (opts) => Docs.Create.TextDocument("", opts), opts: { title: "text note", _width: 200, _autoHeight: true }}, + {key: "emptyButton", creator: Docs.Create.ButtonDocument, opts: { title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, + {key: "emptyWebpage", creator: (opts) => Docs.Create.WebDocument("http://www.bing.com/", opts), opts: { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }}, + {key: "emptyMap", creator: (opts) => Docs.Create.MapDocument([], opts), opts: { title: "map", _showSidebar: true, _width: 800, _height: 600, }} + ]; + + emptyThings.forEach(thing => + this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), thing.funcs)) + if (doc.emptyHeader === undefined) { const json = { doc: { @@ -242,196 +287,124 @@ export class CurrentUserUtils { selection: { type: "text", anchor: 1, head: 1 }, storedMarks: [] }; - const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { - ...standardOps(), title: "text", _height: 70, - _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, - }, "header"); const headerBtnHgt = 10; - headerTemplate[DataSym].layout = + const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { + ...standardOps(), title: "text", _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, + layout: "" + ` ` + " " + ` Metadata` + - ""; + "" + }, "header"); // "
    " + // " " + // " " + // "
    "; - (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView"); + Doc.GetProto(headerTemplate).isTemplateDoc = makeTemplate(Doc.GetProto(headerTemplate), true, "headerView"); doc.emptyHeader = headerTemplate; } - if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "Comparer", _width: 300, _height: 300 }); - } - if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { ...standardOps(), title: "script", _width: 200, _height: 250, }); - } - if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "empty screenshot", _width: 400, _height: 200 }); - } - if (doc.emptyWall === undefined) { - doc.emptyWall = Docs.Create.WebCamDocument("", { _width: 400, _height: 200, title: "recording", system: true, cloneFieldFilter: new List(["system"]) }); - (doc.emptyWall as Doc).recording = true; - } - if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { ...standardOps(), title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, }); - } - if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { ...standardOps(), title: "text note", _width: 200, _autoHeight: true }); - } - if (doc.emptyButton === undefined) { - doc.emptyButton = Docs.Create.ButtonDocument({ ...standardOps(), title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }); - } - if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { ...standardOps(), title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }); - } - if (doc.emptyMap === undefined) { - doc.emptyMap = Docs.Create.MapDocument([], { ...standardOps(), title: "map", _showSidebar: true, _width: 800, _height: 600, }); - } - if (doc.activeMobileMenu === undefined) { - this.setupActiveMobileMenu(doc); - } + return [ - { toolTip: "Tap to create a note in a new pane, drag for a note", title: "Note", icon: "sticky-note", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyNote as Doc, noviceMode: true, clickFactory: doc.emptyNote as Doc, }, - { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, - { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, - { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc }, - { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, - { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc }, - { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc }, - { toolTip: "Tap to create an audio recorder in a overlay pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openInOverlay(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, - { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc }, - // { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, - { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc }, - { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, - { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc }, - { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { toolTip: "Tap to create a map in the new pane, drag for a map", title: "Map", icon: "map-marker-alt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyMap as Doc, noviceMode: true } - ]; - + { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, clickFactory: doc.emptyPane as Doc, }, + { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: {onClick: 'openInOverlay(copyDragFactory(this.dragFactory))',onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a custom note", title: "Custom", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a progressive slide",title: "Slide", icon: "file", dragFactory: doc.emptySlide as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreenshot as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs:{ hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a mobile view", title: "Phone", icon: "mobile", dragFactory: doc.activeMobileMenu as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: 'this.dragFactory'}, funcs: {hidden: 'IsNoviceMode()'} }, + { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, + // { toolTip: "Tap or drag to create a presentation", title: "Trails", icon: "pres-trail", dragFactory: doc.emptyPresentation as Doc,scripts: {onClick: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', onDragStart: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`}, funcs: {hidden: 'IsNoviceMode()'} }, + ]; } - // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools - static setupCreatorButtons(doc: Doc) { - let alreadyCreatedButtons: string[] = []; - const dragCreatorSet = Cast(doc.myItemCreators, Doc, null); - if (dragCreatorSet) { - alreadyCreatedButtons = DocListCast(dragCreatorSet.data).map(d => StrCast(d.title)); - } - const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); - const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ - _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, - icon, - title, - toolTip, - btnType: ButtonType.ToolButton, - ignoreClick, - _dropAction: "alias", - onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, - onClick: click ? ScriptField.MakeScript(click) : undefined, - backgroundColor: backgroundColor ? backgroundColor : Colors.DARK_GRAY, - color: Colors.WHITE, - _hideContextMenu: true, - _removeDropProperties: new List(["_stayInCollection"]), - _stayInCollection: true, - dragFactory, - clickFactory, - hidden: !noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, - system: true, - })); + /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools + static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc { + const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { + const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; + const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["backgroundColor"]).omit, + _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias", + btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, + _removeDropProperties: new List(["_stayInCollection"]), + }; + return this.AssignScripts(this.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs); + }); - if (dragCreatorSet === undefined) { - doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { - title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true - })); - } else { - creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); - } - return doc.myItemCreators as Doc; + const reqdOpts = { + title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, + _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, + }; + const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; + return this.AssignScripts(this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); } - static menuBtnDescriptions(doc: Doc) { - const badgeValue = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") + /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents + static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}}[] { + const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; return [ - { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, - { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, - { title: "Files", target: Cast(doc.myFilesystem, Doc, null), icon: "folder-open", click: 'selectMainMenu(self)' }, - { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, - { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, - { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue}, - { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, - { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, + { title: "Dashboards", target: CurrentUserUtils.MyDashboards, icon: "desktop", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Search", target: CurrentUserUtils.MySearcher, icon: "search", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Files", target: CurrentUserUtils.MyFilesystem, icon: "folder-open", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Tools", target: CurrentUserUtils.MyTools, icon: "wrench", scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, + { title: "Imports", target: CurrentUserUtils.MyImports, icon: "upload", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Recently Closed", target: CurrentUserUtils.MyRecentlyClosed, icon: "archive", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Shared with me", target: CurrentUserUtils.MySharedDocs, icon: "users", scripts:{onClick: 'selectMainMenu(self)'}, funcs:{badgeValue:badgeValue}}, + { title: "Trails", target: CurrentUserUtils.MyTrails, icon: "pres-trail", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "User Doc", target: CurrentUserUtils.MyUserDocView, icon: "address-card",scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, ]; } - static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.menuStack === undefined) { - await this.setupLinkDocs(doc, linkDatabaseId); - await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => - Docs.Create.FontIconDocument({ - icon, - btnType: ButtonType.MenuButton, - _stayInCollection: true, - _hideContextMenu: true, - _chromeHidden: true, - system: true, - dontUndo: true, - title, - target, - dontRegisterView: true, - hidden: hidden ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - _width: 60, - _height: 60, - badgeValue, - onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) - }) - ); - - doc.searchBtn = menuBtns.find(btn => btn.title === "Search"); - - doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, { - title: "menuItemPanel", - childDropAction: "alias", - _chromeHidden: true, - backgroundColor: Colors.DARK_GRAY, - boxShadow: "rgba(0,0,0,0)", - dontRegisterView: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - ignoreClick: true, - _gridGap: 0, - _yMargin: 0, - _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true - })); - } - return doc.menuStack as Doc; + /// setup the left sidebar container and panels that can be displayed within it + static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { + this.AssignOpts(DocCast(doc[field]), {}) ?? (doc[field] = ((doc:Doc) => {doc.system = true; return doc;})(new Doc())); + CurrentUserUtils.setupSearcher(doc, "mySearcher"); + CurrentUserUtils.setupToolsBtnPanel(doc, "myTools"); + CurrentUserUtils.setupImportSidebar(doc, "myImports"); + CurrentUserUtils.setupDashboards(doc, "myDashboards"); + CurrentUserUtils.setupTrails(doc, "myTrails"); + CurrentUserUtils.setupFilesystem(doc, "myFilesystem"); + CurrentUserUtils.setupRecentlyClosedDocs(doc, "myRecentlyClosed"); + CurrentUserUtils.setupUserDocView(doc, "myUserDocView"); } + /// Initializes the left sidebar menu buttons and the panels they open up + static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") { + this.setupLeftSidebarPanel(doc); // the tools/panels opened up by the menu buttons + const myLeftSidebarMenu = DocCast(doc[field]); + const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => { + const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; + const reqdBtnOpts:DocumentOptions = { + title, icon, target, btnType: ButtonType.MenuButton, system: true, dontUndo: true, dontRegisterView: true, + _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + }; + return this.AssignScripts(this.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs); + }); - // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu - static setupActiveMobileMenu(doc: Doc) { - if (doc.activeMobileMenu === undefined) { - doc.activeMobileMenu = this.setupMobileMenu(); - } - return doc.activeMobileMenu as Doc; + const reqdStackOpts:DocumentOptions ={ + title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, + _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true + }; + const reqdScripts = { dropConverter: "convertToButtons(dragData)" } + return this.AssignScripts(this.AssignOpts(myLeftSidebarMenu, reqdStackOpts, menuBtns) ?? (doc[field] = Docs.Create.StackingDocument(menuBtns, reqdStackOpts)), reqdScripts); } - // Sets up mobileMenu stacking document - static setupMobileMenu() { - const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), { - _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true, - })); - return menu; + // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu + static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") { + const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,}; + this.AssignOpts(DocCast(doc[field]), reqdOpts, this.setupMobileButtons()) ?? (doc[field] = Docs.Create.StackingDocument(this.setupMobileButtons(), reqdOpts)); } - // SEts up mobile buttons for inside mobile menu + // Sets up mobile buttons for inside mobile menu static setupMobileButtons(doc?: Doc, buttons?: string[]) { + return []; const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " }, { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." }, @@ -533,375 +506,329 @@ export class CurrentUserUtils { }); } - static setupLibrary(userDoc: Doc) { - return CurrentUserUtils.setupDashboards(userDoc); - } - - // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. - // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static setupToolsBtnPanel(doc: Doc) { - // setup a masonry view of all he creators - const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc); - const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); - - doc.myCreators = doc.myCreators ?? new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { - title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true, - _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true, - })); - if (!DocListCast(doc.myCreators).includes(creatorBtns) || !DocListCast(doc.myCreators).includes(templateBtns)) Doc.GetProto(doc.myCreators as Doc).data = new List([creatorBtns, templateBtns]); - - doc.myColorPicker = doc.myColorPicker ?? new PrefetchProxy(Docs.Create.ColorDocument({ - title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true - })); - - doc.myTools = doc.myTools ?? new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], { - title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, - system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0", - })) as any as Doc; - if (!DocListCast(doc.myTools).includes(doc.myCreators as Doc)) Doc.GetProto(doc.myTools as Doc).data = new List([doc.myCreators as Doc]); - } - - static async setupDashboards(doc: Doc) { - // setup dashboards library item - await doc.myDashboards; - if (doc.myDashboards === undefined) { - const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); - const newDashboardButton: Doc = Docs.Create.FontIconDocument({ onClick: newDashboard, _forceActive: true, toolTip: "Create new dashboard", _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); - doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, freezeChildren: "remove|add", - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, - explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." - })); - const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`); - const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); - const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`); - const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`); - const removeDashboard = ScriptField.MakeScript('removeDashboard(self)'); - const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()'); - // (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, shareDashboard!, removeDashboard!]); - // (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]); - // (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "user-friends", "times"]); - (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]); - (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]); - (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "chalkboard", "tv", "camera", "users", "times"]); - (doc.myDashboards as any as Doc).childContextMenuFilters = new List([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]); - } - return doc.myDashboards as any as Doc; + /// Search option on the left side button panel + static setupSearcher(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { + dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", + _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, + }; + this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.SearchDocument(reqdOpts)); } - static setupPresentations(doc: Doc) { - if (doc.myTrails === undefined) { - const newTrail = ScriptField.MakeScript(`createNewPresentation()`); - const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "Create new trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); - doc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Trails", _showTitle: "title", _height: 100, - treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, - explainer: "All of the trails that you have created will appear here." - })); - (doc.myTrails as any as Doc).contextMenuScripts = new List([newTrail!]); - (doc.myTrails as any as Doc).contextMenuLabels = new List(["Create New Trail"]); - (doc.myTrails as any as Doc).childContextMenuIcons = new List(["plus"]); - } - return doc.myTrails as any as Doc; + /// Initializes the panel of draggable tools that is opened from the left sidebar. + static setupToolsBtnPanel(doc: Doc, field:string) { + const myTools = DocCast(doc[field]); + const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined); + //const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); + const reqdToolOps:DocumentOptions = { + title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", + _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, + }; + this.AssignOpts(myTools, reqdToolOps, [creatorBtns, /*templateBtns*/]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, /*templateBtns*/], reqdToolOps)); } - static async setupFilesystem(doc: Doc) { - await doc.myFilesystem; - if (doc.myFilesystem === undefined) { - doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); - // doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true }); - const newFolder = ScriptField.MakeFunction(`makeTopLevelFolder()`, { scriptContext: "any" })!; - const newFolderButton: Doc = Docs.Create.FontIconDocument({ - onClick: newFolder, _forceActive: true, toolTip: "Create new folder", - _stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30, - buttonText: "New folder", icon: "folder-plus", system: true - }); - doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileOrphans as Doc], { - title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100, - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true, - explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." - })); - (doc.myFilesystem as any as Doc).contextMenuScripts = new List([newFolder]); - (doc.myFilesystem as any as Doc).contextMenuLabels = new List(["Create new folder"]); - (doc.myFilesystem as any as Doc).childContextMenuIcons = new List(["plus"]); + /// initializes the left sidebar dashboard pane + static setupDashboards(doc: Doc, field:string) { + var myDashboards = DocCast(doc[field]); + + const newDashboard = `createNewDashboard(Doc.UserDoc())`; + const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, + title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true }; + const reqdBtnScript = {onClick: newDashboard,} + const newDashboardButton = this.AssignScripts(this.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); + + const reqdOpts:DocumentOptions = { + title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true, + targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true, + buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias", + _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true, + contextMenuLabels: new List(["Create New Dashboard"]), + contextMenuIcons: new List(["plus"]), + childContextMenuLabels: new List(["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]),// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters + childContextMenuIcons: new List(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters + explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." + }; + myDashboards = this.AssignOpts(myDashboards, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; + const contextMenuScripts = [newDashboard]; + const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters + const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts + if (Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { + myDashboards.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); + } + if (Cast(myDashboards.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { + myDashboards.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); + } + if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) { + myDashboards.childContextMenuFilters = new List(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!)); } - return doc.myFilesystem as any as Doc; } - static setupRecentlyClosedDocs(doc: Doc) { - if (doc.myRecentlyClosedDocs === undefined) { - const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); - const clearDocsButton: Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true }); - doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true, - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, - explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." - - })); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List([clearAll!]); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List(["Empty recently closed"]); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuIcons = new List(["trash"]); - + /// initializes the left sidebar Trails pane + static setupTrails(doc: Doc, field:string) { + var myTrails = DocCast(doc[field]); + const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, + title: "New trail", toolTip: "Create new trail", btnType: ButtonType.ClickButton, buttonText: "New trail", icon: "plus", system: true }; + const reqdBtnScript = {onClick: `createNewPresentation()`}; + const newTrailButton = this.AssignScripts(this.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); + + const reqdOpts:DocumentOptions = { + title: "My Trails", _showTitle: "title", _height: 100, + treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton, + contextMenuIcons: new List(["plus"]), + contextMenuLabels: new List(["Create New Trail"]), + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, + explainer: "All of the trails that you have created will appear here." + }; + myTrails = this.AssignOpts(myTrails, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([],reqdOpts )); + const contextMenuScripts = [reqdBtnScript.onClick]; + if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { + myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } } - static setupFilterDocs(doc: Doc) { - // setup Filter item - if (doc.currentFilter === undefined) { - doc.currentFilter = Docs.Create.FilterDocument({ - title: "Unnamed Filter", _height: 150, - treeViewHideTitle: true, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, _autoHeight: true, _fitWidth: true - }); - const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); - (doc.currentFilter as Doc).contextMenuScripts = new List([clearAll!]); - (doc.currentFilter as Doc).contextMenuLabels = new List(["Clear All"]); - (doc.currentFilter as Doc).filterBoolean = "AND"; + /// initializes the left sidebar File system pane + static setupFilesystem(doc: Doc, field:string) { + var myFilesystem = DocCast(doc[field]); + const reqdOrphansOpts:DocumentOptions = { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }; + this.AssignOpts(DocCast(doc.myFileOrphans), reqdOrphansOpts) ?? (doc.myFileOrphans = Docs.Create.TreeDocument([], reqdOrphansOpts)); + + const newFolder = `makeTopLevelFolder()`; + const newFolderOpts: DocumentOptions = { + _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _width: 30, _height: 30, + title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", system: true + }; + const newFolderScript = { onClick: newFolder}; + const newFolderButton = this.AssignScripts(this.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); + + const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, + title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true, + isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias", + childContextMenuLabels: new List(["Create new folder"]), + childContextMenuIcons: new List(["plus"]), + explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." + }; + myFilesystem = this.AssignOpts(myFilesystem, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([DocCast(doc.myFileOrphans)], reqdOpts)); + const childContextMenuScripts = [newFolder]; + if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { + myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } } - static setupUserDoc(doc: Doc) { - if (doc.myUserDoc === undefined) { - doc.treeViewOpen = true; - doc.treeViewExpandedView = "fields"; - doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true - })) as any as Doc; + /// initializes the panel displaying docs that have been recently closed + static setupRecentlyClosedDocs(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, + title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", + contextMenuLabels: new List(["Empty recently closed"]), + contextMenuIcons:new List(["trash"]), + explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." + }; + const recentlyClosed = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + + const clearAll = (target:string) => `getProto(${target}).data = new List([])`; + const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, + title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", system: true, + toolTip: "Empty recently closed",}; + const clearBtnsScripts = {onClick: clearAll("self.target")} + const clearDocsButton = this.AssignScripts( + this.AssignOpts(DocCast(recentlyClosed?.clearDocsBtn), clearBtnsOpts) ?? (recentlyClosed.clearDocsBtn = Docs.Create.FontIconDocument(clearBtnsOpts)), + clearBtnsScripts); + + if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton; + + if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) { + recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("self"))!]) } } - static setupSidebarContainer(doc: Doc) { - if (doc.sidebar === undefined) { - const sidebarContainer = new Doc(); - sidebarContainer.system = true; - doc.sidebar = new PrefetchProxy(sidebarContainer); - } - return doc.sidebar as Doc; + /// creates a new, empty filter doc + static createFilterDoc() { + const clearAll = `getProto(self).data = new List([])`; + const reqdOpts:DocumentOptions = { + _lockedPosition: true, _autoHeight: true, _fitWidth: true, _height: 150, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, + title: "Unnamed Filter", filterBoolean: "AND", boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, + childDropAction: "none", treeViewHideTitle: true, treeViewTruncateTitleWidth: 150, + childContextMenuLabels: new List(["Clear All"]), + childContextMenuScripts: new List([ScriptField.MakeFunction(clearAll)!]), + }; + return Docs.Create.FilterDocument(reqdOpts); } - // setup the list of sidebar mode buttons which determine what is displayed in the sidebar - static async setupSidebarButtons(doc: Doc) { - CurrentUserUtils.setupSidebarContainer(doc); - CurrentUserUtils.setupToolsBtnPanel(doc); - CurrentUserUtils.setupImportSidebar(doc); - CurrentUserUtils.setupDashboards(doc); - CurrentUserUtils.setupPresentations(doc); - CurrentUserUtils.setupFilesystem(doc); - CurrentUserUtils.setupRecentlyClosedDocs(doc); - CurrentUserUtils.setupUserDoc(doc); + /// initializes the left sidebar panel view of the UserDoc + static setupUserDocView(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { + _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view", + boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, + treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 + }; + if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }) + this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([doc], reqdOpts)); } - static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { - ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true, + static linearButtonList = (title: string, opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { + title, ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), _lockedPosition: true, system: true, flexDirection: "row" - })) as any as Doc + }) - static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ + static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({ btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, - })) as any as Doc - - /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window - static setupDockedButtons(doc: Doc) { - if (doc.dockedBtns === undefined) { - const dockBtn = (opts: DocumentOptions) => CurrentUserUtils.createToolButton({ _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...opts }); - const btnDescs = [ - { title: "undo", opts: () => ({ icon: "undo-alt", onClick: ScriptField.MakeScript("undo()"), toolTip: "Click to undo" }) }, - { title: "redo", opts: () => ({ icon: "redo-alt", onClick: ScriptField.MakeScript("redo()"), toolTip: "Click to redo" }) } - ]; - doc.dockedBtns = CurrentUserUtils.linearButtonList({ - title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, - childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true - }, btnDescs.map(desc => doc[`dockedBtn-${desc.title}`] as Doc ?? (doc[`dockedBtn-${desc.title}`] = dockBtn({ title: desc.title, ...desc.opts() })))); + }) + + /// initializes the required buttons in the expanding button menu at the bottom of the Dash window + static setupDockedButtons(doc: Doc, field="myDockedBtns") { + const dockedBtns = DocCast(doc[field]); + const dockBtn = (title:string, onClick: string, opts: DocumentOptions) => { + const btn = this.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === title), opts) ?? + CurrentUserUtils.createToolButton({title, ...opts}); + this.AssignScripts(btn, {onClick}) + return btn; } + const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet + { title: "undo", click: "undo()", opts: { icon: "undo-alt", toolTip: "Click to undo" }}, + { title: "redo", click:"redo()", opts: { icon: "redo-alt", toolTip: "Click to redo" }} + ]; + const btns = btnDescs.map(desc => dockBtn(desc.title, desc.click, {_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts})); + const dockBtnsReqdOpts = { + title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, + childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true + }; + reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); + reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); + return this.AssignOpts(dockedBtns, dockBtnsReqdOpts, btns) ?? (doc[field] = CurrentUserUtils.linearButtonList("dockedBtns", dockBtnsReqdOpts, btns)); } - static textTools(doc: Doc) { - const tools: Button[] = - [ - { - 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(value, _readOnly_)' - }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' }, - { title: "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: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' }, - { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' }, - { title: "#", toolTip: "Number List", 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", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' }, - { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' }, - { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' }, - { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", click: 'toggleNoAutoLinkAnchor(_readOnly_)' }, - ]; - return tools; + static textTools():Button[] { + return [ + { + title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]), + scripts :{script : 'setFont(value, _readOnly_)'} + }, + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'} }, + { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts:{script: '{ return setFontColor(value, _readOnly_); }'}}, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, + { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline",scripts: {onClick:'{ return toggleUnderline(_readOnly_);}'} }, + { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} }, + { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} }, + + // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, + // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}}, + // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}}, + { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }}, + { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} }, + { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} }, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}}, + ]; } - static inkTools(doc: Doc) { - const tools: Button[] = [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", click: 'setActiveInkTool("pen", _readOnly_)' }, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _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_)' }, + static inkTools():Button[] { + return [ + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveInkTool("pen", _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveInkTool("write", _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveInkTool("eraser", _readOnly_);}' }}, + // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveInkTool("highlighter")'} }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return 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", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" }, - { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' }, - { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveInkTool("line", _readOnly_);}' }}, + { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: {script: "{ return setFillColor(value, _readOnly_);}"} }, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'} }, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, ]; - return tools; } - static schemaTools(doc: Doc) { - const tools: Button[] = - [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", click: 'toggleSchemaPreview(_readOnly_)', }]; - return tools; + static schemaTools():Button[] { + return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{onClick:'toggleSchemaPreview(_readOnly_)'}, }]; } - static webTools(doc: Doc) { - const tools: Button[] = - [ - { 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(value, _readOnly_)' }, - ]; - - return tools; + static webTools() { + return [ + { 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(value, _readOnly_)' }, + ]; } - static contextMenuTools(doc: Doc) { + static contextMenuTools():Button[] { return [ { title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, - CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, - CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, - CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Grid], - script: 'setView(value, _readOnly_)', + btnList: new List([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, + CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, + CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, + CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, + CollectionViewType.Grid]), + scripts: {script: 'setView(value, _readOnly_)'}, }, // Always show - { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame(_readOnly_)', icon: "chevron-left", hidden: 'IsNoviceMode()' }, - { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame(_readOnly_)', icon: "chevron-right", hidden: 'IsNoviceMode()' }, - { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' }, // Only when a document is selected - { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', }, - { 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: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton,icon: "chevron-left", scripts:{onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'} }, + { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, icon: "chevron-right", scripts:{onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, + { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: { script: "setBackgroundColor(value, _readOnly_)"}, funcs:{ hidden: 'selectedDocumentType()' }}, // Only when a document is selected + { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", scripts: {script: "setHeaderColor(value, _readOnly_)"}, funcs : {hidden: 'selectedDocumentType()'} }, + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", scripts: {onClick : 'toggleOverlay(_readOnly_)'}, funcs: {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: CurrentUserUtils.textTools(doc), expanded: 'selectedDocumentType("rtf")' }, // Always available - { title: "Ink", type: "inkTools", subMenu: CurrentUserUtils.inkTools(doc), expanded: 'selectedDocumentType("ink")' }, // Always available - { title: "Web", type: "webTools", subMenu: CurrentUserUtils.webTools(doc), hidden: 'selectedDocumentType("web")' }, // Only when Web is selected - { title: "Schema", type: "schemaTools", subMenu: CurrentUserUtils.schemaTools(doc), hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected + { title: "Text", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available + { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available + { title: "Web", icon: "web", subMenu: CurrentUserUtils.webTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.WEB}")`, hidden: `!selectedDocumentType("${DocumentType.WEB}")`} }, // Only when Web is selected + { title: "Schema", icon: "schema", subMenu: CurrentUserUtils.schemaTools(), funcs: { linearViewIsExpanded: `selectedDocumentType(undefined, "${CollectionViewType.Schema}")`, hidden: `!selectedDocumentType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected ]; } - // Sets up the default context menu buttons - static setupContextMenuButtons(doc: Doc) { - const btnFunc = (params: Button) => Docs.Create.FontIconDocument({ - title: params.title, icon: params.icon, toolTip: params.toolTip, color: Colors.WHITE, system: true, dontUndo: true, ignoreClick: params.ignoreClick, + /// initializes a context menu button for the top bar context menu + static setupContextMenuButton(params:Button, btnDoc?:Doc) { + const reqdOpts:DocumentOptions = { + title: params.title, btnType: params.btnType, icon: params.icon, toolTip: params.toolTip, ignoreClick: params.ignoreClick, + numBtnType: params.numBtnType, numBtnMin: params.numBtnMin, numBtnMax: params.numBtnMax,_nativeHeight: 30, btnList: params.btnList, + backgroundColor: params.scripts?.onClick ? undefined: "transparent", /// a bit hacky. if an onClick is specified, then we assume we assume a toggle use onClick to get the backgroundColor (see below). Otherwise, assume a transparent background _nativeWidth: params.width ? params.width : 30, - _nativeHeight: 30, _width: params.width ? params.width : 30, _height: 30, - btnType: params.btnType, - numBtnType: params.numBtnType, numBtnMin: params.numBtnMin, numBtnMax: params.numBtnMax, - btnList: new List(params.list), + color: Colors.WHITE, system: true, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - script: params.script ? ScriptField.MakeScript(params.script, { value: "any" }) : undefined, - backgroundColor: params.click ? ComputedField.MakeFunction(params.click) as any : "transparent", - onClick: params.click ? ScriptField.MakeScript(params.click, { scriptContext: "any" }, { _readOnly_: false }) : undefined, - hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, - }); - if (doc.contextMenuBtns === undefined) { - doc.contextMenuBtns = CurrentUserUtils.linearButtonList( - { title: "menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, - CurrentUserUtils.contextMenuTools(doc).map(params => - !params.subMenu ? - btnFunc(params) : - CurrentUserUtils.linearButtonList({ - title: params.title, - childDontRegisterViews: true, - linearViewSubMenu: true, flexGap: 0, ignoreClick: true, - linearViewExpandable: true, icon: params.title, _height: 30, - linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, - hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, - }, params.subMenu.map(btnFunc)))); - } else { - const menuBtnList = DocListCast((doc.contextMenuBtns as Doc).data); - let prev = ""; - CurrentUserUtils.contextMenuTools(doc).forEach(params => { - const menuBtnDoc = menuBtnList.find(doc => doc.title === params.title); - if (!menuBtnDoc) { - const newMenuBtnDoc = !params.subMenu ? - btnFunc(params) : - CurrentUserUtils.linearButtonList({ - title: params.title, - childDontRegisterViews: true, - linearViewSubMenu: true, flexGap: 0, ignoreClick: true, - linearViewExpandable: true, icon: params.title, _height: 30, - linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, - hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, - }, params.subMenu.map(btnFunc)); - const after = menuBtnList.find(doc => doc.title === prev); - Doc.AddDocToList(doc.contextMenuBtns as Doc, "data", newMenuBtnDoc, after, false, !after); - } - const subMenuBtnList = menuBtnDoc?.data ? DocListCast(menuBtnDoc.data) : undefined; - if (menuBtnDoc && subMenuBtnList && params.subMenu && DocListCast(doc.data).length !== subMenuBtnList.length) { - let prevSub = ""; - params.subMenu.forEach(sub => { - if (!subMenuBtnList.find(doc => doc.title === sub.title)) { - const newSubMenuBtnDoc = btnFunc(sub); - const after = subMenuBtnList.find(doc => doc.title === prevSub); - Doc.AddDocToList(menuBtnDoc, "data", newSubMenuBtnDoc, after, false, !prevSub); - } - prevSub = params.title; - }); - } - prev = params.title; - }); + }; + const reqdFuncs:{[key:string]:any} = { + ...params.funcs, + backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally } + return this.AssignScripts(this.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } - // sets up the default set of documents to be shown in the Overlay layer - static setupOverlays(doc: Doc) { - if (doc.myOverlayDocs === undefined) { - doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true })); - } + /// Initializes all the default buttons for the top bar context menu + static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { + const ctxtMenuBtnsDoc = DocCast(doc[field]); + const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { + const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); + if (!params.subMenu) { + return this.setupContextMenuButton(params, menuBtnDoc); + } else { + const reqdSubMenuOpts = { title: params.title, icon: params.icon, childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, + linearViewSubMenu: true, linearViewExpandable: true, }; + const reqdSubMenuFuncs:{[key:string]:any} = { ...params.funcs}; + return this.AssignScripts(this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? + CurrentUserUtils.linearButtonList("submenu", reqdSubMenuOpts, params.subMenu.map(sub => + this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) + )), undefined, reqdSubMenuFuncs); + } + }); + const reqdCtxtOpts = { title: "menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; + return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts) ?? (doc[field] = CurrentUserUtils.linearButtonList("contextMenuButtons", reqdCtxtOpts, ctxtMenuBtns)); } - // the initial presentation Doc to use - static setupDefaultPresentation(doc: Doc) { - if (doc["template-presentation"] === undefined) { - doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _fitWidth: true, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true - })); - } + /// collection of documents rendered in the overlay layer above all tabs and other UI + static setupOverlays(doc: Doc, field = "myOverlayDocs") { + const reqdOpts = { title: "overlay documents", backgroundColor: "#aca3a6", system: true }; + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.FreeformDocument([], reqdOpts)); } - - // Sharing sidebar is where shared documents are contained + + /// The database of all links on all documents static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); @@ -915,6 +842,8 @@ export class CurrentUserUtils { doc.myLinkDatabase = new PrefetchProxy(linkDocs); } } + + /// Shared documents option on the left side button panel // A user's sharing document is where all documents that are shared to that user are placed. // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents @@ -941,37 +870,26 @@ export class CurrentUserUtils { }; const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer")); - if (!(sharedDocs instanceof Doc)) { - doc.mySharedDocs = new PrefetchProxy( - Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); - } else { - Object.entries(sharedRequiredDocOpts).forEach(pair => { - const targetDoc = pair[0].startsWith("_") ? sharedDocs as Doc : Doc.GetProto(sharedDocs as Doc); - targetDoc[pair[0]] = pair[1]; - }); - } + return this.AssignOpts(DocCast(sharedDocs), sharedRequiredDocOpts) ?? + (doc.mySharedDocs = Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); } - // Import sidebar is where shared documents are contained - static setupImportSidebar(doc: Doc) { - if (doc.myImportDocs === undefined) { - const newImportButton: Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true }); - doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { - title: "My Imports", _forceActive: true, buttonMenu: true, buttonMenuDoc: newImportButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, - childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, - dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." - })); - } - } - - // Search sidebar is where searches within the document are performed - static setupSearchSidebar(doc: Doc) { - if (doc.mySearchPanel === undefined) { - doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({ - dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true, - childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Panel", system: true - })) as any as Doc; - } + /// Import option on the left side button panel + static setupImportSidebar(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { + title: "My Imports", _forceActive: true, buttonMenu: true, ignoreClick: true, _showTitle: "title", + _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, + childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, + dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." + }; + const myImports = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.StackingDocument([], reqdOpts)); + + const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", + _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, + buttonText: "Import", icon: "upload", system: true }; + const reqdBtnScripts = { onClick: "importDocument()" }; + const newImportBtn = this.AssignOpts(DocCast(myImports.buttonMenuDoc), reqdBtnOpts) ?? (myImports.buttonMenuDoc = Docs.Create.FontIconDocument(reqdBtnOpts)); + this.AssignScripts(newImportBtn, reqdBtnScripts); } static setupClickEditorTemplates(doc: Doc) { @@ -1033,8 +951,8 @@ export class CurrentUserUtils { }, { fireImmediately: true }); // Document properties on load doc.system = true; - doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; + doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc._raiseWhenDragged = true; doc._showLabel = true; doc._showMenuLabel = true; @@ -1053,29 +971,22 @@ export class CurrentUserUtils { doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false); doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); doc.noviceMode = BoolCast(doc.noviceMode, true); - doc.savedFilters = new List(); + !doc.savedFilters && (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; doc.myPublishedDocs = doc.myPublishedDocs ?? new List(); - doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); + doc.myHeaderBar = doc.myHeaderBar?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents + await this.setupLinkDocs(doc, linkDatabaseId); + await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates - this.setupImportSidebar(doc); // sets up the import sidebar - this.setupSearchSidebar(doc); // sets up the search sidebar this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile - this.setupOverlays(doc); // documents in overlay layer - this.setupContextMenuButtons(doc); // set up context menu buttons + this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard + this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected this.setupDockedButtons(doc); // the bottom bar of font icons - await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels - await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId); + this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); - setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered - - // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { @@ -1227,7 +1138,7 @@ export class CurrentUserUtils { public static async snapshotDashboard(userDoc: Doc) { const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); + Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); CurrentUserUtils.openDashboard(userDoc, copy); } @@ -1277,15 +1188,24 @@ 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 MyHeaderBarDoc() { return Cast(Doc.UserDoc().myHeaderBarDoc, Doc, null); } - public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } - public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); } - public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); } - public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); } - public static get OverlayDocs() { return DocListCast((Doc.UserDoc().myOverlayDocs as Doc)?.data); } + public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } + public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); } + public static get MySearcher() { return DocCast(Doc.UserDoc().mySearcher); } + public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } + public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); } + public static get MyTools() { return DocCast(Doc.UserDoc().myTools); } + public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } + public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } + public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } + public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } + public static get MyTrails() { return DocCast(Doc.UserDoc().myTrails); } + public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } + public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } + public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } + public static get MyOverlayDocs() { return DocListCast(DocCast(Doc.UserDoc().myOverlayDocs)?.data); } + public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } + public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } + public static get EmptyPane() { return DocCast(Doc.UserDoc().emptyPane); } public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } } @@ -1298,7 +1218,7 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { view && SelectionManager.SelectView(view, false); } }); -ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, "document containing all shared Docs"); +ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); @@ -1329,15 +1249,15 @@ ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colTy const parentDoc: Doc = Cast(selected.context, Doc, null); selected = parentDoc; } - if (selected && docType && selected.type === docType) return false; - else if (selected && colType && selected.viewType === colType) return false; - else if (selected && !colType && !docType) return false; - else return true; + if (selected && docType && selected.type === docType) return true; + else if (selected && colType && selected.viewType === colType) return true; + else if (selected && !colType && !docType) return true; + else return false; }); 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); + return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); }); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { if (readOnly) return; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index a82f99d5a..0435cd535 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -10,6 +10,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 { CurrentUserUtils } from './CurrentUserUtils'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; @@ -296,7 +297,7 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { Doc.linkFollowHighlight(dv?.props.Document, false); } else { - const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null); + const context = doc.context !== CurrentUserUtils.MyFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 223b0268c..3791bec73 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -223,7 +223,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`); } const paramString = paramList.join(", "); - const body = addReturn ? `return ${script};` : script; + const body = addReturn && !script.startsWith("{ return") ? `return ${script};` : script; const reqTypes = requiredType ? `: ${requiredType}` : ''; const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile("file.ts", funcScript); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 8486b3d62..be5927497 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -141,7 +141,7 @@ export function ViewBoxAnnotatableComponent

    () const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { - const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc; + const recent = CurrentUserUtils.MyRecentlyClosed; toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 767efdbc2..16d7c9a23 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -235,7 +235,7 @@ export class KeyManager { if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) { SelectionManager.Views()[0].ComponentView?.search?.("", false, false); } else { - const searchBtn = Doc.UserDoc().searchBtn as Doc; + const searchBtn = CurrentUserUtils.MySearcher; if (searchBtn) { MainView.Instance.selectMenu(searchBtn); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5fd76c388..7e3916b3f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -10,7 +10,7 @@ import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; -import { PromiseValue, StrCast } from '../../fields/Types'; +import { DocCast, PromiseValue, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; @@ -79,7 +79,7 @@ export class MainView extends React.Component { @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons @observable private _panelContent: string = "none"; - @observable private _sidebarContent: any = this.userDoc?.sidebar; + @observable private _sidebarContent: any = CurrentUserUtils.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; @computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @@ -92,7 +92,7 @@ export class MainView extends React.Component { @computed private get userDoc() { return Doc.UserDoc(); } @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); } @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; } - @computed private get headerBarDoc() { return this.userDoc ? CurrentUserUtils.MyHeaderBarDoc : CurrentUserUtils.MyHeaderBarDoc; } + @computed private get headerBarDoc() { return CurrentUserUtils.MyHeaderBar; } @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } headerBarDocWidth = () => this.mainDocViewWidth(); @@ -249,24 +249,8 @@ export class MainView extends React.Component { @action createNewFolder = async () => { - if (!await this.userDoc.myFilesystem) { - this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); - const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!; - const newFolderButton: Doc = Docs.Create.FontIconDocument({ - onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder", - btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true - }); - this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.myFileOrphans as Doc], { - title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true, - explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." - })); - } const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); - Doc.AddDocToList(this.userDoc.myFilesystem as Doc, "data", folder); + Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); } @observable _exploreMode = false; @@ -397,7 +381,7 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} - styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === Doc.UserDoc().myFilesystem ? DashboardStyleProvider : DefaultStyleProvider} + styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} ScreenToLocalTransform={this.mainContainerXf} @@ -423,7 +407,7 @@ export class MainView extends React.Component { @computed get leftMenuPanel() { return

    (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); + remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(CurrentUserUtils.MyDockedBtns, "data", doc), true); moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc); - addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); + addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(CurrentUserUtils.MyDockedBtns, "data", doc), true); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); @@ -543,9 +527,9 @@ export class MainView extends React.Component { @computed get docButtons() { return !(this.userDoc.dockedBtns instanceof Doc) ? (null) : -
    +
    { + return CurrentUserUtils.MyOverlayDocs?.map(d => { let offsetx = 0, offsety = 0; const dref = React.createRef(); const onPointerMove = action((e: PointerEvent, down: number[]) => { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 16eb95cf4..2cf334ae1 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -931,7 +931,7 @@ export class PropertiesView extends React.Component { * If it doesn't exist, it creates it. */ checkFilterDoc() { - if (!this.selectedDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.selectedDoc); + if (!this.selectedDoc.currentFilter) this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); } /** @@ -944,8 +944,7 @@ export class PropertiesView extends React.Component { this.selectedDoc._docRangeFilters = new List(); (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; - this.selectedDoc.currentFilter = undefined; - CurrentUserUtils.setupFilterDocs(this.selectedDoc); + this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); } /** diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index d2687df17..1ebbe08ef 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -416,7 +416,7 @@ export class CollectionDockingView extends CollectionSubView() { } tabDestroyed = (tab: any) => { - Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); + Doc.AddDocToList(CurrentUserUtils.MyHeaderBar, "data", tab.DashDoc); Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); const dview = CollectionDockingView.Instance.props.Document; const fieldKey = CollectionDockingView.Instance.props.fieldKey; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 39f6466d6..1c25421f5 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -99,7 +99,7 @@ export class CollectionMenu extends AntimodeMenu{ } @computed get contMenuButtons() { - const selDoc = Doc.UserDoc().contextMenuBtns; + const selDoc = CurrentUserUtils.MyContextMenuBtns; return !(selDoc instanceof Doc) ? (null) :
    { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }), initialize: (button: Doc) => { - button['target-docFilters'] = (Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) instanceof ObjectField ? - ObjectField.MakeCopy((Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) as any as ObjectField) : undefined; + button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) instanceof ObjectField ? + ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) as any as ObjectField) : undefined; button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined; }, }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 52e99f26b..c07e44fcc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -985,7 +985,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.MyOverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -1031,7 +1031,7 @@ export class CollectionFreeFormView extends CollectionSubView {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
    - {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.DockedBtns ? null : + {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : e.stopPropagation()} > @@ -226,7 +226,7 @@ export class CollectionLinearView extends CollectionSubView() { } - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.DockedBtns ? (null) : + {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? (null) : Currently playing: diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index 4b33ef8ae..a6f6bd35f 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -68,8 +68,8 @@ export class LinkPopup extends React.Component { className="linkPopup-searchBox searchBox-input" /> */} 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { @@ -563,7 +563,7 @@ export class DocumentViewInternal extends DocComponent 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index e3708d7b9..5add09653 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -35,7 +35,7 @@ export class FilterBox extends ViewBoxBaseComponent() { constructor(props: Readonly) { super(props); const targetDoc = FilterBox.targetDoc; - if (targetDoc && !targetDoc.currentFilter) CurrentUserUtils.setupFilterDocs(targetDoc); + if (targetDoc && !targetDoc.currentFilter) targetDoc.currentFilter = CurrentUserUtils.createFilterDoc(); } public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 10974ca03..967158cbf 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -156,20 +156,22 @@ export class WebBox extends ViewBoxAnnotatableComponent { this._annotationKeySuffix = () => this._urlHash + "-annotations"; + const reqdFuncs:{[key:string]: string} = {}; // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) - this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`); - this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`); + reqdFuncs[this.fieldKey + "-annotations"] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`; + reqdFuncs[this.fieldKey + "-sidebar"] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`; + CurrentUserUtils.AssignScripts(this.dataDoc, {}, reqdFuncs); }); - reaction(() => this.props.isSelected() || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), + reaction(() => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), async (selected) => { if (selected) { this._webPageHasBeenRendered = true; - } else if ((!this.props.isContentActive() || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) + } else if ((!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty. this.updateThumb(); } - }, { fireImmediately: this.props.isSelected() || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegree(this.props.Document) ? true : false) }); + }, { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) }); this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, autoHeight => { diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 97e6eddfe..f29dfe489 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
    {this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
    ); break; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 1dd6fef9b..52b03b0a5 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -623,7 +623,7 @@ export class PresBox extends ViewBoxBaseComponent() { */ @action updateMinimize = async () => { - if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, "right"); @@ -868,7 +868,7 @@ export class PresBox extends ViewBoxBaseComponent() { } break; case "Escape": - if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); } + if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); } else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; } else this.layoutDoc.presStatus = "edit"; if (this._presTimer) clearTimeout(this._presTimer); @@ -2511,7 +2511,7 @@ export class PresBox extends ViewBoxBaseComponent() { const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); - return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ? + return CurrentUserUtils.MyOverlayDocs.includes(this.rootDoc) ?
    e.stopPropagation()}>
    {"Loop"}
    }>
    () {
    : -
    +
    {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 7b72787d1..94be286f5 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -941,6 +941,9 @@ export namespace Doc { export function isBrushedHighlightedDegree(doc: Doc) { return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc); } + export function isBrushedHighlightedDegreeUnmemoized(doc: Doc) { + return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegreeUnmemoized(doc); + } export class DocBrush { BrushedDoc: ObservableMap = new ObservableMap(); @@ -980,7 +983,7 @@ export namespace Doc { export function SearchQuery(): string { return manager._searchQuery; } export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } export function UserDoc(): Doc { return manager._user_doc; } - export function SharingDoc(): Doc { return Cast(Doc.UserDoc().mySharedDocs, Doc, null); } + export function SharingDoc(): Doc { return CurrentUserUtils.MySharedDocs; } export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); } export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); } diff --git a/src/fields/util.ts b/src/fields/util.ts index d1e565774..37b9be31b 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -88,6 +88,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number delete target.__fields[prop]; } else { target.__fieldKeys && (target.__fieldKeys[prop] = true); + // if (target.__fields[prop] !== value) { + // console.log("ASSIGN " + prop + " " + value); + // } target.__fields[prop] = value; } //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 78ec706d7..c5c6fb688 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -68,7 +68,7 @@ export class MobileInterface extends React.Component { constructor(props: Readonly<{}>) { super(props); - this._library = CurrentUserUtils.setupLibrary(Doc.UserDoc()); // to access documents in Dash Web + this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc()); // to access documents in Dash Web MobileInterface.Instance = this; } -- cgit v1.2.3-70-g09d2 From b292a055401af6236e0537cfad603016d77a535a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 13:40:07 -0400 Subject: a bunch of changes to make some userDoc() field accesses more typesafe by going through CurrentUserUtils. Some normalization of naming/functionality in CurrentUserUtils --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/documents/Documents.ts | 6 +- src/client/util/CurrentUserUtils.ts | 118 +++++++++++---------- src/client/util/SettingsManager.tsx | 16 +-- src/client/util/SharingManager.tsx | 4 +- src/client/views/DashboardView.tsx | 4 +- src/client/views/DocComponent.tsx | 6 +- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/GestureOverlay.tsx | 12 +-- src/client/views/GlobalKeyHandler.ts | 8 +- src/client/views/InkStrokeProperties.ts | 4 +- src/client/views/InkTranscription.tsx | 2 +- src/client/views/InkingStroke.tsx | 2 +- src/client/views/MainView.tsx | 4 +- src/client/views/OverlayView.tsx | 8 +- src/client/views/PropertiesButtons.tsx | 4 +- src/client/views/PropertiesView.tsx | 86 ++++++++------- src/client/views/TemplateMenu.tsx | 11 +- .../views/collections/CollectionDockingView.tsx | 22 ++-- src/client/views/collections/CollectionMenu.tsx | 35 +++--- .../views/collections/CollectionStackingView.tsx | 2 +- .../CollectionStackingViewFieldColumn.tsx | 4 +- .../views/collections/CollectionTreeView.tsx | 8 +- src/client/views/collections/CollectionView.tsx | 14 +-- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 30 +++--- .../collections/collectionFreeForm/MarqueeView.tsx | 6 +- .../collectionSchema/CollectionSchemaHeaders.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 5 +- src/client/views/nodes/ColorBox.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 40 +++---- src/client/views/nodes/FilterBox.tsx | 47 ++++---- src/client/views/nodes/ImageBox.tsx | 4 +- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/MapBox/MapBox.tsx | 4 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 6 +- src/client/views/nodes/button/FontIconBox.tsx | 35 +++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +-- .../views/nodes/formattedText/RichTextMenu.tsx | 1 - src/client/views/nodes/trails/PresBox.tsx | 30 +++--- src/client/views/nodes/trails/PresElementBox.tsx | 16 +-- src/client/views/pdf/PDFViewer.tsx | 2 +- src/client/views/topbar/TopBar.tsx | 10 +- src/client/views/webcam/DashWebRTCVideo.tsx | 2 +- src/fields/Doc.ts | 18 ++-- src/fields/util.ts | 2 +- src/mobile/MobileInterface.tsx | 20 ++-- 49 files changed, 360 insertions(+), 335 deletions(-) (limited to 'src/client/util') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index c5ff2db68..e14dc60b4 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -351,7 +351,7 @@ export class YoutubeBox extends React.Component { const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <>
    diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 927996af2..f96bffb3c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -687,7 +687,7 @@ export namespace Docs { ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) && - !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc); + !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, dataDoc); updateCachedAcls(dataDoc); updateCachedAcls(viewDoc); @@ -802,7 +802,7 @@ export namespace Docs { I.rotation = 0; I.data = new InkField(points); I.creationDate = new DateField; - I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + I["acl-Public"] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; I["acl-Override"] = "None"; I.links = ComputedField.MakeFunction("links(self)"); I[Initializing] = false; @@ -1204,7 +1204,7 @@ export namespace DocUtils { created = Docs.Create.RecordingDocument((field).url.href, resolved); layout = RecordingBox.LayoutString; } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); + created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index afb1442bf..7a38886fe 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -74,16 +74,16 @@ export class CurrentUserUtils { @observable public static headerBarHeight: number = 0; @observable public static searchPanelWidth: number = 0; - static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, functions?:{[key:string]: string}) { + static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); } }); - functions && Object.keys(functions).map(key => { + funcs && Object.keys(funcs).map(key => { const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== functions[key] && functions[key]) { - doc[key] = ComputedField.MakeFunction(functions[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); + if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) { + doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); } }); return doc; @@ -180,14 +180,14 @@ export class CurrentUserUtils { } /// Initializes collection of templates for notes and click functions - static setupDocTemplates(doc: Doc) { + static setupDocTemplates(doc: Doc, field="myTemplates") { const templates = [ CurrentUserUtils.setupNoteTemplates(doc), CurrentUserUtils.setupClickEditorTemplates(doc) ]; const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; - return this.AssignScripts(this.AssignOpts(DocCast(doc.templateDocs), reqdOpts, templates) ?? (doc.templateDocs = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts); + return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, templates) ?? (doc[field] = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts); } // setup templates for different document types when they are iconified from Document Decorations @@ -329,7 +329,7 @@ export class CurrentUserUtils { static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc { const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; - const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["backgroundColor"]).omit, + const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias", btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), @@ -562,6 +562,7 @@ export class CurrentUserUtils { if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) { myDashboards.childContextMenuFilters = new List(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!)); } + return myDashboards; } /// initializes the left sidebar Trails pane @@ -728,13 +729,13 @@ export class CurrentUserUtils { static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveInkTool("pen", _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveInkTool("write", _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveInkTool("eraser", _readOnly_);}' }}, - // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveInkTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return 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", scripts:{onClick: '{ return setActiveInkTool("line", _readOnly_);}' }}, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveTool("pen", _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveTool("write", _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveTool("eraser", _readOnly_);}' }}, + // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return setActiveTool("circle", _readOnly_);}'} }, + // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveTool("line", _readOnly_);}' }}, { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: {script: "{ return setFillColor(value, _readOnly_);}"} }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'} }, { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, @@ -941,41 +942,39 @@ export class CurrentUserUtils { } static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); - await DocListCastAsync((doc.globalGroupDatabase as Doc).data); - reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]), + doc.globalGroupDatabase ?? (doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument()); + await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); + reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]), async () => { - const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); }, { fireImmediately: true }); // Document properties on load - doc.system = true; - doc.title = Doc.CurrentUserEmail; - doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; - doc._raiseWhenDragged = true; - doc._showLabel = true; - doc._showMenuLabel = true; - doc.textAlign = StrCast(doc.textAlign, "left"); - doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); - doc.activeInkWidth = Number(StrCast(doc.activeInkWidth, "1")); - doc.activeInkBezier = StrCast(doc.activeInkBezier, "0"); - doc.activeFillColor = StrCast(doc.activeFillColor, ""); - doc.activeArrowStart = StrCast(doc.activeArrowStart, ""); - doc.activeArrowEnd = StrCast(doc.activeArrowEnd, ""); - doc.activeDash = StrCast(doc.activeDash, "0"); - doc.fontSize = StrCast(doc.fontSize, "12px"); - doc.fontFamily = StrCast(doc.fontFamily, "Arial"); - doc.fontColor = StrCast(doc.fontColor, "black"); - doc.fontHighlight = StrCast(doc.fontHighlight, ""); - doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false); - doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); - doc.noviceMode = BoolCast(doc.noviceMode, true); - !doc.savedFilters && (doc.savedFilters = new List()); + doc.system ?? (doc.system = true); + doc.title ?? (doc.title = Doc.CurrentUserEmail); + Doc.noviceMode ?? (Doc.noviceMode = true); + doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); + doc._showLabel ?? (doc._showLabel = true); + doc._showMenuLabel ?? (doc._showMenuLabel = true); + doc.textAlign ?? (doc.textAlign = "left"); + doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");; + doc.activeInkWidth ?? (doc.activeInkWidth = 1); + doc.activeInkBezier ?? (doc.activeInkBezier = "0"); + doc.activeFillColor ?? (doc.activeFillColor = ""); + doc.activeArrowStart ?? (doc.activeArrowStart = ""); + doc.activeArrowEnd ?? (doc.activeArrowEnd = ""); + doc.activeDash ?? (doc.activeDash == "0"); + doc.fontSize ?? (doc.fontSize = "12px"); + doc.fontFamily ?? (doc.fontFamily = "Arial"); + doc.fontColor ?? (doc.fontColor = "black"); + doc.fontHighlight ?? (doc.fontHighlight = ""); + doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false); + doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; - doc.myPublishedDocs = doc.myPublishedDocs ?? new List(); - doc.myHeaderBar = doc.myHeaderBar?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents + doc.myPublishedDocs ?? (doc.myPublishedDocs = new List()); + doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents await this.setupLinkDocs(doc, linkDatabaseId); await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon @@ -985,7 +984,7 @@ export class CurrentUserUtils { this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left - if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); + doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); @@ -1022,7 +1021,7 @@ export class CurrentUserUtils { const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; Docs.newAccount &&(userDoc.activePage = "home"); const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); - (await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager + (await DocListCastAsync(Doc.LinkDBDoc()?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager const a1 = await Cast(link?.anchor1, Doc, null); const a2 = await Cast(link?.anchor2, Doc, null); }); @@ -1097,7 +1096,7 @@ export class CurrentUserUtils { } } } else if (input.files && input.files.length !== 0) { - const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null); + const importDocs = CurrentUserUtils.MyImports; const disposer = OverlayView.ShowSpinner(); DocListCastAsync(importDocs.data).then(async list => { const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); @@ -1115,9 +1114,10 @@ export class CurrentUserUtils { } public static CaptureDashboardThumbnail() { + const activeDashboard = CurrentUserUtils.ActiveDashboard; const docView = CollectionDockingView.Instance.props.DocumentView?.(); const content = docView?.ContentDiv; - if (docView && content) { + if (docView && content && activeDashboard) { const _width = Number(getComputedStyle(content).width.replace("px","")); const _height = Number(getComputedStyle(content).height.replace("px","")); return CollectionFreeFormView.UpdateIcon( @@ -1130,20 +1130,22 @@ export class CurrentUserUtils { const proto = Cast(img.proto, Doc, null)!; proto["data-nativeWidth"] = _width; proto["data-nativeHeight"] = _height; - Doc.GetProto(CurrentUserUtils.ActiveDashboard).thumb = img; + Doc.GetProto(activeDashboard).thumb = img; }); } } public static async snapshotDashboard(userDoc: Doc) { - const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); - CurrentUserUtils.openDashboard(userDoc, copy); + if (CurrentUserUtils.ActiveDashboard) { + const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); + Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); + CurrentUserUtils.openDashboard(userDoc, copy); + } } public static closeActiveDashboard = () => { - Doc.UserDoc().activeDashboard = undefined; + CurrentUserUtils.ActiveDashboard = undefined; } public static createNewDashboard = async (userDoc: Doc, id?: string) => { @@ -1195,6 +1197,8 @@ export class CurrentUserUtils { public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); } public static get MyTools() { return DocCast(Doc.UserDoc().myTools); } public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } + public static get MyFileOrphans() { return DocCast(Doc.UserDoc().myFileOrphans); } + public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); } public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } @@ -1202,12 +1206,16 @@ export class CurrentUserUtils { public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } - public static get MyOverlayDocs() { return DocListCast(DocCast(Doc.UserDoc().myOverlayDocs)?.data); } + public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); } public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } + public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; } public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } + public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; } + public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); } + public static set ActivePage(val) { Doc.UserDoc().activePage = val; } public static get EmptyPane() { return DocCast(Doc.UserDoc().emptyPane); } - public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } - @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } + public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; } + public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } } ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { @@ -1219,7 +1227,7 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { } }); ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); -ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode"); +ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called"); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 6a26dfdc7..382274462 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -38,7 +38,7 @@ export class SettingsManager extends React.Component<{}> { @observable activeTab = "Accounts"; @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; } - @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; } + @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme; } constructor(props: {}) { super(props); @@ -59,7 +59,7 @@ export class SettingsManager extends React.Component<{}> { } } - @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice"); + @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.noviceMode = (e.currentTarget as any)?.value === "Novice"); @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value ? "title" : undefined); @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value); @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value); @@ -78,19 +78,21 @@ export class SettingsManager extends React.Component<{}> { @undoBatch @action changeColorScheme = action((e: React.ChangeEvent) => { + const activeDashboard= CurrentUserUtils.ActiveDashboard; + if (!activeDashboard) return; const scheme: ColorScheme = (e.currentTarget as any).value; switch (scheme) { case ColorScheme.Light: - CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + activeDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" }); break; case ColorScheme.Dark: - CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark; + activeDashboard.colorScheme = ColorScheme.Dark; addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" }); break; case ColorScheme.System: default: window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) }); break; } @@ -254,7 +256,7 @@ export class SettingsManager extends React.Component<{}> {
    Modes
    - @@ -270,7 +272,7 @@ export class SettingsManager extends React.Component<{}> {
    Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} /> + onChange={action(() => Doc.defaultAclPrivate = !Doc.defaultAclPrivate)} />
    Default access private
    diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index f439d4488..d77633b8d 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -369,7 +369,7 @@ export class SharingManager extends React.Component<{}> { const dropdownValues: string[] = Object.values(SharingPermissions); if (!uniform) dropdownValues.unshift("-multiple-"); if (override) dropdownValues.unshift("None"); - return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => + return dropdownValues.filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => (
    - {Doc.UserDoc().noviceMode ? (null) : + {Doc.noviceMode ? (null) :
    this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} />
    } diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index efc1644fe..9a0f25fe3 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -34,8 +34,8 @@ export class DashboardView extends React.Component { clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => { if (e.detail === 2) { - Doc.UserDoc().activeDashboard = dashboard; - Doc.UserDoc().activePage = "dashboard"; + CurrentUserUtils.ActiveDashboard = dashboard; + CurrentUserUtils.ActivePage = "dashboard"; } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index be5927497..169bd3873 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -65,7 +65,7 @@ export function ViewBoxBaseComponent

    () { isContentActive = (outsideReaction?: boolean) => ( this.props.isContentActive?.() === false ? false : - (CurrentUserUtils.SelectedTool !== InkTool.None || + (CurrentUserUtils.ActiveTool !== InkTool.None || (this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction)) ? true : undefined)) @@ -200,7 +200,7 @@ export function ViewBoxAnnotatableComponent

    () if (effectiveAcl === AclAugment) { added.map(doc => { - if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); + if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); doc.context = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); @@ -213,7 +213,7 @@ export function ViewBoxAnnotatableComponent

    () doc.context = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); + CurrentUserUtils.ActiveDashboard && inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 0bbe473d7..9b8f7238d 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -347,7 +347,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV

    {this.menuButton}
    - {/* {Doc.UserDoc().noviceMode ? (null) :
    + {/* {Doc.noviceMode ? (null) :
    {this.moreButton}
    } */}
    ; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 6afe64868..e960f5cca 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -129,7 +129,7 @@ export class GestureOverlay extends Touchable { // and this seems to be the only way of differentiating pen and touch on touch events if (pt.radiusX > 1 && pt.radiusY > 1) { InkTranscription.Instance.createInkGroup(); - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; this.prevPoints.set(pt.identifier, pt); } } @@ -498,18 +498,18 @@ export class GestureOverlay extends Touchable { setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => { if (doubleTap) { InkTranscription.Instance.createInkGroup(); - CurrentUserUtils.SelectedTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; return; } })); } - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { - CurrentUserUtils.SelectedTool = InkTool.Write; + CurrentUserUtils.ActiveTool = InkTool.Write; } this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); - // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); + // if (CurrentUserUtils.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } } @@ -606,7 +606,7 @@ export class GestureOverlay extends Touchable { this._points = []; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { this.InkShape = ""; - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; } } // if we're not drawing in a toolglass try to recognize as gesture diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 16d7c9a23..f5122df3f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -57,7 +57,7 @@ export class KeyManager { public handle = action((e: KeyboardEvent) => { if (e.key?.toLowerCase() === "shift") DocumentDecorations.Instance.AddToSelection = true; - //if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); + //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); const keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); @@ -112,7 +112,7 @@ export class KeyManager { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; InkStrokeProperties.Instance._controlButton = false; - CurrentUserUtils.SelectedTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; DragManager.CompleteWindowDrag?.(true); var doDeselect = true; if (SnappingManager.GetIsDragging()) { @@ -241,9 +241,9 @@ export class KeyManager { } } break; - case "e": CurrentUserUtils.SelectedTool = InkTool.Eraser; + case "e": CurrentUserUtils.ActiveTool = InkTool.Eraser; break; - case "p": CurrentUserUtils.SelectedTool = InkTool.Pen; + case "p": CurrentUserUtils.ActiveTool = InkTool.Pen; break; case "o": const target = SelectionManager.Docs().lastElement(); diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 0449da483..471ad09e9 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -25,8 +25,8 @@ export class InkStrokeProperties { constructor() { InkStrokeProperties._Instance = this; - reaction(() => this._controlButton, button => button && (CurrentUserUtils.SelectedTool = InkTool.None)); - reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); + reaction(() => this._controlButton, button => button && (CurrentUserUtils.ActiveTool = InkTool.None)); + reaction(() => CurrentUserUtils.ActiveTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); } /** diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 8ab54918c..487bbcd00 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -254,7 +254,7 @@ export class InkTranscription extends React.Component { */ createInkGroup() { // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (CurrentUserUtils.SelectedTool === InkTool.Write) { + if (CurrentUserUtils.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those const selected = ffView.unprocessedDocs; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index aa5a815ac..35d89d2b1 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -345,7 +345,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(), onContextMenu: () => { const cm = ContextMenu.Instance; - !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" }); + !Doc.noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" }); cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" }); cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" }); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7e3916b3f..791c64630 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -381,7 +381,7 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} - styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} + styleProvider={this._sidebarContent.proto === CurrentUserUtils.MyDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} ScreenToLocalTransform={this.mainContainerXf} @@ -656,7 +656,7 @@ export class MainView extends React.Component { {this.mainDashboardArea} ; case "home": return ; - } })(StrCast(Doc.UserDoc().activePage)) + } })(CurrentUserUtils.ActivePage) } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index bdb3bb857..598fff29a 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import * as React from "react"; import ReactLoading from 'react-loading'; -import { Doc, WidthSym, HeightSym } from "../../fields/Doc"; +import { Doc, WidthSym, HeightSym, DocListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { Cast, NumCast } from "../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../Utils"; @@ -149,7 +149,7 @@ export class OverlayView extends React.Component { } removeOverlayDoc = (doc: Doc | Doc[]) => { - (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc)); + (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc)); return true; } @@ -158,7 +158,7 @@ export class OverlayView extends React.Component { }.bind(this)); @computed get overlayDocs() { - return CurrentUserUtils.MyOverlayDocs?.map(d => { + return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).map(d => { let offsetx = 0, offsety = 0; const dref = React.createRef(); const onPointerMove = action((e: PointerEvent, down: number[]) => { @@ -172,7 +172,7 @@ export class OverlayView extends React.Component { dragData.dropAction = "move"; dragData.removeDocument = (doc: Doc | Doc[]) => { const docs = (doc instanceof Doc) ? [doc] : doc; - docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocs, Doc, null), "data", d)); + docs.forEach(d => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, d)); return true; }; dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 74055f057..9c6d9a108 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -200,7 +200,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {list}
    - {Doc.UserDoc().noviceMode ? (null) :
    Edit onClick Script
    } + {Doc.noviceMode ? (null) :
    Edit onClick Script
    }
    ; } @computed @@ -229,7 +229,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree; const isTabView = this.selectedTabView; const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) =>
    {ele}
    ; - const isNovice = Doc.UserDoc().noviceMode; + const isNovice = Doc.noviceMode; return !this.selectedDoc ? (null) :
    {toggle(this.titleButton)} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 2cf334ae1..90c86fa18 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -11,7 +11,7 @@ import { Id } from "../../fields/FieldSymbols"; import { InkField } from "../../fields/InkField"; import { List } from "../../fields/List"; import { ComputedField } from "../../fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; +import { Cast, NumCast, StrCast, DocCast } from "../../fields/Types"; import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils"; import { DocumentType } from "../documents/DocumentTypes"; @@ -340,7 +340,7 @@ export class PropertiesView extends React.Component { */ @undoBatch changePermissions = (e: any, user: string) => { - const docs = SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.props.Document); + const docs = SelectionManager.Views().length < 2 ? (this.selectedDoc ? [this.selectedDoc]:[]) : SelectionManager.Views().map(docView => docView.props.Document); SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); } @@ -355,7 +355,7 @@ export class PropertiesView extends React.Component { value={permission} onChange={e => this.changePermissions(e, user)}> {dropdownValues.filter(permission => - !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => + !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => )} ; } @@ -419,7 +419,7 @@ export class PropertiesView extends React.Component { // all selected docs const docs = SelectionManager.Views().length < 2 ? - [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc[DataSym]] + [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc?.[DataSym]] : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]); const target = docs[0]; @@ -907,7 +907,7 @@ export class PropertiesView extends React.Component { {!this.openSharing ? (null) :
    - {!Doc.UserDoc().noviceMode ? (
    + {!Doc.noviceMode ? (
    this.layoutDocAcls = !this.layoutDocAcls)} @@ -931,42 +931,46 @@ export class PropertiesView extends React.Component { * If it doesn't exist, it creates it. */ checkFilterDoc() { - if (!this.selectedDoc.currentFilter) this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); + if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = CurrentUserUtils.createFilterDoc(); } /** * Creates a new currentFilter for this.filterDoc, */ createNewFilterDoc = () => { - const currentDocFilters = this.selectedDoc._docFilters; - const currentDocRangeFilters = this.selectedDoc._docRangeFilters; - this.selectedDoc._docFilters = new List(); - this.selectedDoc._docRangeFilters = new List(); - (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; - (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; - this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); + if (this.selectedDoc) { + const currentDocFilters = this.selectedDoc._docFilters; + const currentDocRangeFilters = this.selectedDoc._docRangeFilters; + this.selectedDoc._docFilters = new List(); + this.selectedDoc._docRangeFilters = new List(); + (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; + (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; + this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); + } } /** * Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter */ updateFilterDoc = (doc: Doc) => { - if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc - const savedDocFilters = doc._docFiltersList; - const currentDocFilters = this.selectedDoc._docFilters; - this.selectedDoc._docFilters = new List(); - (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; - this.selectedDoc.currentFilter = doc; - doc._docFiltersList = new List(); - this.selectedDoc._docFilters = savedDocFilters; - - const savedDocRangeFilters = doc._docRangeFiltersList; - const currentDocRangeFilters = this.selectedDoc._docRangeFilters; - this.selectedDoc._docRangeFilters = new List(); - (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; - this.selectedDoc.currentFilter = doc; - doc._docRangeFiltersList = new List(); - this.selectedDoc._docRangeFilters = savedDocRangeFilters; + if (this.selectedDoc) { + if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc + const savedDocFilters = doc._docFiltersList; + const currentDocFilters = this.selectedDoc._docFilters; + this.selectedDoc._docFilters = new List(); + (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; + this.selectedDoc.currentFilter = doc; + doc._docFiltersList = new List(); + this.selectedDoc._docFilters = savedDocFilters; + + const savedDocRangeFilters = doc._docRangeFiltersList; + const currentDocRangeFilters = this.selectedDoc._docRangeFilters; + this.selectedDoc._docRangeFilters = new List(); + (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; + this.selectedDoc.currentFilter = doc; + doc._docRangeFiltersList = new List(); + this.selectedDoc._docRangeFilters = savedDocRangeFilters; + } } @computed get filtersSubMenu() { @@ -1059,13 +1063,13 @@ export class PropertiesView extends React.Component {
    - {!Doc.UserDoc().noviceMode && this.openFields ?
    + {!Doc.noviceMode && this.openFields ?
    {this.fieldsCheckbox}
    Layout
    : null} {!this.openFields ? (null) :
    - {Doc.UserDoc().noviceMode ? this.noviceFields : this.expandedField} + {Doc.noviceMode ? this.noviceFields : this.expandedField}
    }
    ; } @@ -1122,7 +1126,7 @@ export class PropertiesView extends React.Component { @undoBatch handleDescriptionChange = action((value: string) => { - if (LinkManager.currentLink) { + if (LinkManager.currentLink && this.selectedDoc) { this.selectedDoc.description = value; this.description = value; return true; @@ -1131,7 +1135,7 @@ export class PropertiesView extends React.Component { @undoBatch handleLinkRelationshipChange = action((value: string) => { - if (LinkManager.currentLink) { + if (LinkManager.currentLink && this.selectedDoc) { this.selectedDoc.linkRelationship = value; this.relationship = value; return true; @@ -1188,7 +1192,7 @@ export class PropertiesView extends React.Component { @undoBatch changeFollowBehavior = action((follow: string) => { - if (LinkManager.currentLink) { + if (LinkManager.currentLink && this.selectedDoc) { this.selectedDoc.followLinkLocation = follow; return true; } @@ -1219,18 +1223,18 @@ export class PropertiesView extends React.Component { } toggleAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove)))); } toggleArrow = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)))); } toggleZoomToTarget1 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor1).followLinkZoom = !DocCast(this.selectedDoc.anchor1).followLinkZoom)))); } toggleZoomToTarget2 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor2).followLinkZoom = !DocCast(this.selectedDoc.anchor2).followLinkZoom)))); } @computed @@ -1238,7 +1242,7 @@ export class PropertiesView extends React.Component { return this.handleLinkRelationshipChange(e.currentTarget.value)} @@ -1252,7 +1256,7 @@ export class PropertiesView extends React.Component { return this.handleDescriptionChange(e.currentTarget.value)} @@ -1273,7 +1277,7 @@ export class PropertiesView extends React.Component { // } render() { - const isNovice = BoolCast(Doc.UserDoc().noviceMode); + const isNovice = Doc.noviceMode; if (!this.selectedDoc && !this.isPres) { return
    diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 636f7042f..689ee4fc1 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -15,6 +15,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { DefaultStyleProvider } from "./StyleProvider"; import './TemplateMenu.scss'; import React = require("react"); +import { CurrentUserUtils } from "../util/CurrentUserUtils"; @observer class TemplateToggle extends React.Component<{ template: string, checked: boolean, toggle: (event: React.ChangeEvent, template: string) => void }> { @@ -111,22 +112,22 @@ export class TemplateMenu extends React.Component { const firstDoc = this.props.docViews[0].props.Document; const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", ""); const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data); - const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); + const addedTypes = Doc.noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); const layout = Doc.Layout(firstDoc); const templateMenu: Array = []; this.props.templates?.forEach((checked, template) => templateMenu.push()); templateMenu.push(); templateMenu.push(); - !Doc.UserDoc().noviceMode && templateMenu.push(); + !Doc.noviceMode && templateMenu.push(); addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template)); this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push( this.toggleLayout(e, template)} />)); return
      - {Doc.UserDoc().noviceMode ? (null) : } + {Doc.noviceMode ? (null) : } {templateMenu} - {Doc.UserDoc().noviceMode ? (null) : { stack.header?.element.on('mousedown', (e: any) => { - if (e.target === stack.header?.element[0] && e.button === 2) { - const dashboard= CurrentUserUtils.ActiveDashboard; + const dashboard = CurrentUserUtils.ActiveDashboard; + if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; const docToAdd = Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}`, @@ -462,12 +462,14 @@ export class CollectionDockingView extends CollectionSubView() { .click(action(() => { // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size const dashboard = CurrentUserUtils.ActiveDashboard; - dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; - const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}` - }); - this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); - CollectionDockingView.AddSplit(docToAdd, "", stack); + if (dashboard) { + dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; + const docToAdd = Docs.Create.FreeformDocument([], { + _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}` + }); + this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); + CollectionDockingView.AddSplit(docToAdd, "", stack); + } })); } @@ -480,6 +482,6 @@ ScriptingGlobals.add(function openInLightbox(doc: any) { LightboxView.AddDocTab( "opens up document in a lightbox", "(doc: any)"); ScriptingGlobals.add(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); }, "opens up document in tab on right side of the screen", "(doc: any)"); -ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); }, +ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc); }, "opens up document in screen overlay layer", "(doc: any)"); ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); }); \ No newline at end of file diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 1c25421f5..9b1bb5b97 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -268,17 +268,20 @@ export class CollectionViewBaseChrome extends React.Component { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }), initialize: (button: Doc) => { - button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) instanceof ObjectField ? - ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) as any as ObjectField) : undefined; - button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined; + const activeDash = CurrentUserUtils.ActiveDashboard; + if (activeDash) { + button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) instanceof ObjectField ? + ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) as any as ObjectField) : undefined; + button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined; + } }, }; - @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } - @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } - @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } - @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } - @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } + @computed get _freeform_commands() { return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } + @computed get _stacking_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } + @computed get _masonry_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } + @computed get _schema_commands() { return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } + @computed get _doc_commands() { return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } @computed get _tree_commands() { return undefined; } private get _buttonizableCommands() { switch (this.props.type) { @@ -467,7 +470,7 @@ export class CollectionViewBaseChrome extends React.Component { const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); - //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); + //Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc); CollectionDockingView.AddSplit(doc, "right"); } @@ -691,8 +694,8 @@ export class CollectionFreeFormViewChrome extends React.Component Back Frame
    } placement="bottom">
    @@ -879,7 +882,7 @@ export class CollectionStackingViewChrome extends React.Component key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index dddae4a34..277fcd59c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -598,7 +598,7 @@ export class CollectionStackingView extends CollectionSubView {buttonMenu || noviceExplainer ?
    {buttonMenu ? this.buttonMenu : null} - {Doc.UserDoc().noviceMode && noviceExplainer ? + {Doc.noviceMode && noviceExplainer ?
    {noviceExplainer}
    diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 7f96217b8..f3a798143 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -224,8 +224,8 @@ export class CollectionStackingViewFieldColumn extends React.Component { Doc.GetProto(this.props.Document)[name] = ""; const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index e809bfbce..ba72fb7b9 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -67,7 +67,7 @@ export class CollectionTreeView extends CollectionSubView this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); - isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || + isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.ActiveTool !== InkTool.None || (this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction)) ? true : false) @@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { const layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: "Make tree state " + (this.doc.treeViewOpenIsTransient ? "persistent" : "transient"), event: () => this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient, icon: "paint-brush" }); layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" }); @@ -286,7 +286,7 @@ export class CollectionTreeView extends CollectionSubView {this.rootDoc.explainer}
    ; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 965f0a352..4b5c5e3fb 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -160,8 +160,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent func(CollectionViewType.Masonry), icon: "columns" }); subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" }); subItems.push({ description: "3D Carousel", event: () => func(CollectionViewType.Carousel3D), icon: "columns" }); - !Doc.UserDoc().noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); - !Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); + !Doc.noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); + !Doc.noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" }); if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { @@ -184,16 +184,16 @@ export class CollectionView extends ViewBoxAnnotatableComponent this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null; + !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null; if (this.rootDoc.childLayout instanceof Doc) { optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" }); } if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" }); } - !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" }); + !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" }); - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { optionItems.push({ description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram" }); @@ -210,7 +210,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 022b9fa24..70db121d1 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -277,7 +277,7 @@ export class TabDocView extends React.Component { pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array }); if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { - const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); + const docs = Cast(CurrentUserUtils.MyOverlayDocs.data, listSpec(Doc), []); if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); CollectionDockingView.AddSplit(curPres, "right"); setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 59dc5671b..704b8989a 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -114,7 +114,7 @@ export class TreeView extends React.Component { return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : this.props.treeView.dashboardMode ? this.fieldKey : this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : // for displaying - this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); + this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); } @computed get doc() { return this.props.document; } @@ -560,8 +560,8 @@ export class TreeView extends React.Component { const links = () => DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? "links" : ""; const data = () => this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ""; const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases"; - const fields = () => Doc.UserDoc().noviceMode ? "" : "fields"; - const layout = (Doc.UserDoc().noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; + const fields = () => Doc.noviceMode ? "" : "fields"; + const layout = (Doc.noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m); } @action diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c07e44fcc..ffe146ae4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -458,7 +458,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { @@ -834,7 +834,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.MyOverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -1031,7 +1031,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); - !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); @@ -1640,27 +1640,27 @@ export class CollectionFreeFormView extends CollectionSubView s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" }); - !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; + !Doc.noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); const viewctrls = ContextMenu.Instance.findByDescription("UI Controls..."); const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : []; - !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; - !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); const options = ContextMenu.Instance.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; - !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode && + !this.props.isAnnotationOverlay && !Doc.noviceMode && optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" }); this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); } !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); const mores = ContextMenu.Instance.findByDescription("More..."); const moreItems = mores && "subitems" in mores ? mores.subitems : []; - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) }); moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) }); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 051da795f..b62020a04 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -330,7 +330,7 @@ export class MarqueeView extends React.Component { if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { - if (CurrentUserUtils.SelectedTool === InkTool.None) { + if (CurrentUserUtils.ActiveTool === InkTool.None) { if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; if (!this.props.trySelectCluster(e.shiftKey)) { @@ -367,7 +367,7 @@ export class MarqueeView extends React.Component { Doc.GetProto(doc).data = new List(selected); Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; - !this.props.isAnnotationOverlay && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(doc)); + !this.props.isAnnotationOverlay && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, Doc.GetProto(doc)); doc._panX = doc._panY = 0; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); @@ -643,7 +643,7 @@ export class MarqueeView extends React.Component e.preventDefault()} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index 0875c80b3..9653f2808 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -321,7 +321,7 @@ export class KeysDropdown extends React.Component { const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"]; const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const showKeys = new Set(); - [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.UserDoc().noviceMode || + [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.noviceMode || whitelistKeys.includes(key) || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null); return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm)); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 669622455..8e24fce11 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -348,11 +348,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { @undoBatch @action static switchColor(color: ColorState) { - // Doc.UserDoc().backgroundColor = Utils.colorString(color); // bcz: this can't go here ... needs a proper home in the settings panel SetActiveInkColor(color.hex); SelectionManager.Views().map(view => { @@ -49,7 +48,7 @@ export class ColorBox extends ViewBoxBaseComponent() { style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }} > CurrentUserUtils.SelectedTool === InkTool.None && ColorBox.switchColor(c)} + onChange={c => CurrentUserUtils.ActiveTool === InkTool.None && ColorBox.switchColor(c)} color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e2b37d2b4..4d84a8ad2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -327,7 +327,7 @@ export class DocumentViewInternal extends DocComponent 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { @@ -543,9 +543,9 @@ export class DocumentViewInternal extends DocComponent { - if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.SelectedTool === InkTool.Eraser) return; + if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.ActiveTool === InkTool.Eraser) return; // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) - if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) { + if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool))) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { e.stopPropagation(); if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it @@ -563,7 +563,7 @@ export class DocumentViewInternal extends DocComponent { if (e.cancelBubble) return; - if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return; + if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool))) return; - if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { + if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); @@ -697,7 +697,7 @@ export class DocumentViewInternal extends DocComponent { - if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) { + if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); @@ -747,8 +747,8 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, "add:right"), icon: "eye" }); - !Doc.UserDoc().noviceMode && appearanceItems.push({ + !Doc.noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" }); + !Doc.noviceMode && appearanceItems.push({ description: "Add a Field", event: () => { const alias = Doc.MakeAlias(this.rootDoc); alias.layout = FormattedTextBox.LayoutString("newfield"); @@ -765,7 +765,7 @@ export class DocumentViewInternal extends DocComponent this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" }); + !Doc.noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" }); const existingOnClick = cm.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; @@ -778,8 +778,8 @@ export class DocumentViewInternal extends DocComponent this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" }); if (!this.Document.annotationOn) { @@ -803,7 +803,7 @@ export class DocumentViewInternal extends DocComponent this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) }); funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) }); funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined }); @@ -813,8 +813,8 @@ export class DocumentViewInternal extends DocComponent SharingManager.Instance.open(this.props.DocumentView()), icon: "users" }); - if (!Doc.UserDoc().noviceMode) { + (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" }); + if (!Doc.noviceMode) { moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" }); @@ -835,9 +835,9 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); - !Doc.UserDoc().noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); - !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); - !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); + !Doc.noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); + !Doc.noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); + !Doc.noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); } @@ -860,7 +860,7 @@ export class DocumentViewInternal extends DocComponent Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view); isContentActive = (outsideReaction?: boolean) => { return this.props.isContentActive() === false ? false : ( - CurrentUserUtils.SelectedTool !== InkTool.None || + CurrentUserUtils.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.props.Document.forceActive || diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 5add09653..17b57cb3b 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -8,7 +8,7 @@ import { List } from "../../../fields/List"; import { RichTextField } from "../../../fields/RichTextField"; import { listSpec } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { Cast, NumCast, StrCast, DocCast } from "../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; @@ -84,7 +84,7 @@ export class FilterBox extends ViewBoxBaseComponent() { return "data"; } @computed static get targetDocChildren() { - return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard.data); + return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard?.data); } @observable _loaded = false; @@ -115,7 +115,7 @@ export class FilterBox extends ViewBoxBaseComponent() { const keys = new Set(noviceFields); this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); - return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.UserDoc().noviceMode).sort(); + return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.noviceMode).sort(); } @@ -167,23 +167,25 @@ export class FilterBox extends ViewBoxBaseComponent() { removeFilterDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false; public removeFilter = (filterName: string) => { const targetDoc = FilterBox.targetDoc; - const filterDoc = targetDoc.currentFilter as Doc; - const attributes = DocListCast(filterDoc.data); - const found = attributes.findIndex(doc => doc.title === filterName); - if (found !== -1) { - (filterDoc.data as List).splice(found, 1); - const docFilter = Cast(targetDoc._docFilters, listSpec("string")); - if (docFilter) { - let index: number; - while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) { - docFilter.splice(index, 1); + if (targetDoc) { + const filterDoc = targetDoc.currentFilter as Doc; + const attributes = DocListCast(filterDoc.data); + const found = attributes.findIndex(doc => doc.title === filterName); + if (found !== -1) { + (filterDoc.data as List).splice(found, 1); + const docFilter = Cast(targetDoc._docFilters, listSpec("string")); + if (docFilter) { + let index: number; + while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) { + docFilter.splice(index, 1); + } } - } - const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string")); - if (docRangeFilters) { - let index: number; - while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) { - docRangeFilters.splice(index, 3); + const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string")); + if (docRangeFilters) { + let index: number; + while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) { + docRangeFilters.splice(index, 3); + } } } } @@ -194,6 +196,7 @@ export class FilterBox extends ViewBoxBaseComponent() { */ facetClick = (facetHeader: string) => { const { targetDoc, targetDocChildren } = FilterBox; + if (!targetDoc) return; const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader); if (found !== -1) { this.removeFilter(facetHeader); @@ -267,7 +270,7 @@ export class FilterBox extends ViewBoxBaseComponent() { */ @action changeBool = (e: any) => { - (FilterBox.targetDoc.currentFilter as Doc).filterBoolean = e.currentTarget.value; + FilterBox.targetDoc && (DocCast(FilterBox.targetDoc.currentFilter).filterBoolean = e.currentTarget.value); } /** @@ -322,7 +325,7 @@ export class FilterBox extends ViewBoxBaseComponent() { * Changes the title of the filterDoc */ onTitleValueChange = (val: string) => { - this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc.title}`; + this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc?.title}`; return true; } @@ -373,7 +376,7 @@ export class FilterBox extends ViewBoxBaseComponent() {
    - diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 8c27b3508..60eb48114 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -179,7 +179,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(this.choosePath(field.url)), icon: "expand-arrows-alt" }); - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); @@ -357,7 +357,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent; } marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index b0b050cea..b58a9affb 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -52,7 +52,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - !Doc.UserDoc().noviceMode && funcs.push({ + !Doc.noviceMode && funcs.push({ description: "Clear Script Params", event: () => { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); params?.map(p => this.paramsDoc[p] = undefined); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 5f4c17ee6..0b7264d79 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -471,7 +471,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; @@ -570,7 +570,7 @@ export class MapBox extends ViewBoxAnnotatableComponent; + // pointerEvents={CurrentUserUtils.ActiveTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />; return
    {/*console.log(apiKey)*/} {/* Loading
    :
    @@ -484,7 +484,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.layoutDoc._currentTimecode, () => !this._playing && this.Seek(NumCast(this.layoutDoc._currentTimecode))); this._disposers.youtubeReactionDisposer = reaction( - () => CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, + () => CurrentUserUtils.ActiveTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true }); }; if (typeof (YT) === undefined) setTimeout(() => this.loadYouTube(iframe), 100); @@ -707,7 +707,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 967158cbf..d14af49ea 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); + !Doc.noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); funcs.push({ description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => { this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts; @@ -561,7 +561,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; @@ -678,7 +678,7 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}> diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index f29dfe489..3af6a3d51 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -1,6 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; +import { StringIterator } from 'lodash'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -70,7 +71,7 @@ export class FontIconBox extends DocComponent() { useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); }; specificContextMenu = (): void => { - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { const cm = ContextMenu.Instance; cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" }); cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" }); @@ -269,7 +270,7 @@ export class FontIconBox extends DocComponent() { // Get items to place into the list const list = this.buttonList.map((value) => { - if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) { + if (Doc.noviceMode && !noviceList.includes(value)) { return; } return
    = 1) return Colors.MEDIUM_BLUE; + if (checkResult) { + if (NumCast(selected?.Document.z) >= 1) return Colors.MEDIUM_BLUE; return "transparent"; } selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed"); @@ -710,7 +711,7 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) { export function checkInksToGroup() { // console.log("getting here to inks group"); - if (CurrentUserUtils.SelectedTool === InkTool.Write) { + if (CurrentUserUtils.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other @@ -723,7 +724,7 @@ export function checkInksToGroup() { export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (CurrentUserUtils.SelectedTool === InkTool.Write) { + if (CurrentUserUtils.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those const selected = ffView.unprocessedDocs; @@ -785,38 +786,38 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { /** INK - * setActiveInkTool + * setActiveTool * setStrokeWidth * setStrokeColor **/ -ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) { +ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); if (checkResult) { - return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? + return ((CurrentUserUtils.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? Colors.MEDIUM_BLUE : "transparent"; } if (["circle", "square", "line"].includes(tool)) { if (GestureOverlay.Instance.InkShape === tool) { - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; GestureOverlay.Instance.InkShape = InkTool.None; } else { - Doc.UserDoc().activeInkTool = InkTool.Pen; + CurrentUserUtils.ActiveTool = InkTool.Pen; GestureOverlay.Instance.InkShape = tool; } } else if (tool) { // pen or eraser - if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { - Doc.UserDoc().activeInkTool = InkTool.None; - } else if (tool == "write") { + if (CurrentUserUtils.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { + CurrentUserUtils.ActiveTool = InkTool.None; + } else if (tool == InkTool.Write) { // console.log("write mode selected - create groupDoc here!", tool) - Doc.UserDoc().activeInkTool = tool; + CurrentUserUtils.ActiveTool = tool; GestureOverlay.Instance.InkShape = ""; } else { - Doc.UserDoc().activeInkTool = tool; + CurrentUserUtils.ActiveTool = tool as any; GestureOverlay.Instance.InkShape = ""; } } else { - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; } }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 16a523b40..90199618b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -648,7 +648,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const highlighting: ContextMenuProps[] = []; const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"]; const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; - (Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => + (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => highlighting.push({ description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { e.stopPropagation(); @@ -663,11 +663,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp })); const uicontrols: ContextMenuProps[] = []; - !Doc.UserDoc().noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); + !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); - !Doc.UserDoc().noviceMode && uicontrols.push({ + !Doc.noviceMode && uicontrols.push({ description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" }); @@ -677,7 +677,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); - !Doc.UserDoc().noviceMode && appearanceItems.push({ + !Doc.noviceMode && appearanceItems.push({ description: "Make Default Layout", event: () => { if (!this.layoutDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); @@ -1636,7 +1636,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp noSidebar={true} fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />; }; - return
    {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
    ; @@ -1647,7 +1647,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const active = this.props.isContentActive(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); + const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 9bc2e5628..98343a261 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -67,7 +67,6 @@ export class RichTextMenu extends AntimodeMenu { runInAction(() => { RichTextMenu.Instance = this; this._canFade = false; - //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); this.Pinned = true; }); } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 52b03b0a5..0d2cffc2c 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -134,7 +134,7 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); - if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); + if (CurrentUserUtils.ActivePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" @@ -178,16 +178,16 @@ export class PresBox extends ViewBoxBaseComponent() { this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; this.turnOffEdit(true); - DocListCastAsync((Doc.UserDoc().myTrails as Doc).data).then(pres => - !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.UserDoc().myTrails as Doc, "data", this.rootDoc)); + DocListCastAsync(CurrentUserUtils.MyTrails.data).then(pres => + !pres?.includes(this.rootDoc) && Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", this.rootDoc)); this._disposers.selection = reaction(() => SelectionManager.Views(), views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()); } @action updateCurrentPresentation = (pres?: Doc) => { - if (pres) Doc.UserDoc().activePresentation = pres; - else Doc.UserDoc().activePresentation = this.rootDoc; + if (pres) CurrentUserUtils.ActivePresentation = pres; + else CurrentUserUtils.ActivePresentation = this.rootDoc; document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); document.addEventListener("keydown", PresBox.keyEventsWrapper, true); this._presKeyEventsActive = true; @@ -623,9 +623,9 @@ export class PresBox extends ViewBoxBaseComponent() { */ @action updateMinimize = async () => { - if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { + if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); + Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, "right"); } else { this.layoutDoc.presStatus = PresStatus.Edit; @@ -635,7 +635,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.rootDoc.y = pt[1] + 10; this.rootDoc._height = 30; this.rootDoc._width = 248; - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); this.props.removeDocument?.(this.layoutDoc); } } @@ -732,7 +732,7 @@ export class PresBox extends ViewBoxBaseComponent() { removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && !this.layoutDoc._lockedPosition) && + isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) /** @@ -868,7 +868,7 @@ export class PresBox extends ViewBoxBaseComponent() { } break; case "Escape": - if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); } + if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.updateMinimize(); } else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; } else this.layoutDoc.presStatus = "edit"; if (this._presTimer) clearTimeout(this._presTimer); @@ -2247,7 +2247,7 @@ export class PresBox extends ViewBoxBaseComponent() { const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel"; const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); + const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; return (mode === CollectionViewType.Carousel3D) ? (null) : ( @@ -2305,7 +2305,7 @@ export class PresBox extends ViewBoxBaseComponent() { value={mode}> - {Doc.UserDoc().noviceMode ? (null) : } + {Doc.noviceMode ? (null) : } }
    @@ -2508,10 +2508,10 @@ export class PresBox extends ViewBoxBaseComponent() { // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); + const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); - return CurrentUserUtils.MyOverlayDocs.includes(this.rootDoc) ? + return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc) ?
    e.stopPropagation()}>
    {"Loop"}
    }>
    () {
    : -
    +
    {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 50df00612..1a2f4b93f 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -309,7 +309,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get recordingIsInOverlay() { let isInOverlay = false - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { + DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides === this.rootDoc) { isInOverlay = true return @@ -319,9 +319,9 @@ export class PresElementBox extends ViewBoxBaseComponent() { } removeAllRecordingInOverlay = () => { - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { + DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides === this.rootDoc) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); + Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc); } }) } @@ -339,7 +339,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { this.removeAllRecordingInOverlay() if (activeItem.recording) { // if we already have an existing recording - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null)); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); } } @@ -348,15 +348,15 @@ export class PresElementBox extends ViewBoxBaseComponent() { @action startRecording = (activeItem: Doc) => { // Remove every recording that already exists in overlay view - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { + DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides !== null) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); + Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc); } }) if (activeItem.recording) { // if we already have an existing recording - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null)); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); } else { // if we dont have any recording @@ -376,7 +376,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { // make recording box appear in the bottom right corner of the screen recording.x = window.innerWidth - recording[WidthSym]() - 20; recording.y = window.innerHeight - recording[HeightSym]() - 20; - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, recording); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, recording); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0ded1bb3c..2869d4f2d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -340,7 +340,7 @@ export class PDFViewer extends React.Component { if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) { this._setPreviewCursor?.(e.clientX, e.clientY, true, false); } - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { this.props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 0db3950a2..1de0c0b10 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -27,12 +27,12 @@ import "./TopBar.scss"; export class TopBar extends React.Component { navigateToHome = () => { CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => { - Doc.UserDoc().activePage = "home"; + CurrentUserUtils.ActivePage = "home"; CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use }); } render() { - const activeDashboard = Cast(Doc.UserDoc().activeDashboard, Doc, null) + const activeDashboard = CurrentUserUtils.ActiveDashboard; return ( //TODO:glr Add support for light / dark mode
    @@ -44,11 +44,11 @@ export class TopBar extends React.Component { }}>{Doc.CurrentUserEmail}
    : (null)}
    -
    SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}> +
    activeDashboard && SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}> {activeDashboard ? StrCast(activeDashboard.title) : "Dash"}
    { - const dashView = DocumentManager.Instance.getDocumentView(activeDashboard); + const dashView = activeDashboard && DocumentManager.Instance.getDocumentView(activeDashboard); ContextMenu.Instance.addItem({ description: "Open Dashboard View", event: this.navigateToHome, icon: "edit" }); ContextMenu.Instance.addItem({ description: "Snapshot Dashboard", event: async () => { const batch = UndoManager.StartBatch("snapshot"); @@ -69,7 +69,7 @@ export class TopBar extends React.Component {
    {SharingManager.Instance.open(undefined, activeDashboard)}}> {/* TODO: if this is my dashboard, display share if this is a shared dashboard, display "view original or view annotated" */} - { Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author === Doc.CurrentUserEmail ? "Share": "view original" } + { CurrentUserUtils.ActiveDashboard && (Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author === Doc.CurrentUserEmail ? "Share": "view original") }
    window.open( "https://brown-dash.github.io/Dash-Documentation/", "_blank")}> diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index e0d328c89..6c0c9b301 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -71,7 +71,7 @@ export class DashWebRTCVideo extends React.Component; const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 94be286f5..74213652b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -21,7 +21,7 @@ import { FieldId, RefField } from "./RefField"; import { RichTextField } from "./RichTextField"; import { listSpec } from "./Schema"; import { ComputedField, ScriptField } from "./ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField"; import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; import JSZip = require("jszip"); @@ -233,6 +233,10 @@ export class Doc extends RefField { } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; + public static get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode); } + public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } + public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } + public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; } public static CurrentUserEmail: string = ""; public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); } public async [HandleUpdate](diff: any) { @@ -805,7 +809,7 @@ export namespace Doc { Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy); } copy.context = undefined; - Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } @@ -861,7 +865,7 @@ export namespace Doc { const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")"); target.layoutKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (applied["acl-Public"] = "Not Shared"); return applied; } return undefined; @@ -1234,19 +1238,19 @@ export namespace Doc { export function isDocPinned(doc: Doc) { //add this new doc to props.Document - const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + const curPres = CurrentUserUtils.ActivePresentation; return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; } export function copyDragFactory(dragFactory: Doc) { const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); - ndoc && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(ndoc)); + ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc)); if (ndoc && dragFactory["dragFactory-count"] !== undefined) { dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); } - if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); + if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); return ndoc; } @@ -1448,7 +1452,7 @@ ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a docum ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); }); ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); }); ScriptingGlobals.add(function activePresentationItem() { - const curPres = Doc.UserDoc().activePresentation as Doc; + const curPres = CurrentUserUtils.ActivePresentation; return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; }); ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { diff --git a/src/fields/util.ts b/src/fields/util.ts index 37b9be31b..8fb35981b 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -145,7 +145,7 @@ export function inheritParentAcls(parent: Doc, child: Doc) { const dataDoc = parent[DataSym]; for (const key of Object.keys(dataDoc)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - const permission = (key === "acl-Public" && Doc.UserDoc().defaultAclPrivate) ? AclPrivate : dataDoc[key]; + const permission = (key === "acl-Public" && Doc.defaultAclPrivate) ? AclPrivate : dataDoc[key]; key.startsWith("acl") && distributeAcls(key, permission, child); } } diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index c5c6fb688..fe8100997 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -51,7 +51,7 @@ library.add(...[faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, fa @observer export class MobileInterface extends React.Component { static Instance: MobileInterface; - private _library: Promise; + private _library: Doc; private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc()); @observable private _sidebarActive: boolean = false; //to toggle sidebar display @observable private _imageUploadActive: boolean = false; //to toggle image upload @@ -68,7 +68,7 @@ export class MobileInterface extends React.Component { constructor(props: Readonly<{}>) { super(props); - this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc()); // to access documents in Dash Web + this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), "myDashboards"); // to access documents in Dash Web MobileInterface.Instance = this; } @@ -76,7 +76,7 @@ export class MobileInterface extends React.Component { componentDidMount = () => { // if the home menu is in list view -> adjust the menu toggle appropriately this._menuListView = this._homeDoc._viewType === "stacking" ? true : false; - CurrentUserUtils.SelectedTool = InkTool.None; // ink should intially be set to none + CurrentUserUtils.ActiveTool = InkTool.None; // ink should intially be set to none Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home AudioBox.Enabled = true; @@ -125,7 +125,7 @@ export class MobileInterface extends React.Component { * Method called when 'Library' button is pressed on the home screen */ switchToLibrary = async () => { - this._library.then(library => this.switchCurrentView(library)); + this.switchCurrentView(this._library); runInAction(() => this._homeMenu = false); this.toggleSidebar(); } @@ -140,7 +140,7 @@ export class MobileInterface extends React.Component { // Case 1: Parent document is 'dashboards' if (doc === Cast(this._library, Doc) as Doc) { this.dashboards = null; - this._library.then(library => this.switchCurrentView(library)); + this.switchCurrentView(this._library); // Case 2: Parent document is the 'home' menu (root node) } else if (doc === Cast(this._homeDoc, Doc) as Doc) { this._homeMenu = true; @@ -179,7 +179,7 @@ export class MobileInterface extends React.Component { @action returnMain = () => { this._parents = [this._homeDoc]; - this._library.then(library => this.switchCurrentView(library)); + this.switchCurrentView(this._library); this._homeMenu = false; this.dashboards = null; } @@ -419,10 +419,10 @@ export class MobileInterface extends React.Component { button.style.color = this._ink ? "black" : "white"; if (!this._ink) { - CurrentUserUtils.SelectedTool = InkTool.Pen; + CurrentUserUtils.ActiveTool = InkTool.Pen; this._ink = true; } else { - CurrentUserUtils.SelectedTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; this._ink = false; } } @@ -514,7 +514,7 @@ export class MobileInterface extends React.Component { return
    TabDocView.PinDoc(this._activeDoc, { unpin: isPinned })}> + onClick={e => TabDocView.PinDoc(this._activeDoc)}>
    ; } else return (null); @@ -573,7 +573,7 @@ export class MobileInterface extends React.Component { // For setting up the presentation document for the home menu @action setupDefaultPresentation = () => { - const presentation = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + const presentation = CurrentUserUtils.ActivePresentation; if (presentation) { this.switchCurrentView(presentation); -- cgit v1.2.3-70-g09d2 From c415fb65e3d87b851015fb2ac7c41361609e0719 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 16:27:39 -0400 Subject: fixed contextmenu when hitting enter to not iconify. fixed : menu to add equations properly. --- src/client/documents/Documents.ts | 18 ++---------------- src/client/util/CurrentUserUtils.ts | 9 +++++---- src/client/views/ContextMenu.tsx | 1 + src/client/views/DocumentDecorations.tsx | 7 +++++-- src/fields/Doc.ts | 2 +- 5 files changed, 14 insertions(+), 23 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f96bffb3c..e36103938 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1290,21 +1290,7 @@ export namespace DocUtils { })) as ContextMenuProps[], icon: "sticky-note" }); - const math: ContextMenuProps = ({ - description: ":Math", event: () => { - const created = Docs.Create.EquationDocument(); - if (created) { - created.author = Doc.CurrentUserEmail; - created.x = x; - created.y = y; - created.width = 300; - created.height = 35; - EquationBox.SelectOnLoad = created[Id]; - docAdder?.(created); - } - }, icon: "calculator" - }); - const documentList: ContextMenuProps[] = DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ + const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data).lastElement()?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ description: ":" + StrCast(dragDoc.title), event: undoBatch((args: { x: number, y: number }) => { const newDoc = Doc.copyDragFactory(dragDoc); @@ -1312,13 +1298,13 @@ export namespace DocUtils { newDoc.author = Doc.CurrentUserEmail; newDoc.x = x; newDoc.y = y; + EquationBox.SelectOnLoad = newDoc[Id]; if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id]; docAdder?.(newDoc); } }), icon: Doc.toIcon(dragDoc), })) as ContextMenuProps[]; - documentList.push(math); ContextMenu.Instance.addItem({ description: "Create document", subitems: documentList, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7a38886fe..1d5a9fa09 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -77,7 +77,7 @@ export class CurrentUserUtils { static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); + doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); } }); funcs && Object.keys(funcs).map(key => { @@ -248,7 +248,8 @@ export class CurrentUserUtils { }[] = [ {key: "emptyPresentation", creator: Docs.Create.PresDocument, opts: { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, {key: "emptyCollection", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "freeform", _width: 150, _height: 100 }}, - {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts : { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, + {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, + {key: "emptyMath", creator: (opts) => Docs.Create.EquationDocument(opts), opts: { title: "Equation", _backgroundGridShow: true, _width: 300, _height: 35 }}, {key: "emptySlide", creator: (opts) => Docs.Create.TreeDocument([], opts), funcs: {title: 'self.text?.Text'}, opts: { _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined, allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" @@ -265,7 +266,7 @@ export class CurrentUserUtils { ]; emptyThings.forEach(thing => - this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), thing.funcs)) + this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), undefined, thing.funcs)) if (doc.emptyHeader === undefined) { const json = { @@ -309,6 +310,7 @@ export class CurrentUserUtils { return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, clickFactory: doc.emptyPane as Doc, }, + { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyMath as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: {onClick: 'openInOverlay(copyDragFactory(this.dragFactory))',onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, @@ -1213,7 +1215,6 @@ export class CurrentUserUtils { public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; } public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); } public static set ActivePage(val) { Doc.UserDoc().activePage = val; } - public static get EmptyPane() { return DocCast(Doc.UserDoc().emptyPane); } public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; } public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } } diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 80ff16cf9..e2f98de1e 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -287,6 +287,7 @@ export class ContextMenu extends React.Component { } this.closeMenu(); e.preventDefault(); + e.stopPropagation(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b666b5977..d22fb5f01 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -7,8 +7,8 @@ import { DateField } from '../../fields/DateField'; import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from "../../fields/Doc"; import { Document } from '../../fields/documentSchemas'; import { InkField } from "../../fields/InkField"; -import { ScriptField } from '../../fields/ScriptField'; -import { Cast, NumCast, StrCast } from "../../fields/Types"; +import { ComputedField, ScriptField } from '../../fields/ScriptField'; +import { Cast, FieldValue, NumCast, StrCast } from "../../fields/Types"; import { GetEffectiveAcl } from '../../fields/util'; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { Docs } from "../documents/Documents"; @@ -461,6 +461,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || ""; } if (this._titleControlString.startsWith("#")) { + const cfield = ComputedField.WithoutComputed(() => FieldValue(selected.props.Document.title)); + console.log(cfield); + (cfield as any)?.script.run().result; return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-"; } return this._accumulatedTitle; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 74213652b..981514b25 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -233,7 +233,7 @@ export class Doc extends RefField { } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; - public static get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode); } + public static get noviceMode() { return Doc.UserDoc().noviceMode as boolean; } public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; } -- cgit v1.2.3-70-g09d2 From 3e22a44461095d95062e3b809b525a0a87342ac3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 22:18:42 -0400 Subject: more cleanup to get rid of unecessary references to Doc.UserDoc(). --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 48 +++++++++++++--------- src/client/util/DragManager.ts | 4 +- src/client/util/History.ts | 2 +- src/client/util/SettingsManager.tsx | 19 ++++----- src/client/util/SnappingManager.ts | 4 ++ src/client/views/DashboardView.tsx | 2 +- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/MainView.tsx | 21 ++++------ .../views/collections/CollectionDockingView.tsx | 4 +- src/client/views/collections/CollectionMenu.tsx | 4 +- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/button/FontIconBox.tsx | 30 +++++++------- .../nodes/button/colorDropdown/ColorDropdown.tsx | 3 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/client/views/nodes/trails/PresBox.tsx | 11 +---- src/client/views/topbar/TopBar.tsx | 2 +- src/mobile/MobileInterface.tsx | 2 +- 22 files changed, 93 insertions(+), 89 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e36103938..73b756a8d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -662,7 +662,7 @@ export namespace Docs { const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); dataProps["acl-Override"] = "None"; - dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; dataProps.system = viewProps.system; dataProps.isPrototype = true; @@ -679,7 +679,7 @@ export namespace Docs { const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); const viewFirstProps: { [id: string]: any } = {}; - viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; viewFirstProps["acl-Override"] = "None"; viewFirstProps.author = Doc.CurrentUserEmail; const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 1d5a9fa09..9b5fc5b98 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -181,7 +181,12 @@ export class CurrentUserUtils { /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc, field="myTemplates") { + const preEleOpts:DocumentOptions = { + title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" + }; + this.AssignOpts(DocCast(doc.presElement), preEleOpts) ?? (doc.presElement= Docs.Create.PresElementBoxDocument(preEleOpts)); const templates = [ + DocCast(doc.presElement), CurrentUserUtils.setupNoteTemplates(doc), CurrentUserUtils.setupClickEditorTemplates(doc) ]; @@ -323,8 +328,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, { toolTip: "Tap or drag to create a mobile view", title: "Phone", icon: "mobile", dragFactory: doc.activeMobileMenu as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: 'this.dragFactory'}, funcs: {hidden: 'IsNoviceMode()'} }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, - // { toolTip: "Tap or drag to create a presentation", title: "Trails", icon: "pres-trail", dragFactory: doc.emptyPresentation as Doc,scripts: {onClick: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', onDragStart: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`}, funcs: {hidden: 'IsNoviceMode()'} }, - ]; + ]; } /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools @@ -533,7 +537,7 @@ export class CurrentUserUtils { static setupDashboards(doc: Doc, field:string) { var myDashboards = DocCast(doc[field]); - const newDashboard = `createNewDashboard(Doc.UserDoc())`; + const newDashboard = `createNewDashboard()`; const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true }; const reqdBtnScript = {onClick: newDashboard,} @@ -830,6 +834,11 @@ export class CurrentUserUtils { const reqdOpts = { title: "overlay documents", backgroundColor: "#aca3a6", system: true }; return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.FreeformDocument([], reqdOpts)); } + + static setupPublished(doc:Doc, field = "myPublishedDocs") { + const reqdOpts = { title: "published docs", backgroundColor: "#aca3a6", system: true }; + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + } /// The database of all links on all documents static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { @@ -958,7 +967,6 @@ export class CurrentUserUtils { Doc.noviceMode ?? (Doc.noviceMode = true); doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); doc._showLabel ?? (doc._showLabel = true); - doc._showMenuLabel ?? (doc._showMenuLabel = true); doc.textAlign ?? (doc.textAlign = "left"); doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");; doc.activeInkWidth ?? (doc.activeInkWidth = 1); @@ -975,17 +983,17 @@ export class CurrentUserUtils { doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; - doc.myPublishedDocs ?? (doc.myPublishedDocs = new List()); doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents await this.setupLinkDocs(doc, linkDatabaseId); await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon - this.setupDocTemplates(doc); // sets up the template menu of templates this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard + this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box) this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left + this.setupDocTemplates(doc); // sets up the template menu of templates doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); @@ -1037,7 +1045,8 @@ export class CurrentUserUtils { public static _urlState: HistoryUtil.DocUrl; - public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => { + public static openDashboard = (doc: Doc, fromHistory = false) => { + const userDoc = Doc.UserDoc(); CurrentUserUtils.MainDocId = doc[Id]; if (!DocListCast(CurrentUserUtils.MyDashboards.data).includes(doc)) { Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); @@ -1045,7 +1054,7 @@ export class CurrentUserUtils { if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); - userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); + userDoc ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); } const state = CurrentUserUtils._urlState; if (state.sharing === true && !userDoc) { @@ -1138,11 +1147,11 @@ export class CurrentUserUtils { } - public static async snapshotDashboard(userDoc: Doc) { + public static async snapshotDashboard() { if (CurrentUserUtils.ActiveDashboard) { const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); - CurrentUserUtils.openDashboard(userDoc, copy); + CurrentUserUtils.openDashboard(copy); } } @@ -1150,9 +1159,9 @@ export class CurrentUserUtils { CurrentUserUtils.ActiveDashboard = undefined; } - public static createNewDashboard = async (userDoc: Doc, id?: string) => { - const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); - const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; + public static createNewDashboard = (id?: string) => { + const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true); + const dashboards = CurrentUserUtils.MyDashboards; const dashboardCount = DocListCast(dashboards.data).length + 1; const freeformOptions: DocumentOptions = { x: 0, @@ -1172,10 +1181,10 @@ export class CurrentUserUtils { dashboardDoc.data = new List(dashboardTabs); dashboardDoc["pane-count"] = 1; - userDoc.activePresentation = presentation; + CurrentUserUtils.ActivePresentation = presentation; Doc.AddDocToList(dashboards, "data", dashboardDoc); - // CurrentUserUtils.openDashboard(userDoc, dashboardDoc); + // CurrentUserUtils.openDashboard(dashboardDoc); } public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { @@ -1209,6 +1218,7 @@ export class CurrentUserUtils { public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); } + public static get MyPublishedDocs() { return DocCast(Doc.UserDoc().myPublishedDocs); } public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; } public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } @@ -1230,8 +1240,8 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); -ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); -ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called"); +ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(); }, "creates a snapshot copy of a dashboard"); +ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(); }, "creates a new dashboard when called"); ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); @@ -1240,7 +1250,7 @@ ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.In 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)!); + if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!); Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); } }, @@ -1248,7 +1258,7 @@ ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); - CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias); + CurrentUserUtils.openDashboard(dashboardAlias); }, "adds Dashboard to set of Dashboards"); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index dbfba8992..2b62945af 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -6,7 +6,7 @@ import { PrefetchProxy } from "../../fields/Proxy"; import { listSpec } from "../../fields/Schema"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ScriptField } from "../../fields/ScriptField"; -import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; import { emptyFunction, Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import * as globalCssVariables from "../views/global/globalCssVariables.scss"; @@ -72,6 +72,8 @@ export namespace DragManager { 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 GetRaiseWhenDragged() { return BoolCast(Doc.UserDoc()._raiseWhenDragged); } + export function SetRaiseWhenDragged(val:boolean) { Doc.UserDoc()._raiseWhenDragged = val } export function Root() { const root = document.getElementById("root"); if (!root) { diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 632348306..7dcff9c56 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -197,7 +197,7 @@ export namespace HistoryUtil { await Promise.all(Object.keys(init).map(id => initDoc(id, init[id]))); } if (field instanceof Doc) { - CurrentUserUtils.openDashboard(Doc.UserDoc(), field, true); + CurrentUserUtils.openDashboard(field, true); } } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 382274462..22e33ab1e 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -10,7 +10,9 @@ import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager import { DocServer } from "../DocServer"; import { Networking } from "../Network"; import { MainViewModal } from "../views/MainViewModal"; +import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { CurrentUserUtils } from "./CurrentUserUtils"; +import { DragManager } from "./DragManager"; import { GroupManager } from "./GroupManager"; import "./SettingsManager.scss"; import { undoBatch } from "./UndoManager"; @@ -155,19 +157,14 @@ export class SettingsManager extends React.Component<{}> {
    Show full toolbar
    - Doc.UserDoc()._raiseWhenDragged = !Doc.UserDoc()._raiseWhenDragged} - checked={BoolCast(Doc.UserDoc()._raiseWhenDragged)} /> + DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())} + checked={DragManager.GetRaiseWhenDragged()} />
    Raise on drag
    - Doc.UserDoc()._showLabel = !Doc.UserDoc()._showLabel} - checked={BoolCast(Doc.UserDoc()._showLabel)} /> -
    Show tool button labels
    -
    -
    - Doc.UserDoc()._showMenuLabel = !Doc.UserDoc()._showMenuLabel} - checked={BoolCast(Doc.UserDoc()._showMenuLabel)} /> -
    Show menu button labels
    + FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} + checked={FontIconBox.GetShowLabels()} /> +
    Show button labels
    ; } @@ -271,7 +268,7 @@ export class SettingsManager extends React.Component<{}> {
    - Doc.defaultAclPrivate = !Doc.defaultAclPrivate)} />
    Default access private
    diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 069f81d38..057843c68 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -1,5 +1,6 @@ import { observable, action, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; +import { Doc } from "../../fields/Doc"; export namespace SnappingManager { @@ -30,6 +31,9 @@ export namespace SnappingManager { export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } export function GetIsDragging() { return manager.IsDragging; } + export function SetShowSnapLines(show: boolean) { runInAction(() => Doc.UserDoc().showSnapLines = show); } + export function GetShowSnapLines() { return Doc.UserDoc().showSnapLines; } + /// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts // need to investigate further what caused the mobx update problems and move to a better location. const getCachedGroupByNameCache = computedFn(function (name: string) { return manager.cachedGroups.includes(name); }, true); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 9a0f25fe3..a126218c4 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -28,7 +28,7 @@ export class DashboardView extends React.Component { newDashboard = async () => { const batch = UndoManager.StartBatch("new dash"); - await CurrentUserUtils.createNewDashboard(Doc.UserDoc()); + await CurrentUserUtils.createNewDashboard(); batch.end(); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 18cf785b9..4247501bb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -87,10 +87,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P if (titleFieldKey === "title") { d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"); if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) { - Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc); + Doc.RemoveDocFromList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); } if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) { - Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc); + Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); } } //@ts-ignore diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 61cca0421..6ee8065f5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -225,26 +225,21 @@ export class MainView extends React.Component { // Load the user's active dashboard, or create a new one if initial session after signup const received = CurrentUserUtils.MainDocId; if (received && !this.userDoc) { - reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true }); + reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(), { fireImmediately: true }); } else { PromiseValue(this.userDoc.activeDashboard).then(dash => { - if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash); - else CurrentUserUtils.createNewDashboard(this.userDoc); + if (dash instanceof Doc) CurrentUserUtils.openDashboard(dash); + else CurrentUserUtils.createNewDashboard(); }); } } @action createNewPresentation = async () => { - if (!await this.userDoc.myTrails) { - this.userDoc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true - })); - } const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); CollectionDockingView.AddSplit(pres, "left"); - this.userDoc.activePresentation = pres; - Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres); + CurrentUserUtils.ActivePresentation = pres; + Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", pres); } @action @@ -353,9 +348,9 @@ export class MainView extends React.Component { addDocTabFunc = (doc: Doc, location: string): boolean => { const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); const locationParams = locationFields.length > 1 ? locationFields[1] : ""; - if (doc.dockingConfig) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + if (doc.dockingConfig) return CurrentUserUtils.openDashboard(doc); switch (locationFields[0]) { - case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + case "dashboard": return CurrentUserUtils.openDashboard(doc); case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "lightbox": return LightboxView.AddDocTab(doc, location); @@ -563,7 +558,7 @@ export class MainView extends React.Component {
    ; } @computed get snapLines() { - return !this.userDoc.showSnapLines ? (null) :
    + return !SnappingManager.GetShowSnapLines() ? (null) :
    {SnappingManager.horizSnapLines().map(l => )} {SnappingManager.vertSnapLines().map(l => )} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 5f36a7a51..19355b8e1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -121,7 +121,7 @@ export class CollectionDockingView extends CollectionSubView() { SelectionManager.DeselectAll(); const instance = CollectionDockingView.Instance; if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") { - return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + return CurrentUserUtils.openDashboard(doc); } const newItemStackConfig = { type: 'stack', @@ -172,7 +172,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { - if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document); + if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(document); const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); if (tab) { diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 9b1bb5b97..668d82387 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -164,7 +164,7 @@ export class CollectionMenu extends AntimodeMenu{ // // ; - // OLD BUTTONS + // //OLD BUTTONS // return this.getElement(!this.SelectedCollection ? [/*button*/] : // [ { - const target = this.document !== Doc.UserDoc().sidebar ? this.document : this.document.proto as Doc; + const target = this.document !== CurrentUserUtils.MyLeftSidebarPanel ? this.document : this.document.proto as Doc; //@ts-ignore target._viewType = e.target.selectedOptions[0].value; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 4b5c5e3fb..63616263e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -205,7 +205,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent CurrentUserUtils.createNewDashboard(Doc.UserDoc()), icon: "project-diagram" }); + optionItems.push({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(), icon: "project-diagram" }); } !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 70db121d1..62d07b0e4 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -329,7 +329,7 @@ export class TabDocView extends React.Component { const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); const locationParams = locationFields.length > 1 ? locationFields[1] : ""; switch (locationFields[0]) { - case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + case "dashboard": return CurrentUserUtils.openDashboard(doc); case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ffe146ae4..99eec7892 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -253,7 +253,7 @@ export class CollectionFreeFormView extends CollectionSubView Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? "Hide" : "Show") + " Snap Lines", event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: "compress-arrows-alt" }) : null; !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b62020a04..081a1a924 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -6,7 +6,7 @@ import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { Cast, DocCast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; import { ImageField } from "../../../../fields/URLField"; import { GetEffectiveAcl } from "../../../../fields/util"; import { intersectRect, returnFalse, Utils } from "../../../../Utils"; @@ -156,7 +156,7 @@ export class MarqueeView extends React.Component StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name); - (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); + (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (this._timeout) { clearTimeout(this._timeout); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 3af6a3d51..1b37e729b 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -79,6 +79,9 @@ export class FontIconBox extends DocComponent() { } } + static GetShowLabels() { return BoolCast(Doc.UserDoc()._showLabel); } + static SetShowLabels(show:boolean) { Doc.UserDoc()._showLabel = show; } + // Determining UI Specs @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); @observable private icon = StrCast(this.dataDoc.icon, "user") as any; @@ -111,7 +114,7 @@ export class FontIconBox extends DocComponent() { // Script for checking the outcome of the toggle const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0; - const label = !Doc.UserDoc()._showLabel ? (null) : + const label = !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -212,7 +215,7 @@ export class FontIconBox extends DocComponent() { style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}> - {!this.label || !Doc.UserDoc()._showLabel ? (null) :
    {this.label}
    } + {!this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    }
    @@ -283,7 +286,7 @@ export class FontIconBox extends DocComponent() {
    ; }); - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -337,7 +340,7 @@ export class FontIconBox extends DocComponent() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent"; - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -349,7 +352,7 @@ export class FontIconBox extends DocComponent() {
    ; setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return ( -
    this.colorPickerClosed = !this.colorPickerClosed)} onPointerDown={e => e.stopPropagation()}> @@ -381,7 +384,7 @@ export class FontIconBox extends DocComponent() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); // Button label - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -400,7 +403,7 @@ export class FontIconBox extends DocComponent() { ); } else { return ( -
    {label} @@ -423,7 +426,7 @@ export class FontIconBox extends DocComponent() { style={{ backgroundColor: "transparent", borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
    - {!this.label || !Doc.UserDoc()._showLabel ? (null) : + {!this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    }
    @@ -450,12 +453,12 @@ export class FontIconBox extends DocComponent() { render() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; - const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : + const menuLabel = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -497,7 +500,7 @@ export class FontIconBox extends DocComponent() { break; case ButtonType.ToolButton: button = ( -
    +
    {label}
    @@ -509,7 +512,7 @@ export class FontIconBox extends DocComponent() { break; case ButtonType.ClickButton: button = ( -
    +
    {label}
    @@ -675,10 +678,9 @@ ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: b ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return (editorView ? RichTextMenu.Instance.noAutoLink : Doc.UserDoc().noAutoLink) ? Colors.MEDIUM_BLUE : "transparent"; + return (editorView ? RichTextMenu.Instance.noAutoLink : false) ? Colors.MEDIUM_BLUE : "transparent"; } if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor(); - else Doc.UserDoc().noAutoLink = Doc.UserDoc().noAutoLink ? true : false; }); ScriptingGlobals.add(function toggleBold(checkResult?: boolean) { diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx index 235495250..7f414ddbb 100644 --- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx +++ b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx @@ -5,6 +5,7 @@ import { IButtonProps } from '../ButtonInterface'; import { ColorState, SketchPicker } from 'react-color'; import { ScriptField } from '../../../../../fields/ScriptField'; import { Doc } from '../../../../../fields/Doc'; +import { FontIconBox } from '../FontIconBox'; export class ColorDropdown extends Component { render() { @@ -31,7 +32,7 @@ export class ColorDropdown extends Component { disableAlpha={!stroke} onChange={func} color={boolResult ? boolResult : "#FFFFFF"} presetColors={colorOptions} />; - const label = !this.props.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.props.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.props.label}
    ; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 90199618b..600730d87 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -355,7 +355,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp var tr = this._editorView.state.tr as any; const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; tr = tr.removeMark(0, tr.doc.content.size, autoAnch); - DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); + DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); @@ -376,7 +376,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!(cfield instanceof ComputedField)) { this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); if (str.startsWith("@") && str.length > 1) { - Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc); + Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, this.rootDoc); } } } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0d2cffc2c..9f858539f 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -10,7 +10,7 @@ import { InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { PrefetchProxy } from "../../../../fields/Proxy"; import { listSpec } from "../../../../fields/Schema"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; import { Docs } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; @@ -131,17 +131,10 @@ export class PresBox extends ViewBoxBaseComponent() { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; else return false; } - @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); if (CurrentUserUtils.ActivePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); - if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. - Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" - })); - } this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox - } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -728,7 +721,7 @@ export class PresBox extends ViewBoxBaseComponent() { }); return true; } - childLayoutTemplate = () => !this.isTreeOrStack ? undefined : this.presElement; + childLayoutTemplate = () => !this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement); removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 1de0c0b10..6a4deca38 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -52,7 +52,7 @@ export class TopBar extends React.Component { ContextMenu.Instance.addItem({ description: "Open Dashboard View", event: this.navigateToHome, icon: "edit" }); ContextMenu.Instance.addItem({ description: "Snapshot Dashboard", event: async () => { const batch = UndoManager.StartBatch("snapshot"); - await CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); + await CurrentUserUtils.snapshotDashboard(); batch.end(); }, icon: "edit" }); dashView?.showContextMenu(e.clientX+20, e.clientY+30); diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index fe8100997..bf06faeb9 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -391,7 +391,7 @@ export class MobileInterface extends React.Component { * Handles the 'Create New Dashboard' button in the menu (taken from MainView.tsx) */ @action - createNewDashboard = async (id?: string) => { + createNewDashboard = (id?: string) => { const scens = CurrentUserUtils.MyDashboards; const dashboardCount = DocListCast(scens.data).length + 1; const freeformOptions: DocumentOptions = { -- cgit v1.2.3-70-g09d2 From 0dd3bbfc119d48bd2c32f8e55b7051e3ddc530c3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 21 Jun 2022 14:40:04 -0400 Subject: restored experimental templates. fixed (enabled) pointer events on nested column/row views when container is active. got rid of freezeDimensions which wasn't needed and introduced artifacts for views that set it (timeView, GridView) --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 74 +++++++++++----------- src/client/util/DragManager.ts | 11 ++-- src/client/views/PropertiesView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 1 - .../views/collections/CollectionTimeView.tsx | 5 +- src/client/views/collections/CollectionView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - .../collectionGrid/CollectionGridView.tsx | 1 - .../CollectionMulticolumnView.tsx | 14 ++-- .../CollectionMultirowView.tsx | 6 +- .../collectionSchema/CollectionSchemaView.tsx | 1 - .../collections/collectionSchema/SchemaTable.tsx | 1 - src/client/views/nodes/DocumentContentsView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 7 +- src/client/views/nodes/button/FontIconBox.tsx | 4 +- 16 files changed, 61 insertions(+), 69 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 73b756a8d..59391d150 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -194,6 +194,7 @@ export class DocumentOptions { childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view) childLayoutString?: string; // template string for collection to use to render its children + childDocumentsActive?: boolean; // whether child documents are active when parent is document active childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children childContextMenuFilters?: List; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9b5fc5b98..e80fe6776 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,6 +1,6 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync, StrListCast } from "../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Opt, StrListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -109,40 +109,43 @@ export class CurrentUserUtils { } // initializes experimental advanced template views - slideView, headerView - static setupExperimentalTemplateButtons(doc: Doc, field="template-experimental-buttons") { - const tempDocs = DocCast(doc[field]); - const requiredTypeNameFields:{opts:DocumentOptions, template:() => Doc}[] = [ + static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) { + const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [ { - opts:{type: "slide", icon: "address-card"}, template: () => Docs.Create.MultirowDocument( + btnOpts: { title: "slide", icon: "address-card" }, + templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true }, + template: (opts:DocumentOptions) => Docs.Create.MultirowDocument( [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) - ], - { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } - ) + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _fitWidth:true, _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) + ], opts) }, { - opts:{type: "mobile", icon: "mobile"}, template: () => this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, - [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) - ] + btnOpts: { title: "mobile", icon: "mobile" }, + templateOpts: { title: "NEW MOBILE BUTTON", onClick: undefined, }, + template: (opts:DocumentOptions) => this.mobileButton(opts, + [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) + ] ) }, ]; - const requiredTypes = requiredTypeNameFields.map(({ opts, template }) => { - const docType = DocListCast(tempDocs?.data)?.find(doc => doc.title === opts.type); - const reqdOpts = { - dragFactory: template(), - title: opts.type, - icon: opts.icon + const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => { + const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title); + const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' }; + const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { + if (templateBtn) { + this.AssignOpts(templateBtn,btnOpts); + this.AssignOpts(DocCast(templateBtn.dragFactory), templateOptions) ?? (templateBtn.dragFactory = template(templateOpts)); + } + return templateBtn; }; - const reqdScripts = {onDragStart: 'copyDragFactory(this.dragFactory)'}; const makeTemp = (doc:Doc) => { doc.isTemplateDoc = makeTemplate(doc); return doc; } - return this.AssignScripts(!docType ? makeTemp(CurrentUserUtils.createToolButton(reqdOpts)) : this.AssignOpts(docType, reqdOpts)!, reqdScripts)!; + return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? CurrentUserUtils.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); }); const reqdOpts = { @@ -150,12 +153,9 @@ export class CurrentUserUtils { _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, }; - const reqdScripts = {dropConverter : "convertToButtons(dragData)"}; - const reqdFuncs = {hidden: "IsNoviceMode()"} - return this.AssignScripts(!tempDocs ? - (doc[field] = Docs.Create.MasonryDocument(requiredTypes, reqdOpts)) : - this.AssignOpts(tempDocs, reqdOpts, requiredTypes)!, - reqdScripts, reqdFuncs); + const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; + const reqdFuncs = { hidden: "IsNoviceMode()" }; + return this.AssignScripts(this.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs); } /// Initializes templates that can be applied to notes @@ -525,12 +525,12 @@ export class CurrentUserUtils { static setupToolsBtnPanel(doc: Doc, field:string) { const myTools = DocCast(doc[field]); const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined); - //const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); + const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined); const reqdToolOps:DocumentOptions = { title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, }; - this.AssignOpts(myTools, reqdToolOps, [creatorBtns, /*templateBtns*/]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, /*templateBtns*/], reqdToolOps)); + this.AssignOpts(myTools, reqdToolOps, [creatorBtns, templateBtns]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, templateBtns], reqdToolOps)); } /// initializes the left sidebar dashboard pane @@ -749,7 +749,7 @@ export class CurrentUserUtils { } static schemaTools():Button[] { - return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{onClick:'toggleSchemaPreview(_readOnly_)'}, }]; + return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{return toggleSchemaPreview(_readOnly_);}'}, }]; } static webTools() { @@ -774,9 +774,9 @@ export class CurrentUserUtils { }, // Always show { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton,icon: "chevron-left", scripts:{onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'} }, { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, icon: "chevron-right", scripts:{onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, - { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: { script: "setBackgroundColor(value, _readOnly_)"}, funcs:{ hidden: 'selectedDocumentType()' }}, // Only when a document is selected - { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", scripts: {script: "setHeaderColor(value, _readOnly_)"}, funcs : {hidden: 'selectedDocumentType()'} }, - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", scripts: {onClick : 'toggleOverlay(_readOnly_)'}, funcs: {hidden: 'selectedDocumentType(undefined, "freeform", true)'} }, // Only when floating document is selected in freeform + { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: { script: "setBackgroundColor(value, _readOnly_)"}, funcs:{ hidden: '!selectedDocumentType()' }}, // Only when a document is selected + { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", scripts: {script: "setHeaderColor(value, _readOnly_)"}, funcs : {hidden: '!selectedDocumentType()'} }, + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", scripts: {onClick : 'toggleOverlay(_readOnly_)'}, funcs: {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", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available @@ -983,7 +983,6 @@ export class CurrentUserUtils { doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; - doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents await this.setupLinkDocs(doc, linkDatabaseId); await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon @@ -995,7 +994,8 @@ export class CurrentUserUtils { this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); - + doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents + setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 2b62945af..09b463c2f 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -223,10 +223,13 @@ export namespace DragManager { if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; docDragData.droppedDocuments = - await Promise.all(dragData.draggedDocuments.map(async d => !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : - docDragData.dropAction === "alias" ? Doc.MakeAlias(d) : - docDragData.dropAction === "proto" ? Doc.GetProto(d) : - docDragData.dropAction === "copy" ? (await Doc.MakeClone(d)).clone : d)); + await Promise.all(dragData.draggedDocuments.map(async d => + !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? + addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : + docDragData.dropAction === "alias" ? Doc.MakeAlias(d) : + docDragData.dropAction === "proto" ? Doc.GetProto(d) : + docDragData.dropAction === "copy" ? + (await Doc.MakeClone(d)).clone : d)); !["same", "proto"].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []); const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 90c86fa18..faab2ed26 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -304,7 +304,6 @@ export class PropertiesView extends React.Component { rootSelected={returnFalse} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - freezeDimensions={true} dontCenter={"y"} isDocumentActive={returnFalse} isContentActive={emptyFunction} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 277fcd59c..684c919bd 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -250,7 +250,6 @@ export class CollectionStackingView extends CollectionSubView { - this._childClickedScript = ScriptField.MakeScript("openInLightbox(self, shiftKey)", { this: Doc.name, shiftKey: "boolean" });//, { detailView: detailView! }); + this._childClickedScript = ScriptField.MakeScript("openInLightbox(self)", { this: Doc.name }); this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }); }); } @@ -138,8 +138,7 @@ export class CollectionTimeView extends CollectionSubView() { fitContentsToBox={returnTrue} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} - childFreezeDimensions={true} - dontScaleFilter={this.dontScaleFilter} + //dontScaleFilter={this.dontScaleFilter} layoutEngine={this.layoutEngine} />
    ; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 63616263e..b432104a1 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -75,7 +75,6 @@ export interface CollectionViewProps extends FieldViewProps { childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => (Doc | undefined);// specify a layout Doc template to use for children of the collection childLayoutString?: string; - childFreezeDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height childIgnoreNativeSize?: boolean; childClickScript?: ScriptField; childDoubleClickScript?: ScriptField; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 99eec7892..542b1fce1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1265,7 +1265,6 @@ export class CollectionFreeFormView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return { DataDoc={this._showDataDoc} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - freezeDimensions={true} focus={DocUtils.DefaultFocus} renderDepth={this.props.renderDepth} rootSelected={returnFalse} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 70732e74c..db076c069 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -143,7 +143,6 @@ export class DocumentContentsView extends React.Component, onInput: Opt): JsxBindings { const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews - "freezeDimensions", "hideResizeHandles", "hideTitle", "treeViewDoc", diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 89096c948..263e1dc4c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -160,7 +160,6 @@ export interface DocumentViewSharedProps { // these props are specific to DocuentViews export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView - freezeDimensions?: boolean; hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings @@ -1220,11 +1219,11 @@ export class DocumentView extends React.Component { @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); } @computed get nativeWidth() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : - returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, !this.fitWidth)); } @computed get nativeHeight() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : - returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth)); } @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || @@ -1312,8 +1311,6 @@ export class DocumentView extends React.Component { ContentScale = () => this.nativeScaling; selfView = () => this; screenToLocalTransform = () => { - const oshift = this.fitWidth && this.ComponentView instanceof FormattedTextBox; - const shift = oshift ? -(this.props.PanelHeight() - this.rootDoc[HeightSym]()) / 2 : 0; return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).translate(0, shift).scale(1 / this.nativeScaling); } componentDidMount() { diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 1b37e729b..85efc67a5 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -903,9 +903,9 @@ ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { } else if (selected) { if (NumCast(selected.schemaPreviewWidth) > 0) { - selected.schemaPreviewWidth = 200; - } else { selected.schemaPreviewWidth = 0; + } else { + selected.schemaPreviewWidth = 200; } } }); -- cgit v1.2.3-70-g09d2 From 566b33847cff9217ed6e4e11bed2e1b92798bc95 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 21 Jun 2022 18:16:46 -0400 Subject: trying to get basic data viz view to work --- package-lock.json | 871 ++++++++++++------------ src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 9 + src/client/util/CurrentUserUtils.ts | 5 +- src/client/views/nodes/DataViz.scss | 0 src/client/views/nodes/DataViz.tsx | 14 + src/client/views/nodes/DocumentContentsView.tsx | 3 +- 7 files changed, 464 insertions(+), 439 deletions(-) create mode 100644 src/client/views/nodes/DataViz.scss create mode 100644 src/client/views/nodes/DataViz.tsx (limited to 'src/client/util') diff --git a/package-lock.json b/package-lock.json index 9dc7f1b04..e80f370d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -895,7 +895,7 @@ "@types/bcrypt-nodejs": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/bcrypt-nodejs/-/bcrypt-nodejs-0.0.30.tgz", - "integrity": "sha512-gSWCu7EOXhcM0FYM8tanV0deaX1VM07sgBS8dSUmgnAQ5/3Xn6k+mWF+ZfE+c/OCW14IVnNdTOKcHgvL78oDFQ==", + "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=", "dev": true }, "@types/bezier-js": { @@ -1991,7 +1991,7 @@ "@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", "dev": true }, "@types/strip-json-comments": { @@ -2027,7 +2027,7 @@ "@types/typescript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/typescript/-/typescript-2.0.0.tgz", - "integrity": "sha512-WMEWfMISiJ2QKyk5/dSdgL0ZwP//PZj0jmDU0hMh51FmLq4WIYzjlngsUQZXejQL+QtkXJUOGjb3G3UCvgZuSQ==", + "integrity": "sha1-xDNTnJi64oaCswfqp6D9IRW4PCg=", "dev": true, "requires": { "typescript": "*" @@ -2381,12 +2381,16 @@ "Base64": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", - "integrity": "sha512-reGEWshDmTDQDsCec/HduOO9Wyj6yMOupMfhIf3ugN1TDlK2NQW4DDJSqNNtp380SNcvRfXtO8HSCQot0d0SMw==" + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=" }, "D": { - "version": "1.0.0", + "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==" + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } }, "abab": { "version": "2.0.6", @@ -2424,7 +2428,7 @@ "acorn-globals": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", - "integrity": "sha512-uWttZCk96+7itPxK8xCzY86PnxKTMrReKDqrHzv42VQY0K30PUO8WY13WMOuI+cOdX4EIdzdvQ8k6jkuGRFMYw==", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", "requires": { "acorn": "^4.0.4" }, @@ -2432,7 +2436,7 @@ "acorn": { "version": "4.0.13", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug==" + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" } } }, @@ -2461,7 +2465,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "agent-base": { "version": "6.0.2", @@ -2536,7 +2540,7 @@ "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2551,7 +2555,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -2561,12 +2565,12 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==" + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { "string-width": "^2.0.0" } @@ -2613,7 +2617,7 @@ "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, "anymatch": { "version": "2.0.0", @@ -2627,7 +2631,7 @@ "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "requires": { "remove-trailing-separator": "^1.0.1" } @@ -2712,7 +2716,7 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-flatten": { "version": "1.1.0", @@ -2722,7 +2726,7 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-back": { "version": "2.0.0", @@ -2813,23 +2817,23 @@ "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==" + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { "array-uniq": "^1.0.1" @@ -2838,13 +2842,13 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==" + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "array.prototype.reduce": { "version": "1.0.4", @@ -2871,7 +2875,7 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { "version": "0.2.6", @@ -2884,7 +2888,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -2894,7 +2898,7 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==" + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "async": { "version": "2.6.4", @@ -2912,7 +2916,7 @@ "async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==" + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" }, "async-limiter": { "version": "1.0.1", @@ -2923,7 +2927,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -2973,7 +2977,7 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.11.0", @@ -2991,12 +2995,12 @@ "babel": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel/-/babel-6.23.0.tgz", - "integrity": "sha512-ZDcCaI8Vlct8PJ3DvmyqUz+5X2Ylz3ZuuItBe/74yXosk2dwyVo/aN7MCJ8HJzhnnJ+6yP4o+lDgG9MBe91DLA==" + "integrity": "sha1-0NHn2APpdHZb7qMjLU4VPA77kPQ=" }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { "chalk": "^1.1.3", @@ -3007,19 +3011,19 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { "ansi-styles": "^2.2.1", @@ -3032,7 +3036,7 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, "strip-ansi": { @@ -3094,12 +3098,12 @@ "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -3120,7 +3124,7 @@ "babel-types": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "requires": { "babel-runtime": "^6.26.0", "esutils": "^2.0.2", @@ -3143,7 +3147,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "balanced-match": { "version": "1.0.2", @@ -3167,7 +3171,7 @@ "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { "is-descriptor": "^1.0.0" } @@ -3203,12 +3207,12 @@ "base16": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" + "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" }, "base64-js": { "version": "1.5.1", @@ -3228,18 +3232,18 @@ "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, "bcrypt-nodejs": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", - "integrity": "sha512-NmTbLm867btBHCBZ222FQXkQKzecB0KG6pTXFa6NeTVZaSnLfCsx7EK2PL3J+kX8xJThUquEBbhimRCKKZX9zA==" + "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { "tweetnacl": "^0.14.3" } @@ -3247,7 +3251,7 @@ "bezier-curve": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz", - "integrity": "sha512-h6uZJ6qdFfswS1rIRericgouhTeiVi/MnH10OKtCu2IZzXa+ZcjaxRLHY4u/evRsJcxYbbiNkgWQj2Z4UIcpEQ==" + "integrity": "sha1-o9+v6rEqlMRicw1QeYxSqEBdc3k=" }, "bezier-js": { "version": "4.1.1", @@ -3296,7 +3300,7 @@ "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "requires": { "inherits": "~2.0.0" } @@ -3364,7 +3368,7 @@ "bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "dev": true, "requires": { "array-flatten": "^2.1.0", @@ -3386,7 +3390,7 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "bootstrap": { "version": "4.6.1", @@ -3415,7 +3419,7 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==" + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" } } }, @@ -3448,7 +3452,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -3458,7 +3462,7 @@ "browser-assert": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", - "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==" + "integrity": "sha1-mqpaKox0aFwq4Fv+Ru/WBvBowgA=" }, "browser-process-hrtime": { "version": "1.0.0", @@ -3503,12 +3507,12 @@ "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-from": { "version": "1.1.2", @@ -3524,12 +3528,12 @@ "buffer-shims": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==" + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" }, "built-in-math-eval": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/built-in-math-eval/-/built-in-math-eval-0.3.0.tgz", - "integrity": "sha512-5XD5cujru60ooKJ4sGZqoH5v2Xvgw7ezV54gJX/OnPkgDKoH3BnlMEi8xW6hl8xaEjxKHebgrsawroeZnGwIMA==", + "integrity": "sha1-JA3CHLOJQ5WIxhxGDrAHZJfvxBw=", "requires": { "math-codegen": "^0.3.5" } @@ -3660,7 +3664,7 @@ "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "requires": { "callsites": "^2.0.0" }, @@ -3668,14 +3672,14 @@ "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==" + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" } } }, "caller-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "requires": { "caller-callsite": "^2.0.0" } @@ -3702,7 +3706,7 @@ "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" @@ -3711,14 +3715,14 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==" + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" } } }, "camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==" + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, "caniuse-lite": { "version": "1.0.30001351", @@ -3743,12 +3747,12 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "requires": { "align-text": "^0.1.3", "lazy-cache": "^1.0.3" @@ -3786,12 +3790,12 @@ "change-emitter": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", - "integrity": "sha512-YXzt1cQ4a2jqazhcuSWEOc1K2q8g9H6eWNsyZgi640LDzRWVQ2eDe+Y/kVdftH+vYdPF2rgDb3dLdpxE1jvAxw==" + "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" }, "character-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", - "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", "requires": { "is-regex": "^1.0.3" } @@ -3799,12 +3803,12 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==" + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "child_process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", - "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==" + "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o=" }, "chokidar": { "version": "2.1.8", @@ -3833,7 +3837,7 @@ "chrome": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chrome/-/chrome-0.1.0.tgz", - "integrity": "sha512-6KYl20U4Taj6YipylsWr2etUvp9AElJKfGNSBmyGTymYmancnOb041ZNadolEZi2nboLXH7jMSqUmm4kpuTzfg==", + "integrity": "sha1-9h2beS/v6MGUxwVt3BAscmqGQyk=", "requires": { "exeq": "^2.2.0", "plist": "^1.1.0" @@ -3853,7 +3857,7 @@ "clamp": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", - "integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==" + "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=" }, "class-transformer": { "version": "0.2.3", @@ -3874,7 +3878,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -3904,12 +3908,12 @@ "cli-boxes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==" + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, "clipboard": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", - "integrity": "sha512-smkaRaIQsrnKN1F3wd1/vY9Q+DeR4L8ZCXKeHCFC2j8RZuSBbuImcLdnIO4GTxmzJxQuDGNKkyfpGoPW7Ua5bQ==", + "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -3934,7 +3938,7 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==" + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, "yargs-parser": { "version": "7.0.0", @@ -3984,7 +3988,7 @@ "clj-fuzzy": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/clj-fuzzy/-/clj-fuzzy-0.3.3.tgz", - "integrity": "sha512-9cyh9A8+OphDZeKIG21MgyDHWDkWxTvagwvFLVjtdi6eToFENF7iDLlKwhHrnBQRSQwprKNhazG053nE/UgwfQ==" + "integrity": "sha1-seU0MJHFIC28UlMoY+HEp4RX8D0=" }, "clone-deep": { "version": "4.0.1", @@ -3999,7 +4003,7 @@ "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "requires": { "mimic-response": "^1.0.0" }, @@ -4019,12 +4023,12 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -4050,7 +4054,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.9.1", @@ -4108,13 +4112,13 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" }, "component-emitter": { "version": "1.3.0", @@ -4124,7 +4128,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "compress-brotli": { "version": "1.3.8", @@ -4189,7 +4193,7 @@ "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "dev": true }, "debug": { @@ -4204,7 +4208,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } @@ -4212,7 +4216,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -4272,7 +4276,7 @@ "connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", - "integrity": "sha512-2rcfELQt/ZMP+SM/pG8PyhJRaLKp+6Hk2IUBNkEit09X+vwn3QsAL3ZbYtxUn7NVPzbMTSLRDhqe0B/eh30RYA==" + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" }, "connect-history-api-fallback": { "version": "1.6.0", @@ -4301,12 +4305,12 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==" + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "readable-stream": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", - "integrity": "sha512-a6ibcfWFhgihuTw/chl+u3fB5ykBZFmnvpyZHebY0MCQE4vvYcsCLpCeaQ1BkH7HdJYavNSqF0WDLeo4IPHQaQ==", + "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", "requires": { "buffer-shims": "~1.0.0", "core-util-is": "~1.0.0", @@ -4330,7 +4334,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "constantinople": { "version": "3.1.2", @@ -4414,7 +4418,7 @@ "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookies": { "version": "0.8.0", @@ -4459,7 +4463,7 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==" + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-webpack-plugin": { "version": "4.6.0", @@ -4489,7 +4493,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "serialize-javascript": { @@ -4503,7 +4507,7 @@ "core-js": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==" + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" }, "core-js-pure": { "version": "3.22.8", @@ -4567,7 +4571,7 @@ "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "requires": { "capture-stack-trace": "^1.0.0" } @@ -4632,7 +4636,7 @@ "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha512-eZ+m1WNhSZutOa/uRblAc9Ut5MQfukFrFMtPSm3bZCA888NmMd5AWXWdgRZ80zd+pTk1P2JrGjg9pUPTvl2PWQ==", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "requires": { "lru-cache": "^4.0.1", "which": "^1.2.9" @@ -4662,7 +4666,7 @@ "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==" + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, "css-box-model": { "version": "1.2.1", @@ -4675,7 +4679,7 @@ "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" }, "css-in-js-utils": { "version": "2.0.1", @@ -4818,7 +4822,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "requires": { "array-find-index": "^1.0.1" } @@ -4826,19 +4830,18 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=" }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -4972,7 +4975,7 @@ "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" } @@ -5024,7 +5027,7 @@ "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==" + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" }, "debounce": { "version": "1.2.1", @@ -5042,12 +5045,12 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==" + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { "version": "4.2.1", @@ -5212,7 +5215,7 @@ "globby": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { "array-union": "^1.0.1", @@ -5225,7 +5228,7 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } } @@ -5250,7 +5253,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegate": { "version": "3.2.0", @@ -5260,7 +5263,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "denque": { "version": "1.5.1", @@ -5363,7 +5366,7 @@ "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -5398,7 +5401,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -5412,7 +5415,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==" + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "string-width": { "version": "4.2.3", @@ -5479,7 +5482,7 @@ "deps-regex": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.1.4.tgz", - "integrity": "sha512-3tzwGYogSJi8HoG93R5x9NrdefZQOXgHgGih/7eivloOq6yC6O+yoFxZnkgP661twvfILONfoKRdF9GQOGx2RA==" + "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=" }, "destroy": { "version": "1.2.0", @@ -5528,7 +5531,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -5536,7 +5539,7 @@ "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", "dev": true }, "dns-packet": { @@ -5552,7 +5555,7 @@ "dns-txt": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", "dev": true, "requires": { "buffer-indexof": "^1.0.0" @@ -5561,7 +5564,7 @@ "doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==" + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, "dom-converter": { "version": "0.2.0", @@ -5670,12 +5673,12 @@ "double-bits": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/double-bits/-/double-bits-1.1.1.tgz", - "integrity": "sha512-BCLEIBq0O/DWoA7BsCu/R+RP0ZXiowP8BhtJT3qeuuQEBpnS8LK/Wo6UTJQv6v8mK1fj8n90YziHLwGdM5whSg==" + "integrity": "sha1-WKu6RUlNpND6Nrc60RoobJGEscY=" }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==" + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "duplexify": { "version": "3.7.1", @@ -5709,7 +5712,7 @@ "dynamic-dedupe": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", "dev": true, "requires": { "xtend": "^4.0.0" @@ -5718,7 +5721,7 @@ "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -5735,7 +5738,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { "version": "1.4.149", @@ -5764,7 +5767,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "encoding": { "version": "0.1.13", @@ -5839,7 +5842,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "ws": { "version": "7.4.6", @@ -5973,7 +5976,6 @@ "version": "0.10.61", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", - "dev": true, "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -5983,8 +5985,7 @@ "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -5994,13 +5995,12 @@ "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", - "integrity": "sha512-oj4jOSXvWglTsc3wrw86iom3LDPOx1nbipQk+jaG3dy+sMRM6ReSgVr/VlmBuF6lXUrflN9DCcQHeSbAwGUl4g==" + "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" }, "es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, "requires": { "d": "^1.0.1", "ext": "^1.1.2" @@ -6014,12 +6014,12 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.14.3", @@ -6089,7 +6089,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "event-target-shim": { "version": "5.0.1", @@ -6117,7 +6117,7 @@ "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -6131,7 +6131,7 @@ "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -6162,12 +6162,12 @@ "exenv": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" }, "exeq": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/exeq/-/exeq-2.4.0.tgz", - "integrity": "sha512-B648qbDS00nQZv9UQGLT5RbZm/5dNBX10F8oWeXcgpFHSLm1249u95t/3sn2wXdQjLhlF+edAECdshFtSr1K0Q==", + "integrity": "sha1-Td8qaEZIxCeteZNJzzO9dTWPiEo=", "requires": { "bluebird": "^3.0.3", "native-or-bluebird": "^1.2.0" @@ -6176,7 +6176,7 @@ "exif": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/exif/-/exif-0.6.0.tgz", - "integrity": "sha512-gEwM4uanNMfLnDNKclZ7jPEA99E3rpy4ntoS6QW8u6murZjl1o8qRaPdMoC46Syg3d9/QaET0bYKhWlTwJCPgg==", + "integrity": "sha1-YKYmaAdlQst+T1cZnUrG830sX0o=", "requires": { "debug": "^2.2" }, @@ -6192,7 +6192,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -6204,7 +6204,7 @@ "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -6226,7 +6226,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -6234,7 +6234,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -6242,7 +6242,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -6325,7 +6325,7 @@ "express-flash": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/express-flash/-/express-flash-0.0.2.tgz", - "integrity": "sha512-QVUR0ZZRCaa8+iPHoUQaQJrQWcQuK/Q+19M7IUIdIEtvwhrA/ifHT7y1CVJI41YfGiOQnbGtn3uvd2vOdgu58A==", + "integrity": "sha1-I9GovPP5DXB5KOSJ+Whp7K0KzaI=", "requires": { "connect-flash": "0.1.x" } @@ -6382,13 +6382,12 @@ "expressjs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/expressjs/-/expressjs-1.0.1.tgz", - "integrity": "sha512-eFnQ5bMJxTZ29XwRJPV8ee/OURBBMS6Fm+b5rvMMEyz6u2IxPEh2SRzMZt9WvgnV+SMLmnzkALE1DnGG1HxJCw==" + "integrity": "sha1-IgMoRpoY31rWFeK3oM6ZXxf7ru8=" }, "ext": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dev": true, "requires": { "type": "^2.5.0" }, @@ -6396,8 +6395,7 @@ "type": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==", - "dev": true + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" } } }, @@ -6409,7 +6407,7 @@ "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -6443,7 +6441,7 @@ "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { "is-descriptor": "^1.0.0" } @@ -6451,7 +6449,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -6521,7 +6519,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "3.1.3", @@ -6536,7 +6534,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "fast-text-encoding": { @@ -6575,7 +6573,7 @@ "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "requires": { "pend": "~1.2.0" } @@ -6583,7 +6581,7 @@ "ffmpeg": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz", - "integrity": "sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==", + "integrity": "sha1-HEYN+OfaUSf2LO70v6BsWciWMMs=", "requires": { "when": ">= 0.0.1" } @@ -6612,7 +6610,7 @@ "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -6623,7 +6621,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -6633,7 +6631,7 @@ "filter-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=" }, "finalhandler": { "version": "1.2.0", @@ -6667,7 +6665,7 @@ "find": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz", - "integrity": "sha512-jPrupTOe/pO//3a9Ty2o4NqQCp0L46UG+swUnfFtdmtQVN8pEltKpAqR7Nuf6vWn0GBXx5w+R1MyZzqwjEIqdA==", + "integrity": "sha1-yGyHrxqxjyIrvjjeyGy8dg0Wpvs=", "requires": { "traverse-chain": "~0.1.0" } @@ -6675,7 +6673,7 @@ "find-cache-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha512-46TFiBOzX7xq/PcSWfFwkyjpemdRnMe31UQF+os0y+1W3k95f6R4SEt02Hj4p3X0Mir9gfrkmOtshFidS0VPUg==", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { "commondir": "^1.0.1", @@ -6800,7 +6798,7 @@ "fluent-ffmpeg": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", - "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==", + "integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=", "requires": { "async": ">=0.2.9", "which": "^1.1.1" @@ -6867,7 +6865,7 @@ "for-each-property": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/for-each-property/-/for-each-property-0.0.4.tgz", - "integrity": "sha512-xYs28PM0CKXETFzuGC6ZooH0voZlsSDZwidJcy92flQJi3PK7i3gZx23xHXCPOaD4zmet3bDo+wS7E7SujrlCw==", + "integrity": "sha1-z6hXrsFCLh0Sb/CHhPz2Jim8g/Y=", "requires": { "get-prototype-chain": "^1.0.1" } @@ -6875,7 +6873,7 @@ "for-each-property-deep": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/for-each-property-deep/-/for-each-property-deep-0.0.3.tgz", - "integrity": "sha512-qzP8QkODWVVRPpWiBZacSbBl67cTTWoBfxMG0wE46AsS1yl7qv05sGN+dHvD4s4tnvl/goe6Sp4qBI+rlVBgNg==", + "integrity": "sha1-MTCaSvw4qcygbxsiP1PWSm0IP60=", "requires": { "for-each-property": "0.0.4" } @@ -6883,12 +6881,12 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "fork-ts-checker-webpack-plugin": { "version": "1.6.0", @@ -6942,7 +6940,7 @@ "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "requires": { "map-cache": "^0.2.2" } @@ -6950,12 +6948,12 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -7022,7 +7020,7 @@ "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -7051,7 +7049,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.13", @@ -7226,7 +7224,7 @@ "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==" + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" }, "get-intrinsic": { "version": "1.1.2", @@ -7246,12 +7244,12 @@ "get-prototype-chain": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-prototype-chain/-/get-prototype-chain-1.0.1.tgz", - "integrity": "sha512-2m7WZ0jveIg/dAbCbpUxEToaJ8Dmti5EkgDP8YM3UpHUT6SAORjE2odP8XQGNVGXMHi8q8cCCoy3HTByTaTVTw==" + "integrity": "sha1-oXGhFeoeSQbG7ThDofABwYUQQW8=" }, "get-stdin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==" + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=" }, "get-stream": { "version": "6.0.1", @@ -7270,12 +7268,12 @@ "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==" + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" } @@ -7283,7 +7281,7 @@ "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" }, "glob": { "version": "7.2.3", @@ -7301,7 +7299,7 @@ "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "requires": { "is-glob": "^3.1.0", "path-dirname": "^1.0.0" @@ -7310,7 +7308,7 @@ "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "requires": { "is-extglob": "^2.1.0" } @@ -7326,7 +7324,7 @@ "global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "requires": { "ini": "^1.3.4" } @@ -7339,7 +7337,7 @@ "globby": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { "array-union": "^1.0.1", @@ -7353,7 +7351,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -7394,7 +7392,7 @@ "golden-layout": { "version": "1.5.9", "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-1.5.9.tgz", - "integrity": "sha512-iBXDQCXOTgUEQJo96zPbjDoy5bRIk9XW5l+q+pDgLnIyReqaa1aiQctNud4epsskyLt952BG521dew5Z1liSxA==", + "integrity": "sha1-o5vB9qZ+b4hreXwBbdkk6UJrp38=", "requires": { "jquery": "*" } @@ -7402,7 +7400,7 @@ "good-listener": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", "requires": { "delegate": "^3.1.2" } @@ -7569,7 +7567,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.5", @@ -7591,7 +7589,7 @@ "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { "ansi-regex": "^2.0.0" }, @@ -7599,7 +7597,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" } } }, @@ -7619,19 +7617,19 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-property-descriptors": { "version": "1.0.0", @@ -7657,12 +7655,12 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -7672,7 +7670,7 @@ "has-values": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -7686,7 +7684,7 @@ "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "requires": { "is-buffer": "^1.1.5" } @@ -7719,7 +7717,7 @@ "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -7748,7 +7746,7 @@ "html": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz", - "integrity": "sha512-lw/7YsdKiP3kk5PnR1INY17iJuzdAtJewxr14ozKJWbbR97znovZ0mh+WEMZ8rjc3lgTK+ID/htTjuyGKB52Kw==", + "integrity": "sha1-pUT6nqVJK/s6LMqCEKEL57WvH2E=", "requires": { "concat-stream": "^1.4.7" } @@ -7826,7 +7824,7 @@ "http-browserify": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", - "integrity": "sha512-Irf/LJXmE3cBzU1eaR4+NEX6bmVLqt1wkmDiA7kBwH7zmb0D8kBAXsDmQ88hhj/qv9iEZKlyGx/hrMcFi8sOHw==", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", "requires": { "Base64": "~0.2.0", "inherits": "~2.0.1" @@ -7840,7 +7838,7 @@ "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", "dev": true }, "http-errors": { @@ -7887,7 +7885,7 @@ "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -7906,12 +7904,12 @@ "https": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", - "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q=" }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "https-proxy-agent": { "version": "5.0.1", @@ -7968,7 +7966,7 @@ "icss-replace-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", "dev": true }, "icss-utils": { @@ -7988,7 +7986,7 @@ "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, "ignore": { @@ -8000,7 +7998,7 @@ "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, "iink-js": { "version": "1.5.4", @@ -8044,7 +8042,7 @@ "image-size-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", - "integrity": "sha512-N505B5FSy2Xf5l/Haef+99TwfJqTu40hnU560+rC0Cm6cxtwVz2yRFh9WpOk1YEjfv3dI0PgVYAH0hmXQmjDcw==", + "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", "readable-stream": "^1.0.33", @@ -8058,12 +8056,12 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -8081,7 +8079,7 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-fresh": { "version": "3.3.0", @@ -8095,7 +8093,7 @@ "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==" + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" }, "import-local": { "version": "3.1.0", @@ -8109,7 +8107,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "in-publish": { "version": "2.0.1", @@ -8119,7 +8117,7 @@ "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "requires": { "repeating": "^2.0.0" } @@ -8127,12 +8125,12 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -8149,7 +8147,7 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==" + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" } } }, @@ -8166,7 +8164,7 @@ "inline-style-prefixer": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz", - "integrity": "sha512-ne8XIyyqkRaNJ1JfL1NYzNdCNxq+MCBQhC8NgOQlzNm2vv3XxlP0VSLQUbSRCF6KPEoveCVEpayHoHzcMyZsMQ==", + "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=", "requires": { "bowser": "^1.7.3", "css-in-js-utils": "^2.0.0" @@ -8175,7 +8173,7 @@ "inspect-function": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz", - "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "integrity": "sha1-hdoMUli8TDMK4yg7Z0fgdZ2QpjU=", "requires": { "split-skip": "0.0.1", "unpack-string": "0.0.2" @@ -8184,7 +8182,7 @@ "split-skip": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz", - "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA=" } } }, @@ -8207,7 +8205,7 @@ "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -8258,7 +8256,7 @@ "inspect-function": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz", - "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "integrity": "sha1-hdoMUli8TDMK4yg7Z0fgdZ2QpjU=", "requires": { "split-skip": "0.0.1", "unpack-string": "0.0.2" @@ -8267,14 +8265,14 @@ "split-skip": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz", - "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA=" } } }, "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -8285,14 +8283,14 @@ "split-skip": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz", - "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg==" + "integrity": "sha1-2J2Iu9L3Pka1FYqjcKVhIk6A1GE=" } } }, "split-skip": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz", - "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA=" } } }, @@ -8363,7 +8361,7 @@ "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", "dev": true }, "ipaddr.js": { @@ -8380,7 +8378,7 @@ "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" }, @@ -8393,7 +8391,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -8412,7 +8410,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-bigint": { "version": "1.0.4", @@ -8425,7 +8423,7 @@ "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "requires": { "binary-extensions": "^1.0.0" } @@ -8468,7 +8466,7 @@ "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" }, @@ -8481,7 +8479,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -8516,12 +8514,12 @@ "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==" + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" }, "is-expression": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", - "integrity": "sha512-vyMeQMq+AiH5uUnoBfMTwf18tO3bM6k1QXBE9D6ueAAquEfCZe3AJPtud9g6qS0+4X8xA7ndpZiDyeb2l2qOBw==", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", "requires": { "acorn": "~4.0.2", "object-assign": "^4.0.1" @@ -8530,7 +8528,7 @@ "acorn": { "version": "4.0.13", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug==" + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" } } }, @@ -8542,7 +8540,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { "version": "1.1.0", @@ -8552,7 +8550,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-generator-function": { "version": "1.0.10", @@ -8573,12 +8571,12 @@ "is-in-browser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" }, "is-installed-globally": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha512-ERNhMg+i/XgDwPIPF3u24qpajVreaiSuvpb1Uu0jugw7KKcxGyCX8cgp8P5fwTmAuXku6beDHHECdKArjlg7tw==", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "requires": { "global-dirs": "^0.1.0", "is-path-inside": "^1.0.0" @@ -8592,12 +8590,12 @@ "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==" + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { "kind-of": "^3.0.2" }, @@ -8610,7 +8608,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -8628,7 +8626,7 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==" + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-cwd": { "version": "2.2.0", @@ -8659,7 +8657,7 @@ "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "requires": { "path-is-inside": "^1.0.1" } @@ -8680,7 +8678,7 @@ "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==" + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" }, "is-regex": { "version": "1.1.4", @@ -8715,7 +8713,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-string": { "version": "1.0.7", @@ -8748,12 +8746,12 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-weakref": { "version": "1.0.2", @@ -8776,28 +8774,28 @@ "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isomorphic-fetch": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { "node-fetch": "^1.0.1", "whatwg-fetch": ">=0.10.0" @@ -8806,7 +8804,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "its-set": { "version": "1.2.3", @@ -8863,7 +8861,7 @@ "js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==" + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" }, "js-tokens": { "version": "4.0.0", @@ -8882,7 +8880,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { "version": "15.2.1", @@ -9002,7 +9000,7 @@ "json-css": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz", - "integrity": "sha512-B/0T0OxZH9tSb93tXV6VOYtXqrPz/Vgz2QrCT/4NXen8HGElYkYr9V+8IrSVTMj/ftxa8cG1kcu7f3iAMlaFlQ==" + "integrity": "sha1-65ZPg0ouTqobwvaY/12wB6JsfAA=" }, "json-parse-better-errors": { "version": "1.0.2", @@ -9027,7 +9025,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "1.0.1", @@ -9158,7 +9156,7 @@ "jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", - "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", "requires": { "is-promise": "^2.0.0", "promise": "^7.0.1" @@ -9256,7 +9254,7 @@ "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "requires": { "graceful-fs": "^4.1.9" } @@ -9264,7 +9262,7 @@ "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha512-Be1YRHWWlZaSsrz2U+VInk+tO0EwLIyV+23RhWLINJYwg/UIikxjlj3MhH37/6/EDCAusjajvMkMMUXRaMWl/w==", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "requires": { "package-json": "^4.0.0" } @@ -9272,7 +9270,7 @@ "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" }, "lazystream": { "version": "1.0.1", @@ -9301,7 +9299,7 @@ "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { "prelude-ls": "~1.1.2", @@ -9324,7 +9322,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -9336,7 +9334,7 @@ "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "requires": { "error-ex": "^1.2.0" } @@ -9381,22 +9379,22 @@ "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" }, "lodash.chunk": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", - "integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==" + "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=" }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==" + "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" }, "lodash.debounce": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-3.1.1.tgz", - "integrity": "sha512-lcmJwMpdPAtChA4hfiwxTtgFeNAaow701wWUgVUqeD0XJF7vMXIN+bu/2FJSGxT0NUbZy9g9VFrlOFfPjl+0Ew==", + "integrity": "sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=", "requires": { "lodash._getnative": "^3.0.0" } @@ -9404,42 +9402,42 @@ "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, "lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, "lodash.flow": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" + "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, "lodash.merge": { "version": "4.6.2", @@ -9449,23 +9447,23 @@ "lodash.padend": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==" + "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "log-symbols": { "version": "2.2.0", @@ -9493,7 +9491,7 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==" + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, "loose-envify": { "version": "1.4.0", @@ -9506,7 +9504,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -9555,7 +9553,7 @@ "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "requires": { "locate-path": "^2.0.0" } @@ -9563,7 +9561,7 @@ "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -9580,7 +9578,7 @@ "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "requires": { "p-limit": "^1.1.0" } @@ -9588,7 +9586,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==" + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" } } }, @@ -9616,17 +9614,17 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==" + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==" + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "requires": { "object-visit": "^1.0.0" } @@ -9671,7 +9669,7 @@ "math-codegen": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/math-codegen/-/math-codegen-0.3.5.tgz", - "integrity": "sha512-SsFYMv33FxMKYxI1PBiaZT+8AeDITK+k/PKhbHNlOPHIz5FIPF4wy78yWqanN6luXdsXENUZgCIC6xH6bfUq1g==", + "integrity": "sha1-R5nuRnfe0Ud2bQA8ykt4ee3UDMo=", "requires": { "extend": "^3.0.0", "mr-parser": "^0.2.1" @@ -9680,7 +9678,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", + "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", "requires": { "jquery": "^1.12.3" }, @@ -9688,19 +9686,19 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" + "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" } } }, "max-safe-integer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/max-safe-integer/-/max-safe-integer-1.0.1.tgz", - "integrity": "sha512-CHZ/Nopqh46UtA0YvLclxj9F95qExLmTnMS5fnYlXvX4zj1RUOC3cPaGEOMhdPE8SSudSQ6wkQUJLA5E/WeL4A==" + "integrity": "sha1-84BgvixWPYwC5tSK85Ei/YO29BA=" }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "memfs": { "version": "3.4.4", @@ -9751,12 +9749,12 @@ "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==" + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "requires": { "camelcase-keys": "^2.0.0", "decamelize": "^1.1.2", @@ -9781,7 +9779,7 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "merge-stream": { "version": "2.0.0", @@ -9791,7 +9789,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "microevent.ts": { "version": "0.1.1", @@ -10025,7 +10023,7 @@ "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, "minimatch": { @@ -10040,13 +10038,13 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -10055,7 +10053,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "supports-color": { @@ -10179,7 +10177,7 @@ "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { "aproba": "^1.1.1", @@ -10240,14 +10238,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "mr-parser": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/mr-parser/-/mr-parser-0.2.1.tgz", - "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw==" + "integrity": "sha1-hhi5ukF+KOn0OaQcaVtVTq/u2Sc=" }, "ms": { "version": "2.1.1", @@ -10267,7 +10265,7 @@ "multicast-dns-service-types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, "nan": { @@ -10306,7 +10304,7 @@ "native-or-bluebird": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz", - "integrity": "sha512-0SH8UubxDfe382eYiwmd12qxAbiWGzlGZv6CkMA+DPojWa/Y0oH4hE0lRtFfFgJmPQFyKXeB8XxPbZz6TvvKaQ==" + "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck=" }, "negotiator": { "version": "0.6.3", @@ -10327,7 +10325,7 @@ "nextafter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/nextafter/-/nextafter-1.0.0.tgz", - "integrity": "sha512-7PO+A89Tll2rSEfyrjtqO0MaI37+nnxBdnQcPypfbEYYuGaJxWGCqaOwQX4a3GHNTS08l1kazuiLEWZniZjMUQ==", + "integrity": "sha1-t9d7U1MQ4+CX5gJauwqQNHfsGjo=", "requires": { "double-bits": "^1.1.0" } @@ -10358,7 +10356,7 @@ "node-ensure": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==" + "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=" }, "node-environment-flags": { "version": "1.0.5", @@ -10447,7 +10445,7 @@ "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "requires": { "abbrev": "1" } @@ -10488,7 +10486,7 @@ "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==" + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" }, "string-width": { "version": "1.0.2", @@ -10552,12 +10550,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "aproba": { "version": "1.2.0", @@ -10576,7 +10574,7 @@ "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -10603,7 +10601,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==" + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -10701,7 +10699,7 @@ "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==" + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" }, "nopt": { "version": "5.0.0", @@ -14278,7 +14276,7 @@ "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { "path-key": "^2.0.0" } @@ -14305,7 +14303,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { "version": "2.2.0", @@ -14316,7 +14314,7 @@ "oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", - "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" }, "oauth-sign": { "version": "0.9.0", @@ -14326,12 +14324,12 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -14341,7 +14339,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -14354,7 +14352,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -14411,7 +14409,7 @@ "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -14424,7 +14422,7 @@ "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "requires": { "isobject": "^3.0.0" } @@ -14454,7 +14452,7 @@ "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "requires": { "isobject": "^3.0.1" } @@ -14481,7 +14479,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } @@ -14542,12 +14540,12 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", @@ -14566,12 +14564,12 @@ "p-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-1.0.0.tgz", - "integrity": "sha512-ttOxn4Yt0hzIsLLqKi/Ry9QRxW+UQKdoWHz7g99Ci57zPkqUU3kbWKAeHuv+HfRLe109acYLUY6kuVCOOqnt4g==" + "integrity": "sha1-y38svu/YegnrqGHhErZ1J+Yh4v0=" }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "2.3.0", @@ -14612,7 +14610,7 @@ "package-json": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "^6.7.1", "registry-auth-token": "^3.0.1", @@ -14628,7 +14626,7 @@ "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha512-Y/K3EDuiQN9rTZhBvPRWMLXIKdeD1Rj0nzunfoi0Yyn5WBEbzxXKU9Ub2X41oZBagVWOBU3MuDonFMgPWQFnwg==", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { "create-error-class": "^3.0.0", "duplexer3": "^0.1.4", @@ -14744,7 +14742,7 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==" + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "passport": { "version": "0.4.1", @@ -14766,7 +14764,7 @@ "passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", "requires": { "passport-strategy": "1.x.x" } @@ -14786,7 +14784,7 @@ "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==" + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" }, "path-browserify": { "version": "1.0.1", @@ -14796,27 +14794,27 @@ "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==" + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.7", @@ -14826,7 +14824,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { "version": "4.0.0", @@ -14841,7 +14839,7 @@ "pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, "pdf-parse": { "version": "1.1.1", @@ -14889,7 +14887,7 @@ "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, "perfect-scrollbar": { "version": "1.5.5", @@ -14899,7 +14897,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picocolors": { "version": "1.0.0", @@ -14919,12 +14917,12 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==" + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { "pinkie": "^2.0.0" } @@ -14985,7 +14983,7 @@ "plist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", - "integrity": "sha512-dL9Xc2Aj3YyBnwvCNuHmFl2LWvQacm/HEAsoVwLiuu0POboMChETt5wexpU1P6F6MnibIucXlVsMFFgNUT2IyA==", + "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=", "requires": { "base64-js": "0.0.8", "util-deprecate": "1.0.2", @@ -14996,7 +14994,7 @@ "base64-js": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==" + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=" } } }, @@ -15036,7 +15034,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==" + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { "version": "7.0.39", @@ -15234,13 +15232,13 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==" + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "pretty-error": { "version": "4.0.0", @@ -15274,7 +15272,7 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.1", @@ -15297,7 +15295,7 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, "prop-types": { @@ -15343,7 +15341,7 @@ "prosemirror-find-replace": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/prosemirror-find-replace/-/prosemirror-find-replace-0.9.0.tgz", - "integrity": "sha512-LfhQ/Zr0PkkJpCsr9vTJ5ZPYh49mSVVG+hHJ6djT+chlCW+t2ilSxBpBG+2IeE/I5nlbcvuLLAbxeI1g3pTCpA==" + "integrity": "sha1-QgsENNF5xdBJD44hSNhVGpVJY4I=" }, "prosemirror-history": { "version": "1.3.0", @@ -15435,13 +15433,13 @@ "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.8.0", @@ -15673,12 +15671,12 @@ "pure-color": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" + "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, "qs": { "version": "6.5.3", @@ -15699,7 +15697,7 @@ "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, "querystringify": { @@ -15721,7 +15719,7 @@ "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, "randombytes": { "version": "2.1.0", @@ -15826,7 +15824,7 @@ "react-base16-styling": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", - "integrity": "sha512-EPuchwVvYPSFFIjGpH0k6wM0HQsmJ0vCk7BSl5ryxMVFIWW4hX4Kksu4PNtxfgOxDebTLkJQ8iC7zwAql0eusg==", + "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", "requires": { "base16": "^1.0.0", "lodash.curry": "^4.0.1", @@ -15895,7 +15893,7 @@ "react-dock": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.2.4.tgz", - "integrity": "sha512-ywUJPC/TIM9PO700skka0fH4aqbrH8RojUXejZFvjtqlc5KZ+xjHqFdo4A3j+dp+0NLFZ3Nai4xzcf3FUJ9BsQ==", + "integrity": "sha1-5yfcdVCztzEWY13LnA4E0Lev4Xw=", "requires": { "lodash.debounce": "^3.1.1", "prop-types": "^15.5.8" @@ -16252,7 +16250,7 @@ "react-themeable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz", - "integrity": "sha512-kl5tQ8K+r9IdQXZd8WLa+xxYN04lLnJXRVhHfdgwsUJr/SlKJxIejoc9z9obEkx1mdqbTw1ry43fxEUwyD9u7w==", + "integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=", "requires": { "object-assign": "^3.0.0" }, @@ -16260,7 +16258,7 @@ "object-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==" + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" } } }, @@ -16299,7 +16297,7 @@ "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -16309,7 +16307,7 @@ "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -16321,7 +16319,7 @@ "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -16330,7 +16328,7 @@ "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -16339,7 +16337,7 @@ "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "requires": { "pinkie-promise": "^2.0.0" } @@ -16385,12 +16383,12 @@ "readline": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" + "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "requires": { "resolve": "^1.1.6" } @@ -16416,7 +16414,7 @@ "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" @@ -16425,7 +16423,7 @@ "reduce-flatten": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", - "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==" + "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=" }, "redux": { "version": "4.2.0", @@ -16476,7 +16474,7 @@ "registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { "rc": "^1.0.1" } @@ -16484,12 +16482,12 @@ "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "renderkid": { "version": "3.0.0", @@ -16575,12 +16573,12 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "requires": { "is-finite": "^1.0.0" } @@ -16684,7 +16682,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-from-string": { "version": "2.0.2", @@ -16699,7 +16697,7 @@ "require-package-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", - "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==" + "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=" }, "require_optional": { "version": "1.0.1", @@ -16713,14 +16711,14 @@ "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==" + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" } } }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, "resize-observer-polyfill": { @@ -16766,7 +16764,7 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==" + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "responselike": { "version": "2.0.0", @@ -16791,7 +16789,7 @@ "retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, "reveal.js": { @@ -16802,7 +16800,7 @@ "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "requires": { "align-text": "^0.1.1" } @@ -16831,7 +16829,7 @@ "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { "aproba": "^1.1.1" @@ -16853,7 +16851,7 @@ "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" } @@ -16941,13 +16939,13 @@ "scss-loader": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/scss-loader/-/scss-loader-0.0.1.tgz", - "integrity": "sha512-SbT/smRJjkvvdHSEdAYAplosVkrtaSwwgUlnQCOuDS5sOKNjrS/eYCMvKeV6+YxK5cCOCsOJZd3vltrXatFp+g==", + "integrity": "sha1-6uAXueDzjBKlMtslwiC5Avs05nE=", "dev": true }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha512-dYE8LhncfBUar6POCxMTm0Ln+erjeczqEvCJib5/7XNkdw1FkUGgwMPY360FY0FgPWQxHWCx29Jl3oejyGLM9Q==", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", "requires": { "js-base64": "^2.1.8", "source-map": "^0.4.2" @@ -16956,7 +16954,7 @@ "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "requires": { "amdefine": ">=0.0.4" } @@ -16971,17 +16969,17 @@ "section-iterator": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz", - "integrity": "sha512-xvTNwcbeDayXotnV32zLb3duQsP+4XosHpb/F+tu6VzEZFmIjzPdNk6/O+QOOx5XTh08KL2ufdXeCO33p380pQ==" + "integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio=" }, "select": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", "dev": true }, "selfsigned": { @@ -17001,12 +16999,12 @@ "semver-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=" }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { "semver": "^5.0.3" } @@ -17070,7 +17068,7 @@ "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { "accepts": "~1.3.4", @@ -17100,7 +17098,7 @@ "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { "depd": "~1.1.2", @@ -17112,13 +17110,13 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "setprototypeof": { @@ -17149,7 +17147,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", @@ -17165,7 +17163,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -17175,7 +17173,7 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { "version": "1.2.0", @@ -17337,7 +17335,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { "shebang-regex": "^1.0.0" } @@ -17345,7 +17343,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shelljs": { "version": "0.8.5", @@ -17380,7 +17378,7 @@ "simple-assign": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz", - "integrity": "sha512-otdSSQzuVsmDoe5MnSm4ZgHd5sl0ak6A1CTjW1R/DUHQ8xoZuU1NUzf9x6n9Dvp3nxpvW51WNMQ/7rQ9432xDg==" + "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o=" }, "simple-concat": { "version": "1.0.1", @@ -17400,7 +17398,7 @@ "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "requires": { "is-arrayish": "^0.3.1" }, @@ -17415,13 +17413,13 @@ "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" }, "snapdragon": { "version": "0.8.2", @@ -17449,7 +17447,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -17457,7 +17455,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -17465,7 +17463,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -17482,7 +17480,7 @@ "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { "is-descriptor": "^1.0.0" } @@ -17531,7 +17529,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -17595,12 +17593,12 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "socket.io-parser": { "version": "3.3.2", @@ -17710,7 +17708,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.3", @@ -17748,7 +17746,7 @@ "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", "optional": true, "requires": { "memory-pager": "^1.0.2" @@ -17851,7 +17849,7 @@ "split-skip": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz", - "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg==" + "integrity": "sha1-2J2Iu9L3Pka1FYqjcKVhIk6A1GE=" }, "split-string": { "version": "3.1.0", @@ -17864,7 +17862,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.17.0", @@ -17916,7 +17914,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -17995,7 +17993,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -18069,7 +18067,7 @@ "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -18116,7 +18114,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==" + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" } } }, @@ -18449,7 +18447,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -18497,7 +18495,7 @@ "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "requires": { "abbrev": "1" } @@ -18622,7 +18620,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true } } @@ -18921,8 +18919,7 @@ "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "type-check": { "version": "0.3.2", @@ -19000,7 +18997,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { "graceful-fs": "^4.1.6" } @@ -19030,12 +19027,12 @@ "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==" + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "requires": { "center-align": "^0.1.1", "right-align": "^0.1.1", @@ -19116,7 +19113,7 @@ "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" } } }, @@ -19189,7 +19186,7 @@ "has-value": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -19199,7 +19196,7 @@ "isobject": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "requires": { "isarray": "1.0.0" } @@ -19209,7 +19206,7 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==" + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" } } }, @@ -19281,7 +19278,7 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true } } @@ -19805,7 +19802,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "debug": { @@ -19830,7 +19827,7 @@ "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { "errno": "^0.1.3", @@ -20290,7 +20287,7 @@ "lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" } } }, diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index ca942a38a..2343c2f34 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -25,6 +25,7 @@ export enum DocumentType { EQUATION = "equation", // equation editor FUNCPLOT = "funcplot", // function plotter MAP = "map", + DATAVIZ = "dataviz", // special purpose wrappers that either take no data or are compositions of lower level types LINK = "link", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0bb6ce27f..c5ec9eaf6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -62,6 +62,7 @@ import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; +import DataViz from "../views/nodes/DataViz"; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { @@ -517,6 +518,10 @@ export namespace Docs { layout: { view: EmptyBox, dataField: defaultDataKey }, options: { links: "@links(self)" } }], + [DocumentType.DATAVIZ, { + layout: { view: DataViz, dataField: defaultDataKey }, + options: { _fitWidth: true, _fitHeight: true, links: "@links(self)" } + }] ]); const suffix = "Proto"; @@ -922,6 +927,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } + export function DataVizDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), undefined, { title: "Data Viz", ...options }); + } + export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 60bfc165b..a96f5cb18 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -262,7 +262,10 @@ export class CurrentUserUtils { doc.emptyHeader = headerTemplate; } if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "Comparer", _width: 300, _height: 300 }); + doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "comparer", _width: 300, _height: 300 }); + } + if (doc.emptyDataViz === undefined) { + doc.emptyDataViz = Docs.Create.DataVizDocument({ ...standardOps(), title: "data viz", _width: 300, _height: 300 }); } if (doc.emptyScript === undefined) { doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { ...standardOps(), title: "script", _width: 200, _height: 250, }); diff --git a/src/client/views/nodes/DataViz.scss b/src/client/views/nodes/DataViz.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx new file mode 100644 index 000000000..60b27c19c --- /dev/null +++ b/src/client/views/nodes/DataViz.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import "./DataViz.scss"; +import { FieldView, FieldViewProps } from "./FieldView"; +import { ViewBoxBaseComponent } from '../DocComponent'; + +export class DataViz extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DataViz, fieldKey); + } + + render() { + return (
    Hi
    ) + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 70732e74c..8715fc33a 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -42,6 +42,7 @@ import React = require("react"); import XRegExp = require("xregexp"); import { MapBox } from "./MapBox/MapBox"; import { RecordingBox } from "./RecordingBox"; +import { DataViz } from "./DataViz"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -229,7 +230,7 @@ export class DocumentContentsView extends React.Component Date: Tue, 21 Jun 2022 19:30:17 -0400 Subject: running into jsx parsing error --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 3 ++- src/client/views/nodes/DataViz.tsx | 14 +++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c5ec9eaf6..42191bd7e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -62,7 +62,7 @@ import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; -import DataViz from "../views/nodes/DataViz"; +import { DataViz } from "../views/nodes/DataViz"; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a96f5cb18..8ccb0d197 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -310,7 +310,8 @@ export class CurrentUserUtils { { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { toolTip: "Tap to create a map in the new pane, drag for a map", title: "Map", icon: "map-marker-alt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyMap as Doc, noviceMode: true } + { toolTip: "Tap to create a map in the new pane, drag for a map", title: "Map", icon: "map-marker-alt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyMap as Doc, noviceMode: true }, + { toolTip: "Tap to create a data visualization in a new pane", title: "Data Viz", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyDataViz as Doc} ]; } diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx index 60b27c19c..57c5c0259 100644 --- a/src/client/views/nodes/DataViz.tsx +++ b/src/client/views/nodes/DataViz.tsx @@ -4,11 +4,23 @@ import { FieldView, FieldViewProps } from "./FieldView"; import { ViewBoxBaseComponent } from '../DocComponent'; export class DataViz extends ViewBoxBaseComponent() { + + constructor(props: any) { + super(props); + } + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataViz, fieldKey); } render() { - return (
    Hi
    ) + return ( +
    +
    + Hi +
    +
    + ); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 5bf71bb9fde28de8cc2cedaf03708315933acc30 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Jun 2022 13:46:39 -0400 Subject: more cleanup in currentuserutils. fixed so that removing items from a list in code will remove them from the UI --- src/client/documents/Documents.ts | 6 +- src/client/util/CurrentUserUtils.ts | 209 ++++++++++----------- .../views/collections/CollectionStackingView.tsx | 3 +- 3 files changed, 104 insertions(+), 114 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d0c7d2436..01cfe90da 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -708,8 +708,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List(), options); } - export function ScriptingDocument(script: Opt, options: DocumentOptions = {}, fieldKey?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script, + export function ScriptingDocument(script: Opt|null, options: DocumentOptions = {}, fieldKey?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script: undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } @@ -829,7 +829,7 @@ export namespace Docs { const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; if (!nhght && width && height && nwid) options._nativeHeight = Number(nwid) * Number(height) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(url) : undefined, options); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : "http://www.bing.com/"), options); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e119828c7..52fbda9a9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -104,6 +104,7 @@ export class CurrentUserUtils { } }); items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), "data", item)); + items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), "data", item)); } return doc; } @@ -148,9 +149,9 @@ export class CurrentUserUtils { return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? CurrentUserUtils.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); }); - const reqdOpts = { + const reqdOpts:DocumentOptions = { title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, + _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, }; const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; @@ -197,17 +198,12 @@ export class CurrentUserUtils { // setup templates for different document types when they are iconified from Document Decorations static setupDefaultIconTemplates(doc: Doc, field="template-icons") { - const templateIconsDoc = DocCast(doc[field]); + const reqdOpts = { title: "icon templates", _height: 75, system: true }; + const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => { const iconFieldName = "icon" + (type ? "_" + type : ""); - if (!templateIconsDoc?.[iconFieldName]) { - const template = MakeTemplate(iconTemplate(), true, iconFieldName, templateField); - if (templateIconsDoc) { - templateIconsDoc[iconFieldName] = template; - } - return template; - } + return DocCast(templateIconsDoc[iconFieldName] ?? (templateIconsDoc[iconFieldName] = MakeTemplate(iconTemplate(), true, iconFieldName, templateField))) ; }; const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); const labelBox = (extra: object) => Docs.Create.LabelDocument({ @@ -235,68 +231,37 @@ export class CurrentUserUtils { makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })), // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) ].filter(d => d).map(d => d!); - - const reqdOpts = { title: "icon templates", _height: 75, system: true }; - this.AssignOpts(templateIconsDoc, reqdOpts, iconTemplates) ?? (doc[field] = Docs.Create.TreeDocument(iconTemplates, reqdOpts)); + this.AssignOpts(DocCast(doc[field]), {}, iconTemplates); } - /// initalizes the set of default versions of most document types + /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyPresentation static creatorBtnDescriptors(doc: Doc): { title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, }[] { - const standardOps = () => ({ _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); - const emptyThings:{key:string, // the field name where the empty thing will be stored - opts:DocumentOptions, // the document options that are required for the empty thing - funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing - creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist - }[] = [ - {key: "emptyPresentation", creator: Docs.Create.PresDocument, opts: { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, - {key: "emptyCollection", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "freeform", _width: 150, _height: 100 }}, - {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, - {key: "emptyMath", creator: (opts) => Docs.Create.EquationDocument(opts), opts: { title: "Equation", _backgroundGridShow: true, _width: 300, _height: 35 }}, - {key: "emptySlide", creator: (opts) => Docs.Create.TreeDocument([], opts), funcs: {title: 'self.text?.Text'}, opts: { - _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, - "dragFactory-count": undefined, allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" - }}, - {key: "emptyComparison", creator: Docs.Create.ComparisonDocument, opts: { title: "Comparer", _width: 300, _height: 300 }}, - {key: "emptyScript", creator: (opts) => Docs.Create.ScriptingDocument(undefined, opts), opts: { title: "script", _width: 200, _height: 250, }}, - {key: "emptyScreenshot", creator: Docs.Create.ScreenshotDocument, opts: { title: "empty screenshot", _width: 400, _height: 200 }}, - {key: "emptyWebCam", creator: (opts) => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, - {key: "emptyAudio", creator: (opts) => Docs.Create.AudioDocument(nullAudio, opts), opts: { title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, }}, - {key: "emptyNote", creator: (opts) => Docs.Create.TextDocument("", opts), opts: { title: "text note", _width: 200, _autoHeight: true }}, - {key: "emptyButton", creator: Docs.Create.ButtonDocument, opts: { title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, - {key: "emptyWebpage", creator: (opts) => Docs.Create.WebDocument("http://www.bing.com/", opts), opts: { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }}, - {key: "emptyMap", creator: (opts) => Docs.Create.MapDocument([], opts), opts: { title: "map", _showSidebar: true, _width: 800, _height: 600, }}, - {key: "emptyDataViz", creator: (opts) => Docs.Create.DataVizDocument(opts), opts: { title: "DataViz", _width: 300, _height: 300 }}, - ]; - - emptyThings.forEach(thing => - this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), undefined, thing.funcs)) - - if (doc.emptyHeader === undefined) { - const json = { - doc: { - type: "doc", - content: [ - { - type: "paragraph", attrs: {}, content: [{ - type: "dashField", - attrs: { fieldKey: "author", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }, { - type: "dashField", - attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }] + const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); + const json = { + doc: { + type: "doc", + content: [ + { + type: "paragraph", attrs: {}, content: [{ + type: "dashField", + attrs: { fieldKey: "author", docid: "", hideKey: false }, + marks: [{ type: "strong" }] + }, { + type: "dashField", + attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, + marks: [{ type: "strong" }] }] - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - const headerBtnHgt = 10; - const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { - ...standardOps(), title: "text", _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, + }] + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + const headerBtnHgt = 10; + const headerTemplate = (opts:DocumentOptions) => { + const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", layout: "" + ` ` + @@ -309,28 +274,57 @@ export class CurrentUserUtils { // " " + // " " + // "
    "; - Doc.GetProto(headerTemplate).isTemplateDoc = makeTemplate(Doc.GetProto(headerTemplate), true, "headerView"); - doc.emptyHeader = headerTemplate; + Doc.GetProto(header).isTemplateDoc = makeTemplate(Doc.GetProto(header), true, "headerView"); + Doc.GetProto(header).title = "Untitled Header"; + return header; } + const emptyThings:{key:string, // the field name where the empty thing will be stored + opts:DocumentOptions, // the document options that are required for the empty thing + funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing + creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist + }[] = [ + {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, + {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, + {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, + {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, + {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, + {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }}, + {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, + {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, + {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, + {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, + {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, + {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}}, + {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, + {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }}, + {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, + treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, + allowOverlayDrop: true, treeViewType: TreeViewType.outline, + backgroundColor: "white", _xMargin: 0, _yMargin: 0, _singleLine: true + }, funcs: {title: 'self.text?.Text'}}, + ]; + + emptyThings.forEach(thing => + this.AssignScripts(this.AssignOpts(DocCast(doc["empty"+thing.key]), {...standardOps(thing.key), ...thing.opts}) ?? (doc["empty"+thing.key] = thing.creator({...standardOps(thing.key), ...thing.opts})), undefined, thing.funcs)) + return [ - { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, clickFactory: doc.emptyPane as Doc, }, - { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyMath as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: {onClick: 'openInOverlay(copyDragFactory(this.dragFactory))',onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a custom note", title: "Custom", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a progressive slide",title: "Slide", icon: "file", dragFactory: doc.emptySlide as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreenshot as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs:{ hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a mobile view", title: "Phone", icon: "mobile", dragFactory: doc.activeMobileMenu as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: {hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: '{ return copyDragFactory(this.dragFactory);}'} }, + { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, }, + { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab), scripts: { onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, }, + { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, }, + { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, }, + { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, }, + { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, }, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, }, + { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, - ]; + ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, })) } /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools @@ -345,9 +339,10 @@ export class CurrentUserUtils { return this.AssignScripts(this.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs); }); - const reqdOpts = { + const reqdOpts:DocumentOptions = { title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, + childDocumentsActive: true }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; return this.AssignScripts(this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); @@ -357,34 +352,26 @@ export class CurrentUserUtils { static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}}[] { const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; return [ - { title: "Dashboards", target: CurrentUserUtils.MyDashboards, icon: "desktop", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Search", target: CurrentUserUtils.MySearcher, icon: "search", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Files", target: CurrentUserUtils.MyFilesystem, icon: "folder-open", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Tools", target: CurrentUserUtils.MyTools, icon: "wrench", scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, - { title: "Imports", target: CurrentUserUtils.MyImports, icon: "upload", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Recently Closed", target: CurrentUserUtils.MyRecentlyClosed, icon: "archive", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Shared with me", target: CurrentUserUtils.MySharedDocs, icon: "users", scripts:{onClick: 'selectMainMenu(self)'}, funcs:{badgeValue:badgeValue}}, - { title: "Trails", target: CurrentUserUtils.MyTrails, icon: "pres-trail", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "User Doc", target: CurrentUserUtils.MyUserDocView, icon: "address-card",scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, - ]; + { title: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", }, + { title: "Search", target: this.setupSearcher(doc, "mySearcher"), icon: "search", }, + { title: "Files", target: this.setupFilesystem(doc, "myFilesystem"), icon: "folder-open", }, + { title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} }, + { title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", }, + { title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", }, + { title: "Shared Docs", target: this.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}}, + { title: "Trails", target: this.setupTrails(doc, "myTrails"), icon: "pres-trail", }, + { title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} }, + ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}})); } - /// setup the left sidebar container and panels that can be displayed within it + /// the empty panel that is filled with whichever left menu button's panel has been selected static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { this.AssignOpts(DocCast(doc[field]), {}) ?? (doc[field] = ((doc:Doc) => {doc.system = true; return doc;})(new Doc())); - CurrentUserUtils.setupSearcher(doc, "mySearcher"); - CurrentUserUtils.setupToolsBtnPanel(doc, "myTools"); - CurrentUserUtils.setupImportSidebar(doc, "myImports"); - CurrentUserUtils.setupDashboards(doc, "myDashboards"); - CurrentUserUtils.setupTrails(doc, "myTrails"); - CurrentUserUtils.setupFilesystem(doc, "myFilesystem"); - CurrentUserUtils.setupRecentlyClosedDocs(doc, "myRecentlyClosed"); - CurrentUserUtils.setupUserDocView(doc, "myUserDocView"); } /// Initializes the left sidebar menu buttons and the panels they open up static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") { - this.setupLeftSidebarPanel(doc); // the tools/panels opened up by the menu buttons + this.setupLeftSidebarPanel(doc); const myLeftSidebarMenu = DocCast(doc[field]); const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => { const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; @@ -520,7 +507,7 @@ export class CurrentUserUtils { dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, }; - this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.SearchDocument(reqdOpts)); + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.SearchDocument(reqdOpts)); } /// Initializes the panel of draggable tools that is opened from the left sidebar. @@ -532,7 +519,7 @@ export class CurrentUserUtils { title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, }; - this.AssignOpts(myTools, reqdToolOps, [creatorBtns, templateBtns]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, templateBtns], reqdToolOps)); + return this.AssignOpts(myTools, reqdToolOps, [creatorBtns, templateBtns]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, templateBtns], reqdToolOps)); } /// initializes the left sidebar dashboard pane @@ -595,6 +582,7 @@ export class CurrentUserUtils { if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } + return myTrails; } /// initializes the left sidebar File system pane @@ -624,10 +612,11 @@ export class CurrentUserUtils { if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } + return myFilesystem; } /// initializes the panel displaying docs that have been recently closed - static setupRecentlyClosedDocs(doc: Doc, field:string) { + static setupRecentlyClosed(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", @@ -651,6 +640,7 @@ export class CurrentUserUtils { if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) { recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("self"))!]) } + return recentlyClosed; } /// creates a new, empty filter doc @@ -674,7 +664,7 @@ export class CurrentUserUtils { treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 }; if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }) - this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([doc], reqdOpts)); + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([doc], reqdOpts)); } static linearButtonList = (title: string, opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { @@ -903,7 +893,7 @@ export class CurrentUserUtils { buttonText: "Import", icon: "upload", system: true }; const reqdBtnScripts = { onClick: "importDocument()" }; const newImportBtn = this.AssignOpts(DocCast(myImports.buttonMenuDoc), reqdBtnOpts) ?? (myImports.buttonMenuDoc = Docs.Create.FontIconDocument(reqdBtnOpts)); - this.AssignScripts(newImportBtn, reqdBtnScripts); + return this.AssignScripts(newImportBtn, reqdBtnScripts); } static setupClickEditorTemplates(doc: Doc) { @@ -1055,7 +1045,6 @@ export class CurrentUserUtils { } if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard - !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); userDoc ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); } const state = CurrentUserUtils._urlState; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 684c919bd..4e8c14039 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -229,6 +229,7 @@ export class CollectionStackingView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); + isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)); getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); @@ -245,7 +246,7 @@ export class CollectionStackingView extends CollectionSubView Date: Wed, 22 Jun 2022 14:35:23 -0400 Subject: fixed overlay view dragging & opening up audio/video recordings in overlay. --- src/client/util/CurrentUserUtils.ts | 46 ++++++++++++++++--------------------- src/client/views/MainView.tsx | 7 ++---- src/client/views/OverlayView.scss | 1 + 3 files changed, 23 insertions(+), 31 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 52fbda9a9..ff6ef4809 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -146,7 +146,7 @@ export class CurrentUserUtils { doc.isTemplateDoc = makeTemplate(doc); return doc; } - return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? CurrentUserUtils.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); + return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); }); const reqdOpts:DocumentOptions = { @@ -201,34 +201,28 @@ export class CurrentUserUtils { const reqdOpts = { title: "icon templates", _height: 75, system: true }; const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); - const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => { + const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: (opts:DocumentOptions) => Doc) => { const iconFieldName = "icon" + (type ? "_" + type : ""); - return DocCast(templateIconsDoc[iconFieldName] ?? (templateIconsDoc[iconFieldName] = MakeTemplate(iconTemplate(), true, iconFieldName, templateField))) ; + return DocCast(templateIconsDoc[iconFieldName] ?? (templateIconsDoc[iconFieldName] = MakeTemplate(iconTemplate({onClick:deiconifyScript(), system: true}), true, iconFieldName, templateField))) ; }; const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); - const labelBox = (extra: object) => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, system: true, onClick: deiconifyScript(), ...extra, - }); - const imageBox = (url: string, extra: object) => Docs.Create.ImageDocument(url, { - "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, - _showTitle: "title", system: true, onClick: deiconifyScript(), ...extra - }); - const fontBox = () => Docs.Create.FontIconDocument({ - _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() + const labelBox = (opts: object) => Docs.Create.LabelDocument({ + textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts }); + const imageBox = (url: Opt, opts: object) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", ...opts }); + const fontBox = (opts:DocumentOptions) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); const iconTemplates = [ - makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), - makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), - makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), - makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), - makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), - makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), - makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.BUTTON, "data", fontBox), + makeIconTemplate(undefined, "title", (opts) => labelBox({ ...opts, _backgroundColor: "dimgray" })), + makeIconTemplate(DocumentType.AUDIO, "title", (opts) => labelBox({ ...opts, _backgroundColor: "lightgreen" })), + makeIconTemplate(DocumentType.PDF, "title", (opts) => labelBox({ ...opts, _backgroundColor: "pink" })), + makeIconTemplate(DocumentType.WEB, "title", (opts) => labelBox({...opts, _backgroundColor: "brown" })), + makeIconTemplate(DocumentType.RTF, "text", (opts) => labelBox({ ...opts, _showTitle: "creationDate" })), + makeIconTemplate(DocumentType.IMG, "data", (opts) => imageBox("", { ...opts, _height: undefined, })), + makeIconTemplate(DocumentType.COL, "icon", (opts) => imageBox(undefined, opts)), + makeIconTemplate(DocumentType.VID, "icon", (opts) => imageBox(undefined, opts)), + makeIconTemplate(DocumentType.BUTTON, "data", (opts) => fontBox(opts)), //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now - makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })), + makeIconTemplate("transcription" as any, "transcription", (opts) => labelBox({ ...opts, _backgroundColor: "orange" })), // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) ].filter(d => d).map(d => d!); this.AssignOpts(DocCast(doc[field]), {}, iconTemplates); @@ -315,10 +309,10 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, }, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, }, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, }, - { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, }, + { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, }, - { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: { onClick: 'openInOverlay(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'},funcs: { hidden: 'IsNoviceMode()'}}, { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} }, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}}, { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, }, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6ee8065f5..67a73feff 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -8,9 +8,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; -import { DocCast, PromiseValue, StrCast } from '../../fields/Types'; +import { PromiseValue, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; @@ -32,8 +31,7 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; -import { TreeViewType } from './collections/CollectionTreeView'; -import { CollectionView, CollectionViewType } from './collections/CollectionView'; +import { CollectionViewType } from './collections/CollectionView'; import "./collections/TreeView.scss"; import { ComponentDecorations } from './ComponentDecorations'; import { ContextMenu } from './ContextMenu'; @@ -49,7 +47,6 @@ import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; import "./MainView.scss"; import { AudioBox } from './nodes/AudioBox'; -import { ButtonType } from './nodes/button/FontIconBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 302e7a5e3..033cdf1f7 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -15,6 +15,7 @@ flex-direction: column; top: 0; left: 0; + pointer-events: all; } .overlayWindow-outerDiv, -- cgit v1.2.3-70-g09d2 From 6e0554f57a67932d20fab6b1eb3e61e2f8eefc4f Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Jun 2022 17:05:07 -0400 Subject: yet more currentuserutils cleanup --- src/client/util/CurrentUserUtils.ts | 217 ++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 121 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ff6ef4809..fe9a9b2d9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,6 +1,6 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Opt, StrListCast } from "../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -8,7 +8,7 @@ import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types"; +import { Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types"; import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { OmitKeys, Utils } from "../../Utils"; @@ -77,7 +77,7 @@ export class CurrentUserUtils { static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); + doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any", documentView:Doc.name}, {"_readOnly_": true}); } }); funcs && Object.keys(funcs).map(key => { @@ -108,6 +108,9 @@ export class CurrentUserUtils { } return doc; } + static AssignDocField(doc:Doc, field:string, creator:(reqdOpts:DocumentOptions, items?:Doc[]) => Doc, reqdOpts:DocumentOptions, items?: Doc[], scripts?:{[key:string]:string}, funcs?:{[key:string]:string}) { + return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); + } // initializes experimental advanced template views - slideView, headerView static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) { @@ -138,7 +141,7 @@ export class CurrentUserUtils { const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { if (templateBtn) { this.AssignOpts(templateBtn,btnOpts); - this.AssignOpts(DocCast(templateBtn.dragFactory), templateOptions) ?? (templateBtn.dragFactory = template(templateOpts)); + this.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions); } return templateBtn; }; @@ -182,10 +185,7 @@ export class CurrentUserUtils { /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc, field="myTemplates") { - const preEleOpts:DocumentOptions = { - title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" - }; - this.AssignOpts(DocCast(doc.presElement), preEleOpts) ?? (doc.presElement= Docs.Create.PresElementBoxDocument(preEleOpts)); + this.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"}); const templates = [ DocCast(doc.presElement), CurrentUserUtils.setupNoteTemplates(doc), @@ -193,7 +193,7 @@ export class CurrentUserUtils { ]; const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; - return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, templates) ?? (doc[field] = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts); + return this.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts); } // setup templates for different document types when they are iconified from Document Decorations @@ -299,9 +299,7 @@ export class CurrentUserUtils { }, funcs: {title: 'self.text?.Text'}}, ]; - emptyThings.forEach(thing => - this.AssignScripts(this.AssignOpts(DocCast(doc["empty"+thing.key]), {...standardOps(thing.key), ...thing.opts}) ?? (doc["empty"+thing.key] = thing.creator({...standardOps(thing.key), ...thing.opts})), undefined, thing.funcs)) - + emptyThings.forEach(thing => this.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs)); return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, }, @@ -360,7 +358,7 @@ export class CurrentUserUtils { /// the empty panel that is filled with whichever left menu button's panel has been selected static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { - this.AssignOpts(DocCast(doc[field]), {}) ?? (doc[field] = ((doc:Doc) => {doc.system = true; return doc;})(new Doc())); + this.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.system = true; return doc;})(new Doc()), {system:true}); } /// Initializes the left sidebar menu buttons and the panels they open up @@ -381,14 +379,13 @@ export class CurrentUserUtils { title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true }; - const reqdScripts = { dropConverter: "convertToButtons(dragData)" } - return this.AssignScripts(this.AssignOpts(myLeftSidebarMenu, reqdStackOpts, menuBtns) ?? (doc[field] = Docs.Create.StackingDocument(menuBtns, reqdStackOpts)), reqdScripts); + return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); } // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") { const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,}; - this.AssignOpts(DocCast(doc[field]), reqdOpts, this.setupMobileButtons()) ?? (doc[field] = Docs.Create.StackingDocument(this.setupMobileButtons(), reqdOpts)); + this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts); } // Sets up mobile buttons for inside mobile menu @@ -497,11 +494,9 @@ export class CurrentUserUtils { /// Search option on the left side button panel static setupSearcher(doc: Doc, field:string) { - const reqdOpts:DocumentOptions = { - dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", - _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, - }; - return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.SearchDocument(reqdOpts)); + return this.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), { + dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", + _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, }); } /// Initializes the panel of draggable tools that is opened from the left sidebar. @@ -513,7 +508,7 @@ export class CurrentUserUtils { title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, }; - return this.AssignOpts(myTools, reqdToolOps, [creatorBtns, templateBtns]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, templateBtns], reqdToolOps)); + return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]); } /// initializes the left sidebar dashboard pane @@ -537,7 +532,7 @@ export class CurrentUserUtils { childContextMenuIcons: new List(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." }; - myDashboards = this.AssignOpts(myDashboards, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + myDashboards = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; const contextMenuScripts = [newDashboard]; const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters @@ -571,7 +566,7 @@ export class CurrentUserUtils { _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, explainer: "All of the trails that you have created will appear here." }; - myTrails = this.AssignOpts(myTrails, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([],reqdOpts )); + myTrails = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); const contextMenuScripts = [reqdBtnScript.onClick]; if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); @@ -582,8 +577,7 @@ export class CurrentUserUtils { /// initializes the left sidebar File system pane static setupFilesystem(doc: Doc, field:string) { var myFilesystem = DocCast(doc[field]); - const reqdOrphansOpts:DocumentOptions = { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }; - this.AssignOpts(DocCast(doc.myFileOrphans), reqdOrphansOpts) ?? (doc.myFileOrphans = Docs.Create.TreeDocument([], reqdOrphansOpts)); + const myFileOrphans = this.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); const newFolder = `makeTopLevelFolder()`; const newFolderOpts: DocumentOptions = { @@ -601,7 +595,7 @@ export class CurrentUserUtils { childContextMenuIcons: new List(["plus"]), explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." }; - myFilesystem = this.AssignOpts(myFilesystem, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([DocCast(doc.myFileOrphans)], reqdOpts)); + myFilesystem = this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]); const childContextMenuScripts = [newFolder]; if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); @@ -618,16 +612,13 @@ export class CurrentUserUtils { contextMenuIcons:new List(["trash"]), explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." }; - const recentlyClosed = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + const recentlyClosed = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); const clearAll = (target:string) => `getProto(${target}).data = new List([])`; const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", system: true, toolTip: "Empty recently closed",}; - const clearBtnsScripts = {onClick: clearAll("self.target")} - const clearDocsButton = this.AssignScripts( - this.AssignOpts(DocCast(recentlyClosed?.clearDocsBtn), clearBtnsOpts) ?? (recentlyClosed.clearDocsBtn = Docs.Create.FontIconDocument(clearBtnsOpts)), - clearBtnsScripts); + const clearDocsButton = this.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")}); if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton; @@ -657,80 +648,77 @@ export class CurrentUserUtils { boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 }; - if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }) - return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([doc], reqdOpts)); + if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }); + return this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]); } - static linearButtonList = (title: string, opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { - title, ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true, + static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { + ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), _lockedPosition: true, system: true, flexDirection: "row" }) static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({ - btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, + btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, + _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), + _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, }) /// initializes the required buttons in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc, field="myDockedBtns") { const dockedBtns = DocCast(doc[field]); - const dockBtn = (title:string, onClick: string, opts: DocumentOptions) => { - const btn = this.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === title), opts) ?? - CurrentUserUtils.createToolButton({title, ...opts}); - this.AssignScripts(btn, {onClick}) - return btn; - } + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}) => + this.AssignScripts(this.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? + CurrentUserUtils.createToolButton(opts), scripts); + const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - { title: "undo", click: "undo()", opts: { icon: "undo-alt", toolTip: "Click to undo" }}, - { title: "redo", click:"redo()", opts: { icon: "redo-alt", toolTip: "Click to redo" }} + { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, + { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }} ]; - const btns = btnDescs.map(desc => dockBtn(desc.title, desc.click, {_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts})); + const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts = { title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - return this.AssignOpts(dockedBtns, dockBtnsReqdOpts, btns) ?? (doc[field] = CurrentUserUtils.linearButtonList("dockedBtns", dockBtnsReqdOpts, btns)); + return this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); } static textTools():Button[] { return [ - { - title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]), - scripts :{script : 'setFont(value, _readOnly_)'} - }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'} }, - { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts:{script: '{ return setFontColor(value, _readOnly_); }'}}, - { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, - { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline",scripts: {onClick:'{ return toggleUnderline(_readOnly_);}'} }, - { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} }, - { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} }, + { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, scripts: {script: 'setFont(value, _readOnly_)'}, + btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions }, + { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_); }'}}, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, + { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick:'{ return toggleUnderline(_readOnly_);}'} }, + { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} }, + { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} }, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}}, - { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }}, + { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }}, { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} }, - { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} }, - { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}}, + { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} }, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}}, ]; } static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveTool("pen", _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveTool("write", _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveTool("eraser", _readOnly_);}' }}, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", _readOnly_);}' }}, // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return setActiveTool("circle", _readOnly_);}'} }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:'{ return setActiveTool("circle", _readOnly_);}'} }, // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveTool("line", _readOnly_);}' }}, - { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: {script: "{ return setFillColor(value, _readOnly_);}"} }, - { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'} }, - { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick: '{ return setActiveTool("line", _readOnly_);}' }}, + { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: "{ return setFillColor(value, _readOnly_);}"} }, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, ]; } @@ -740,52 +728,43 @@ export class CurrentUserUtils { static webTools() { return [ - { 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: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(_readOnly_); }' }}, + { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(_readOnly_); }'}}, //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, - { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL(value, _readOnly_)' }, + { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, ]; } static contextMenuTools():Button[] { return [ - { - title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - btnList: new List([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, + { btnList: new List([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid]), - scripts: {script: 'setView(value, _readOnly_)'}, - }, // Always show - { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton,icon: "chevron-left", scripts:{onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'} }, - { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, icon: "chevron-right", scripts:{onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, - { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: { script: "setBackgroundColor(value, _readOnly_)"}, funcs:{ hidden: '!selectedDocumentType()' }}, // Only when a document is selected - { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", scripts: {script: "setHeaderColor(value, _readOnly_)"}, funcs : {hidden: '!selectedDocumentType()'} }, - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", scripts: {onClick : 'toggleOverlay(_readOnly_)'}, funcs: {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", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available - { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available - { title: "Web", icon: "web", subMenu: CurrentUserUtils.webTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.WEB}")`, hidden: `!selectedDocumentType("${DocumentType.WEB}")`} }, // Only when Web is selected - { title: "Schema", icon: "schema", subMenu: CurrentUserUtils.schemaTools(), funcs: { linearViewIsExpanded: `selectedDocumentType(undefined, "${CollectionViewType.Schema}")`, hidden: `!selectedDocumentType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected + title: "Perspective", toolTip: "View", width: 100,btnType: ButtonType.DropdownList,ignoreClick: true, scripts: { script: 'setView(value, _readOnly_)'}}, + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, + { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, + { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setBackgroundColor(value, _readOnly_)'},funcs: {hidden: '!selectedDocumentType()'}}, // Only when a document is selected + { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setHeaderColor(value, _readOnly_)'}, funcs: {hidden: '!selectedDocumentType()'}}, + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, scripts: { onClick: 'toggleOverlay(_readOnly_)'}, funcs: {hidden: '!selectedDocumentType(undefined, "freeform", true)'}}, // Only when floating document is selected in freeform + { title: "Text", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available + { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available + { title: "Web", icon: "web", subMenu: CurrentUserUtils.webTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.WEB}")`, hidden: `!selectedDocumentType("${DocumentType.WEB}")`} }, // Only when Web is selected + { title: "Schema", icon: "schema", subMenu: CurrentUserUtils.schemaTools(), funcs: {linearViewIsExpanded: `selectedDocumentType(undefined, "${CollectionViewType.Schema}")`, hidden: `!selectedDocumentType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected ]; } /// initializes a context menu button for the top bar context menu static setupContextMenuButton(params:Button, btnDoc?:Doc) { const reqdOpts:DocumentOptions = { - title: params.title, btnType: params.btnType, icon: params.icon, toolTip: params.toolTip, ignoreClick: params.ignoreClick, - numBtnType: params.numBtnType, numBtnMin: params.numBtnMin, numBtnMax: params.numBtnMax,_nativeHeight: 30, btnList: params.btnList, - backgroundColor: params.scripts?.onClick ? undefined: "transparent", /// a bit hacky. if an onClick is specified, then we assume we assume a toggle use onClick to get the backgroundColor (see below). Otherwise, assume a transparent background - _nativeWidth: params.width ? params.width : 30, - _width: params.width ? params.width : 30, - _height: 30, + ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, + backgroundColor: params.scripts?.onClick ? undefined: "transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background color: Colors.WHITE, system: true, dontUndo: true, - _stayInCollection: true, - _hideContextMenu: true, - _lockedPosition: true, - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + _nativeWidth: params.width ?? 30, _width: params.width ?? 30, + _height: 30, _nativeHeight: 30, + _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, + _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), }; const reqdFuncs:{[key:string]:any} = { ...params.funcs, @@ -802,28 +781,26 @@ export class CurrentUserUtils { if (!params.subMenu) { return this.setupContextMenuButton(params, menuBtnDoc); } else { - const reqdSubMenuOpts = { title: params.title, icon: params.icon, childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, + const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, title:"submenu", + childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, linearViewSubMenu: true, linearViewExpandable: true, }; - const reqdSubMenuFuncs:{[key:string]:any} = { ...params.funcs}; return this.AssignScripts(this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? - CurrentUserUtils.linearButtonList("submenu", reqdSubMenuOpts, params.subMenu.map(sub => + this.linearButtonList(reqdSubMenuOpts, params.subMenu.map(sub => this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) - )), undefined, reqdSubMenuFuncs); + )), undefined, params.funcs); } }); - const reqdCtxtOpts = { title: "menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; - return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts) ?? (doc[field] = CurrentUserUtils.linearButtonList("contextMenuButtons", reqdCtxtOpts, ctxtMenuBtns)); + const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; + return this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, ctxtMenuBtns); } /// collection of documents rendered in the overlay layer above all tabs and other UI static setupOverlays(doc: Doc, field = "myOverlayDocs") { - const reqdOpts = { title: "overlay documents", backgroundColor: "#aca3a6", system: true }; - return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.FreeformDocument([], reqdOpts)); + return this.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", system: true }); } static setupPublished(doc:Doc, field = "myPublishedDocs") { - const reqdOpts = { title: "published docs", backgroundColor: "#aca3a6", system: true }; - return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + return this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", system: true }); } /// The database of all links on all documents @@ -848,8 +825,11 @@ export class CurrentUserUtils { static async setupSharedDocs(doc: Doc, sharingDocumentId: string) { const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); - const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) - + const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}"; + + const sharedScripts = { + treeViewChildDoubleClick: dblClkScript, + } const sharedDocOpts:DocumentOptions = { title: "My Shared Docs", userColor: "rgb(202, 202, 202)", @@ -857,9 +837,6 @@ export class CurrentUserUtils { childContextMenuScripts: new List([addToDashboards!,]), childContextMenuLabels: new List(["Add to Dashboards",]), childContextMenuIcons: new List(["user-plus",]), - treeViewChildDoubleClick: dblClkScript, - }; - const sharedRequiredDocOpts:DocumentOptions = { "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar @@ -867,27 +844,25 @@ export class CurrentUserUtils { explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" }; - const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer")); - return this.AssignOpts(DocCast(sharedDocs), sharedRequiredDocOpts) ?? - (doc.mySharedDocs = Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); + await DocServer.GetRefField(sharingDocumentId + "outer"); + return this.AssignDocField(doc, "mySharedDocs", (opts) => Docs.Create.TreeDocument([], opts, sharingDocumentId + "outer", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); } /// Import option on the left side button panel - static setupImportSidebar(doc: Doc, field:string) { + static setupImportSidebar(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { title: "My Imports", _forceActive: true, buttonMenu: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." }; - const myImports = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.StackingDocument([], reqdOpts)); + const myImports = this.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts); const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true }; const reqdBtnScripts = { onClick: "importDocument()" }; - const newImportBtn = this.AssignOpts(DocCast(myImports.buttonMenuDoc), reqdBtnOpts) ?? (myImports.buttonMenuDoc = Docs.Create.FontIconDocument(reqdBtnOpts)); - return this.AssignScripts(newImportBtn, reqdBtnScripts); + return this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, reqdBtnScripts); } static setupClickEditorTemplates(doc: Doc) { -- cgit v1.2.3-70-g09d2 From 4afe316e6649d915e2bb6f0eecbc9f3cddab7e24 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 22 Jun 2022 23:08:56 -0400 Subject: fixing up Field Infos to work with new initialization. --- src/client/documents/Documents.ts | 124 ++++++++++++++---------------------- src/client/util/CurrentUserUtils.ts | 57 +++++++++++------ 2 files changed, 85 insertions(+), 96 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 01cfe90da..ad6a4dd3c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -69,53 +69,57 @@ class EmptyBox { return ""; } } -abstract class FInfo { +export abstract class FInfo { description: string = ""; - type?: string; + fieldType?: string; values?: Field[]; - layoutField?: boolean; // is this field a layout (or datadoc) field // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string - constructor(d: string, l: boolean = false) { this.description = d; this.layoutField = l; } + constructor(d: string) { this.description = d; } } -class BoolInfo extends FInfo { type?= "boolean"; values?: boolean[] = [true, false]; } -class NumInfo extends FInfo { type?= "number"; values?: number[] = []; } -class StrInfo extends FInfo { type?= "string"; values?: string[] = []; } -class DocInfo extends FInfo { type?= "Doc"; values?: Doc[] = []; } -class DimInfo extends FInfo { type?= "DimUnit"; values?= [DimUnit.Pixel, DimUnit.Ratio]; } -class PEInfo extends FInfo { type?= "pointerEvents"; values?= ["all", "none"]; } -class DAInfo extends FInfo { type?= "dropActionType"; values?= ["alias", "copy", "move", "same", "proto", "none"]; } +class BoolInfo extends FInfo { fieldType?= "boolean"; values?: boolean[] = [true, false]; } +class NumInfo extends FInfo { fieldType?= "number"; values?: number[] = []; constructor(d:string, values?:number[]) { super(d); this.values = values; }} +class StrInfo extends FInfo { fieldType?= "string"; values?: string[] = []; constructor(d:string, values?:string[]) { super(d); this.values = values; }} +class DocInfo extends FInfo { fieldType?= "Doc"; values?: Doc[] = []; constructor(d:string, values?:Doc[]) { super(d); this.values = values; }} +class DimInfo extends FInfo { fieldType?= "DimUnit"; values?= [DimUnit.Pixel, DimUnit.Ratio]; } +class PEInfo extends FInfo { fieldType?= "pointerEvents"; values?= ["all", "none"]; } +class DAInfo extends FInfo { fieldType?= "dropActionType"; values?= ["alias", "copy", "move", "same", "proto", "none"]; } type BOOLt = BoolInfo | boolean; -type NUMt = NumInfo | number; -type STRt = StrInfo | string; -type DOCt = DocInfo | Doc; -type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; -type PEVt = PEInfo | "none" | "all"; +type NUMt = NumInfo | number; +type STRt = StrInfo | string; +type DOCt = DocInfo | Doc; +type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; +type PEVt = PEInfo | "none" | "all"; type DROPt = DAInfo | dropActionType; export class DocumentOptions { + x?: NUMt = new NumInfo("x coordinate of document in a freeform view"); + y?: NUMt = new NumInfo("y coordinage of document in a freeform view"); + z?: NUMt = new NumInfo("whether document is in overlay (1) or not (0)", [1,0]); system?: BOOLt = new BoolInfo("is this a system created/owned doc"); + type?: STRt = new StrInfo("type of document", Array.from(Object.keys(DocumentType))); + title?: string; _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); - userColor?: string; // color associated with a Dash user (seen in header fields of shared documents) - color?: string; // foreground color data doc + userColor?: STRt = new StrInfo("color associated with a Dash user (seen in header fields of shared documents)"); + color?: STRt = new StrInfo("foreground color data doc"); backgroundColor?: STRt = new StrInfo("background color for data doc"); - _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true); - _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents", true); - _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title", true); - _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes", true); - _headerPointerEvents?: PEVt = new PEInfo("types of events the header of a custom text document can consume", true); - _panX?: NUMt = new NumInfo("horizontal pan location of a freeform view", true); - _panY?: NUMt = new NumInfo("vertical pan location of a freeform view", true); - _width?: NUMt = new NumInfo("displayed width of a document", true); - _height?: NUMt = new NumInfo("displayed height of document", true); - _nativeWidth?: NUMt = new NumInfo("native width of document contents (e.g., the pixel width of an image)", true); - _nativeHeight?: NUMt = new NumInfo("native height of document contents (e.g., the pixel height of an image)", true); - _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", true); - _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units", true); - _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)", true); - _fitContentsToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents + _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)"); + _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents"); + _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title"); + _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes"); + _headerPointerEvents?: PEVt = new PEInfo("types of events the header of a custom text document can consume"); + _panX?: NUMt = new NumInfo("horizontal pan location of a freeform view"); + _panY?: NUMt = new NumInfo("vertical pan location of a freeform view"); + _width?: NUMt = new NumInfo("displayed width of a document"); + _height?: NUMt = new NumInfo("displayed height of document"); + _nativeWidth?: NUMt = new NumInfo("native width of document contents (e.g., the pixel width of an image)"); + _nativeHeight?: NUMt = new NumInfo("native height of document contents (e.g., the pixel height of an image)"); + _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height"); + _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units"); + _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)"); + _fitContentsToBox?: BOOLt = new BoolInfo("whether a freeformview should zoom/scale to create a shrinkwrapped view of its content"); _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed @@ -155,23 +159,20 @@ export class DocumentOptions { _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) _timecodeToHide?: number; // the time that a document should be hidden _timelineLabel?: boolean; // whether the document exists on a timeline - "_carousel-caption-xMargin"?: number; - "_carousel-caption-yMargin"?: number; - "icon-nativeWidth"?: number; - "icon-nativeHeight"?: number; - "dragFactory-count"?: number; // number of items created from a drag button (used for setting title with incrementing index) - x?: number; - y?: number; - z?: number; // whether document is in overlay (1) or not (0 or undefined) + "_carousel-caption-xMargin"?: NUMt = new NumInfo("x margin of caption inside of a carouself collection"); + "_carousel-caption-yMargin"?: NUMt = new NumInfo("y margin of caption inside of a carouself collection"); + "icon-nativeWidth"?: NUMt = new NumInfo("native width of icon view"); + "icon-nativeHeight"?: NUMt = new NumInfo("native height of icon view"); + "dragFactory-count"?: NUMt = new NumInfo("number of items created from a drag button (used for setting title with incrementing index)"); lat?: number; lng?: number; infoWindowOpen?: boolean; author?: string; _layoutKey?: string; + fieldValues?: List; // possible field values used by fieldInfos + fieldType?: string; // type of afield used by fieldInfos unrendered?: boolean; // denotes an annotation that is not rendered with a DocumentView (e.g, rtf/pdf text selections and links to scroll locations in web/pdf) - type?: string; - title?: string; - "acl-Public"?: string; // public permissions + "acl-Public"?: string; // public permissions "_acl-Public"?: string; // public permissions version?: string; // version identifier for a document label?: string; @@ -340,33 +341,6 @@ export class DocumentOptions { } export namespace Docs { - const _docOptions = new DocumentOptions(); - - export async function setupFieldInfos() { - return await DocServer.GetRefField("FieldInfos8") as Doc ?? - runInAction(() => { - const infos = new Doc("FieldInfos8", true); - const keys = Object.keys(new DocumentOptions()); - for (const key of keys) { - const options = (_docOptions as any)[key] as FInfo; - const finfo = new Doc(); - finfo.name = key; - switch (options.type) { - case "boolean": finfo.options = new List(options.values as any as boolean[]); break; - case "number": finfo.options = new List(options.values as any as number[]); break; - case "Doc": finfo.options = new List(options.values as any as Doc[]); break; - default: // string, pointerEvents, dimUnit, dropActionType - finfo.options = new List(options.values as any as string[]); break; - } - finfo.layoutField = options.layoutField; - finfo.description = options.description; - finfo.type = options.type; - infos[key] = finfo; - } - return infos; - }); - } - export let newAccount: boolean = false; export namespace Prototypes { @@ -798,8 +772,8 @@ export namespace Docs { I.tool = tool; I["text-align"] = "center"; I.title = "ink"; - I.x = options.x; - I.y = options.y; + I.x = options.x as number; + I.y = options.y as number; I._width = options._width as number; I._height = options._height as number; I._fontFamily = "cursive"; @@ -1266,8 +1240,8 @@ export namespace DocUtils { return DocServer.GetRefField(id).then(field => { if (field instanceof Doc) { const alias = Doc.MakeAlias(field); - alias.x = options.x || 0; - alias.y = options.y || 0; + alias.x = options.x as number || 0; + alias.y = options.y as number || 0; alias._width = (options._width as number) || 300; alias._height = (options._height as number) || (options._width as number) || 300; return alias; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fe9a9b2d9..b9724051a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -13,7 +13,7 @@ import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; -import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; +import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; @@ -39,6 +39,7 @@ import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; interface Button { + // DocumentOptions fields a button can set title?: string; toolTip?: string; icon?: string; @@ -51,6 +52,8 @@ interface Button { btnList?: List; ignoreClick?: boolean; buttonText?: string; + + // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; } funcs?: { [key:string]: string }; subMenu?: Button[]; @@ -861,8 +864,7 @@ export class CurrentUserUtils { const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true }; - const reqdBtnScripts = { onClick: "importDocument()" }; - return this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, reqdBtnScripts); + return this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); } static setupClickEditorTemplates(doc: Doc) { @@ -875,9 +877,8 @@ export class CurrentUserUtils { targetScriptKey: "onChildClick", system: true }); - const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "openOnRight(self.doubleClickView)", - {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); + const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "openOnRight(self.doubleClickView)", {}), + { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); } @@ -914,7 +915,7 @@ export class CurrentUserUtils { } static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - doc.globalGroupDatabase ?? (doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument()); + this.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {}); await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]), async () => { @@ -954,17 +955,35 @@ export class CurrentUserUtils { this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates - doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); - doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents + this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption + this.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {}); + this.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); - doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } return doc; } + static setupFieldInfos(doc:Doc, field="fieldInfos") { + const fieldInfoOpts = { title: "Field Infos", system: true}; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object + const infos = this.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts); + const entries = Object.entries(new DocumentOptions()); + entries.forEach(pair => { + if (!Array.from(Object.keys(fieldInfoOpts)).includes(pair[0])) { + const options = pair[1] as FInfo; + const opts:DocumentOptions = { system: true, title: pair[0], ...OmitKeys(options, ["values"]).omit, fieldIsLayout: pair[0].startsWith("_")}; + switch (options.fieldType) { + case "boolean": opts.fieldValues = new List(options.values as any); break; + case "number": opts.fieldValues = new List(options.values as any); break; + case "Doc": opts.fieldValues = new List(options.values as any); break; + default: opts.fieldValues = new List(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType + } + this.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts); + } + }); + } public static async loadCurrentUser() { return rp.get(Utils.prepend("/getCurrentUser")).then(async response => { @@ -1007,17 +1026,14 @@ export class CurrentUserUtils { public static _urlState: HistoryUtil.DocUrl; public static openDashboard = (doc: Doc, fromHistory = false) => { - const userDoc = Doc.UserDoc(); CurrentUserUtils.MainDocId = doc[Id]; - if (!DocListCast(CurrentUserUtils.MyDashboards.data).includes(doc)) { - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); - } + Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); - if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard - userDoc ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); - } + // this has the side-effect of setting the main container since we're assigning the active/guest dashboard + Doc.UserDoc() ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); + const state = CurrentUserUtils._urlState; - if (state.sharing === true && !userDoc) { + if (state.sharing === true && !Doc.UserDoc()) { DocServer.Control.makeReadOnly(); } else { fromHistory || HistoryUtil.pushState({ @@ -1054,7 +1070,7 @@ export class CurrentUserUtils { const upload = Utils.prepend("/uploadDoc"); const formData = new FormData(); const file = input.files && input.files[0]; - if (file && file.type === 'application/zip') { + if (file?.type === 'application/zip') { formData.append('file', file); formData.append('remap', "true"); const response = await fetch(upload, { method: "POST", body: formData }); @@ -1067,9 +1083,8 @@ export class CurrentUserUtils { } } } else if (input.files && input.files.length !== 0) { - const importDocs = CurrentUserUtils.MyImports; const disposer = OverlayView.ShowSpinner(); - DocListCastAsync(importDocs.data).then(async list => { + DocListCastAsync(CurrentUserUtils.MyImports.data).then(async list => { const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); if (results.length !== input.files?.length) { alert("Error uploading files - possibly due to unsupported file types"); -- cgit v1.2.3-70-g09d2 From 8eb794e233f905daaa5b9a25c6720e567512653e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Jun 2022 12:15:47 -0400 Subject: fixed : contextMenu . fixed some fitWidth issues for text where heights weren't working right --- src/client/documents/Documents.ts | 27 +++------ src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/ContextMenu.tsx | 64 ++++++---------------- src/client/views/ContextMenuItem.tsx | 10 +--- src/client/views/nodes/DocumentView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- 6 files changed, 33 insertions(+), 77 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ad6a4dd3c..fc73deb36 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -363,7 +363,7 @@ export namespace Docs { layout: { view: FormattedTextBox, dataField: "text" }, options: { _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, - links: "@links(self)" + forceReflow: true, links: "@links(self)" } }], [DocumentType.SEARCH, { @@ -400,7 +400,7 @@ export namespace Docs { }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: "lightGray", links: "@links(self)" } + options: { _height: 100, backgroundColor: "lightGray", forceReflow: true, nativeDimModifiable: true, links: "@links(self)" } }], [DocumentType.REC, { layout: { view: VideoBox, dataField: defaultDataKey }, @@ -412,7 +412,7 @@ export namespace Docs { }], [DocumentType.MAP, { layout: { view: MapBox, dataField: defaultDataKey }, - options: { _height: 600, _width: 800, links: "@links(self)" } + options: { _height: 600, _width: 800, nativeDimModifiable: true, links: "@links(self)" } }], [DocumentType.IMPORT, { layout: { view: DirectoryImportBox, dataField: defaultDataKey }, @@ -450,11 +450,11 @@ export namespace Docs { }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, - options: { links: "@links(self)", hideResizeHandles: true, hideDecorationTitle: true } + options: { links: "@links(self)", nativeDimModifiable: true, hideResizeHandles: true, hideDecorationTitle: true } }], [DocumentType.FUNCPLOT, { layout: { view: FunctionPlotBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } + options: { nativeDimModifiable: true, links: "@links(self)" } }], [DocumentType.BUTTON, { layout: { view: LabelBox, dataField: "onClick" }, @@ -493,7 +493,7 @@ export namespace Docs { }], [DocumentType.COMPARISON, { layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" } + options: { clipWidth: 50, nativeDimModifiable: true, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" } }], [DocumentType.GROUPDB, { data: new List(), @@ -506,7 +506,7 @@ export namespace Docs { }], [DocumentType.DATAVIZ, { layout: { view: DataVizBox, dataField: defaultDataKey }, - options: { _fitWidth: true, _fitHeight: true, links: "@links(self)" } + options: { _fitWidth: true, nativeDimModifiable: true, links: "@links(self)" } }] ]); @@ -530,15 +530,6 @@ export namespace Docs { const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix); // fetch the actual prototype documents from the server const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds); - if (!Docs.newAccount) { - Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB - Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true; - Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true; - Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB - Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true; - Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true; - } - // update this object to include any default values: DocumentOptions for all prototypes prototypeIds.map(id => { const existing = actualProtos[id] as Doc; @@ -1273,8 +1264,8 @@ export namespace DocUtils { })) as ContextMenuProps[], icon: "sticky-note" }); - const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data).lastElement()?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ - description: ":" + StrCast(dragDoc.title), + const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data)[0]?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ + description: ":" + StrCast(dragDoc.title).replace("Untitled ",""), event: undoBatch((args: { x: number, y: number }) => { const newDoc = Doc.copyDragFactory(dragDoc); if (newDoc) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b9724051a..28d20c56b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -282,13 +282,13 @@ export class CurrentUserUtils { }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _fitWidth:false, _backgroundGridShow: true, }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, - {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, + {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index e2f98de1e..9034cacb2 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,10 +1,10 @@ import React = require("react"); -import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem"; -import { observable, action, computed, runInAction, IReactionDisposer, reaction } from "mobx"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, IReactionDisposer, observable } from "mobx"; import { observer } from "mobx-react"; import "./ContextMenu.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import Measure from "react-measure"; +import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem"; +import { Utils } from "../../Utils"; @observer export class ContextMenu extends React.Component { @@ -116,10 +116,6 @@ export class ContextMenu extends React.Component { this._defaultItem = item; } - getItems() { - return this._items; - } - static readonly buffer = 20; get pageX() { const x = this._pageX; @@ -199,37 +195,25 @@ export class ContextMenu extends React.Component { return eles; }; - return flattenItems(this._items, name => [name]); + return flattenItems(this._items.slice(), name => [name]); } @computed get flatItems(): OriginalMenuProps[] { return this.filteredItems.filter(item => !Array.isArray(item)) as OriginalMenuProps[]; } - @computed get filteredViews() { - const createGroupHeader = (contents: any) => { - return ( -
    -
    {contents}
    -
    - ); - }; - const createItem = (item: ContextMenuProps, selected: boolean) => ; - let itemIndex = 0; - return this.filteredItems.map(value => { - if (Array.isArray(value)) { - return createGroupHeader(value.join(" -> ")); - } else { - return createItem(value, itemIndex++ === this.selectedIndex); - } - }); - } - @computed get menuItems() { if (!this._searchString) { return this._items.map((item, ind) => ); } - return this.filteredViews; + return this.filteredItems.map((value, index) => + Array.isArray(value) ? +
    +
    {value.join(" -> ")}
    +
    + : + + ); } @computed get itemsNeedSearch() { @@ -237,14 +221,8 @@ export class ContextMenu extends React.Component { } render() { - if (!this._display) { - return null; - } - const style = this._yRelativeToTop ? { left: this.pageX, top: this.pageY } : - { left: this.pageX, bottom: this.pageY }; - - const contents = ( - <> + return !this._display ? (null) : +
    {!this.itemsNeedSearch ? (null) : @@ -253,17 +231,7 @@ export class ContextMenu extends React.Component { } {this.menuItems} - - ); - return ( - { this._width = r.offset.width; this._height = r.offset.height; })}> - {({ measureRef }) => ( -
    - {contents} -
    - )} -
    - ); +
    ; } @action diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 25d00f701..c136de1c3 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -28,11 +28,10 @@ export type ContextMenuProps = OriginalMenuProps | SubmenuProps; export class ContextMenuItem extends React.Component { @observable private _items: Array = []; @observable private overItem = false; - @observable private subRef = React.createRef(); - constructor(props: ContextMenuProps | SubmenuProps) { - super(props); - if ((this.props as SubmenuProps).subitems) { + componentDidMount() { + this._items.length = 0; + if ((this.props as SubmenuProps)?.subitems) { (this.props as SubmenuProps).subitems?.forEach(i => this._items.push(i)); } } @@ -78,9 +77,6 @@ export class ContextMenuItem extends React.Component diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5d2adc321..8d4fd376f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1251,7 +1251,7 @@ export class DocumentView extends React.Component { @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } - @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } + @computed get centeringY() { return this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 600730d87..d3dee3c89 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -862,6 +862,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (nh) this.layoutDoc._nativeHeight = scrollHeight; } + @computed get contentScaling() { return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;} componentDidMount() { !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._cachedLinks = DocListCast(this.Document.links); @@ -875,7 +876,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { - autoHeight && this.props.setHeight?.((this.props.scaling?.() || 1) * (marginsHeight + Math.max(sidebarHeight, textHeight))); + autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight))); }, { fireImmediately: true }); this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { -- cgit v1.2.3-70-g09d2 From 0370192be9c6c723f198fbf28d2a63ceef0e70d4 Mon Sep 17 00:00:00 2001 From: Michael Foiani Date: Thu, 23 Jun 2022 12:36:45 -0400 Subject: small bug fixes and debugging --- src/client/util/ReplayMovements.ts | 9 ++++++--- src/client/views/nodes/VideoBox.tsx | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index 6a5746c0b..86bc4c5de 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -122,8 +122,10 @@ export class ReplayMovements { console.error(`docIdtoDoc did not contain docId ${docId}`) return undefined; } + // console.log('openTab', docId, doc); CollectionDockingView.AddSplit(doc, 'right'); - const docView = DocumentManager.Instance.getDocumentViewById(docId); + const docView = DocumentManager.Instance.getDocumentView(doc); + // BUG - this returns undefined if the doc is already open return docView?.ComponentView as CollectionFreeFormView; } @@ -135,7 +137,7 @@ export class ReplayMovements { document.Document._panY = panY; } - getFirstMovements = (movements: Movement[], timeViewed: number): Map => { + getFirstMovements = (movements: Movement[]): Map => { if (movements === null) return new Map(); // generate a set of all unique docIds const docIdtoFirstMove = new Map(); @@ -172,7 +174,7 @@ export class ReplayMovements { if (isClosed) this.openTab(firstMovement.docId, docIdtoDoc); // for the open tabs, set it to the first move - const docIdtoFirstMove = this.getFirstMovements(filteredMovements, timeViewed); + const docIdtoFirstMove = this.getFirstMovements(filteredMovements); for (const [docId, firstMove] of docIdtoFirstMove) { const colFFView = this.getCollectionFFView(docId); if (colFFView) this.zoomAndPan(firstMove, colFFView); @@ -192,6 +194,7 @@ export class ReplayMovements { } else { // tab wasn't open - open it and play the movement const openedColFFView = this.openTab(movement.docId, docIdtoDoc); + console.log('openedColFFView', openedColFFView); openedColFFView && this.zoomAndPan(movement, openedColFFView); } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index b18d3ad1a..680c5f2a9 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -132,7 +132,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent Date: Fri, 24 Jun 2022 09:30:40 -0400 Subject: fixed highlighting readonly mode. simplified away some async stuff on load. --- src/client/DocServer.ts | 2 +- src/client/util/CurrentUserUtils.ts | 38 +++++++++++++++++-------------------- src/client/util/SettingsManager.tsx | 5 ++--- 3 files changed, 20 insertions(+), 25 deletions(-) (limited to 'src/client/util') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index dbc4783d8..c9a30b8e3 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -180,7 +180,7 @@ export namespace DocServer { _isReadOnly = true; _CreateField = field => _cache[field[Id]] = field; _UpdateField = emptyFunction; - _RespondToUpdate = emptyFunction; + _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB } } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 28d20c56b..e5e12a7e5 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -807,16 +807,13 @@ export class CurrentUserUtils { } /// The database of all links on all documents - static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { - if (doc.myLinkDatabase === undefined) { - let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); - if (!linkDocs) { - linkDocs = new Doc(linkDatabaseId, true); - (linkDocs as Doc).title = "LINK DATABASE: " + Doc.CurrentUserEmail; - (linkDocs as Doc).author = Doc.CurrentUserEmail; - (linkDocs as Doc).data = new List([]); - (linkDocs as Doc)["acl-Public"] = SharingPermissions.Augment; - } + static setupLinkDocs(doc: Doc, linkDatabaseId: string) { + if (!(Docs.newAccount ? undefined : DocCast(doc.myLinkDatabase))) { + const linkDocs = new Doc(linkDatabaseId, true); + linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail; + linkDocs.author = Doc.CurrentUserEmail; + linkDocs.data = new List([]); + linkDocs["acl-Public"] = SharingPermissions.Augment; doc.myLinkDatabase = new PrefetchProxy(linkDocs); } } @@ -825,14 +822,12 @@ export class CurrentUserUtils { // A user's sharing document is where all documents that are shared to that user are placed. // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents - static async setupSharedDocs(doc: Doc, sharingDocumentId: string) { + static setupSharedDocs(doc: Doc, sharingDocumentId: string) { const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}"; - const sharedScripts = { - treeViewChildDoubleClick: dblClkScript, - } + const sharedScripts = { treeViewChildDoubleClick: dblClkScript, } const sharedDocOpts:DocumentOptions = { title: "My Shared Docs", userColor: "rgb(202, 202, 202)", @@ -847,8 +842,7 @@ export class CurrentUserUtils { explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" }; - await DocServer.GetRefField(sharingDocumentId + "outer"); - return this.AssignDocField(doc, "mySharedDocs", (opts) => Docs.Create.TreeDocument([], opts, sharingDocumentId + "outer", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); + this.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); } /// Import option on the left side button panel @@ -914,16 +908,18 @@ export class CurrentUserUtils { return doc.clickFuncs as Doc; } - static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { + + /// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be + /// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field + /// whether to revert to "default" values, or to leave them as the user/system last set them. + static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { this.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {}); - await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]), async () => { const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); }, { fireImmediately: true }); - // Document properties on load doc.system ?? (doc.system = true); doc.title ?? (doc.title = Doc.CurrentUserEmail); Doc.noviceMode ?? (Doc.noviceMode = true); @@ -945,8 +941,8 @@ export class CurrentUserUtils { doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; - await this.setupLinkDocs(doc, linkDatabaseId); - await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing + this.setupLinkDocs(doc, linkDatabaseId); + this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 22e33ab1e..080237649 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -67,12 +67,11 @@ export class SettingsManager extends React.Component<{}> { @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value); @undoBatch switchActiveBackgroundColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex)); @undoBatch switchUserColor = action((color: ColorState) => { Doc.SharingDoc().userColor = undefined; Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex); }); - @undoBatch - playgroundModeToggle = action(() => { + @undoBatch playgroundModeToggle = action(() => { this.playgroundMode = !this.playgroundMode; if (this.playgroundMode) { DocServer.Control.makeReadOnly(); - addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "pink !important" }); + addStyleSheetRule(SettingsManager._settingsStyle, "topbar-inner-container", { background: "red !important" }); } else DocServer.Control.makeEditable(); }); -- cgit v1.2.3-70-g09d2 From dccf5909f4a4bec35559b23a2f355ab4c7a94086 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 24 Jun 2022 19:03:00 -0400 Subject: fixed myImports pane to show imports. fixed saving/loading zip files of a collection. fixed errors related to NumT casting to number --- src/client/util/CurrentUserUtils.ts | 38 ++++++++++------------ src/client/views/collections/CollectionSubView.tsx | 6 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 32 +++++------------- src/fields/Doc.ts | 17 +++++++++- 4 files changed, 45 insertions(+), 48 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e5e12a7e5..d085c5f5e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -858,7 +858,8 @@ export class CurrentUserUtils { const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true }; - return this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); + this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); + return myImports; } static setupClickEditorTemplates(doc: Doc) { @@ -1065,29 +1066,24 @@ export class CurrentUserUtils { input.onchange = async _e => { const upload = Utils.prepend("/uploadDoc"); const formData = new FormData(); - const file = input.files && input.files[0]; + const file = input.files?.[0]; if (file?.type === 'application/zip') { - formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json !== "error") { - const doc = Docs.newAccount ? undefined : await DocServer.GetRefField(json); - if (doc instanceof Doc) { - setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => - docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added. - } - } + const doc = await Doc.importDocument(file); + // NOT USING SOLR, so need to replace this with something else // if (doc instanceof Doc) { + // setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => + // docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added. + // } + const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null); + doc instanceof Doc && list?.splice(0, 0, doc); } else if (input.files && input.files.length !== 0) { const disposer = OverlayView.ShowSpinner(); - DocListCastAsync(CurrentUserUtils.MyImports.data).then(async list => { - const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); - if (results.length !== input.files?.length) { - alert("Error uploading files - possibly due to unsupported file types"); - } - list?.splice(0, 0, ...results); - disposer(); - }); + const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); + if (results.length !== input.files?.length) { + alert("Error uploading files - possibly due to unsupported file types"); + } + const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null); + list?.splice(0, 0, ...results); + disposer(); } else { console.log("No file selected"); } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 17fdba764..03450b798 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -274,7 +274,7 @@ export function CollectionSubView(moreProps?: X) { if (docid) { // prosemirror text containing link to dash document DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + if (options.x || options.y) { f.x = options.x as number; f.y = options.y as number; } // should be in CollectionFreeFormView (f instanceof Doc) && addDocument(f); } }); @@ -311,7 +311,7 @@ export function CollectionSubView(moreProps?: X) { const docid = text.replace(Doc.globalServerPath(), "").split("?")[0]; DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + if (options.x || options.y) { f.x = options.x as number; f.y = options.y as number; } // should be in CollectionFreeFormView (f instanceof Doc) && addDocument(f); } }); @@ -445,7 +445,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 as number, options.y as number)!,); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 542b1fce1..3c2047db7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1660,8 +1660,9 @@ export class CollectionFreeFormView extends CollectionSubView Doc.Zip(this.props.Document) }); - moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) }); + moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) }); } !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); } @@ -1670,28 +1671,13 @@ export class CollectionFreeFormView extends CollectionSubView { - const upload = Utils.prepend("/uploadDoc"); - const formData = new FormData(); - const file = input.files && input.files[0]; - if (file) { - formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); - const json = await response.json(); - if (json !== "error") { - const doc = await DocServer.GetRefField(json); - if (doc instanceof Doc) { - const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); - doc.x = xx, doc.y = yy; - this.props.addDocument?.(doc); - setTimeout(() => - SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { - docs.docs.forEach(d => LinkManager.Instance.addLink(d)); - }), 2000); // need to give solr some time to update so that this query will find any link docs we've added. - } - } - } + input.onchange = _e => { + input.files && Doc.importDocument(input.files[0]).then(doc => { + if (doc instanceof Doc) { + const [xx, yy] = this.getTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument?.(doc);} + }); }; input.click(); } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 981514b25..4fe6eb1e7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -658,7 +658,7 @@ export namespace Doc { const zip = new JSZip(); - zip.file(doc.title + ".json", docString); + zip.file("doc.json", docString); // // Generate a directory within the Zip file structure // var img = zip.folder("images"); @@ -1289,6 +1289,21 @@ export namespace Doc { } } + export async function importDocument(file:File) { + const upload = Utils.prepend("/uploadDoc"); + const formData = new FormData(); + if (file) { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + return doc; + } + } + return undefined; + } export namespace Get { -- cgit v1.2.3-70-g09d2 From 550b5e93097e0708199a9cf88714754d197b3b97 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Jun 2022 12:48:54 -0400 Subject: fixed initialization issues with creating user documents unnecessarily. fixed updating doc icon templates to apply to documents already created. --- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 64 ++++++----- src/client/util/LinkManager.ts | 123 +++++++++++---------- src/client/views/Main.tsx | 3 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/fields/Doc.ts | 2 +- 6 files changed, 104 insertions(+), 100 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 84e6258ac..3780df5b9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -105,7 +105,6 @@ export class DocumentOptions { userColor?: STRt = new StrInfo("color associated with a Dash user (seen in header fields of shared documents)"); color?: STRt = new StrInfo("foreground color data doc"); backgroundColor?: STRt = new StrInfo("background color for data doc"); - _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)"); _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents"); _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title"); _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes"); @@ -328,6 +327,7 @@ export class DocumentOptions { text?: string; textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded + iconTemplate?: string; // name of icon template style selectedIndex?: number; // which item in a linear view has been selected using the "thumb doc" ui clipboard?: Doc; searchQuery?: string; // for quersyBox @@ -1288,9 +1288,7 @@ export namespace DocUtils { const batch = UndoManager.StartBatch("makeCustomViewClicked"); runInAction(() => { doc.layoutKey = "layout_" + templateSignature; - if (doc[doc.layoutKey] === undefined) { - createCustomView(doc, creator, templateSignature, docLayoutTemplate); - } + createCustomView(doc, creator, templateSignature, docLayoutTemplate); }); batch.end(); return doc; @@ -1318,7 +1316,9 @@ export namespace DocUtils { const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; if (docLayoutTemplate) { - Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); + if (docLayoutTemplate !== doc[customName]) { + Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); + } } else { let fieldTemplate: Opt; if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 5c77041e0..f585277d8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -205,29 +205,37 @@ export class CurrentUserUtils { const reqdOpts = { title: "icon templates", _height: 75, system: true }; const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); - const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: (opts:DocumentOptions) => Doc) => { + const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => { const iconFieldName = "icon" + (type ? "_" + type : ""); - return DocCast(templateIconsDoc[iconFieldName] ?? (templateIconsDoc[iconFieldName] = MakeTemplate(iconTemplate({onClick:deiconifyScript(), system: true}), true, iconFieldName, templateField))) ; + const curIcon = DocCast(templateIconsDoc[iconFieldName]); + let creator = labelBox; + switch (opts.iconTemplate) { + case DocumentType.IMG : creator = imageBox; break; + case DocumentType.FONTICON: creator = fontBox; break; + } + const allopts = {system: true, ...opts}; + return this.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? + this.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), + {onClick:"deiconifyView(documentView)"}); }; - const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); - const labelBox = (opts: object) => Docs.Create.LabelDocument({ + const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts }); - const imageBox = (url: Opt, opts: object) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", ...opts }); - const fontBox = (opts:DocumentOptions) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); + const imageBox = (opts: DocumentOptions, url?:string) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", ...opts }); + const fontBox = (opts:DocumentOptions, data?:string) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); const iconTemplates = [ - makeIconTemplate(undefined, "title", (opts) => labelBox({ ...opts, _backgroundColor: "dimgray" })), - makeIconTemplate(DocumentType.AUDIO, "title", (opts) => labelBox({ ...opts, _backgroundColor: "lightgreen" })), - makeIconTemplate(DocumentType.PDF, "title", (opts) => labelBox({ ...opts, _backgroundColor: "pink" })), - makeIconTemplate(DocumentType.WEB, "title", (opts) => labelBox({...opts, _backgroundColor: "brown" })), - makeIconTemplate(DocumentType.RTF, "text", (opts) => labelBox({ ...opts, _showTitle: "creationDate" })), - makeIconTemplate(DocumentType.IMG, "data", (opts) => imageBox("", { ...opts, _height: undefined, })), - makeIconTemplate(DocumentType.COL, "icon", (opts) => imageBox(undefined, opts)), - makeIconTemplate(DocumentType.VID, "icon", (opts) => imageBox(undefined, opts)), - makeIconTemplate(DocumentType.BUTTON, "data", (opts) => fontBox(opts)), + makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}), + makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}), + makeIconTemplate(DocumentType.PDF, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "pink"}), + makeIconTemplate(DocumentType.WEB, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "brown"}), + makeIconTemplate(DocumentType.RTF, "text", { iconTemplate:DocumentType.LABEL, _showTitle: "creationDate"}), + makeIconTemplate(DocumentType.IMG, "data", { iconTemplate:DocumentType.IMG, _height: undefined}), + makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), + makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}), + makeIconTemplate(DocumentType.BUTTON,"data", { iconTemplate:DocumentType.FONTICON}), //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now - makeIconTemplate("transcription" as any, "transcription", (opts) => labelBox({ ...opts, _backgroundColor: "orange" })), - // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) + makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }), + //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts)) ].filter(d => d).map(d => d!); this.AssignOpts(DocCast(doc[field]), {}, iconTemplates); } @@ -779,23 +787,23 @@ export class CurrentUserUtils { /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const ctxtMenuBtnsDoc = DocCast(doc[field]); + const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; + const ctxtMenuBtnsDoc = this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); if (!params.subMenu) { return this.setupContextMenuButton(params, menuBtnDoc); } else { - const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, title:"submenu", + const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, linearViewSubMenu: true, linearViewExpandable: true, }; return this.AssignScripts(this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? - this.linearButtonList(reqdSubMenuOpts, params.subMenu.map(sub => + (ctxtMenuBtnsDoc[StrCast(params.title)]= this.linearButtonList(reqdSubMenuOpts, params.subMenu.map(sub => this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) - )), undefined, params.funcs); + ))), undefined, params.funcs); } }); - const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; - return this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, ctxtMenuBtns); + return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); } /// collection of documents rendered in the overlay layer above all tabs and other UI @@ -957,12 +965,13 @@ export class CurrentUserUtils { this.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {}); this.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents - setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); if (doc.activeDashboard instanceof Doc) { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } + new LinkManager(); + DocServer.UPDATE_SERVER_CACHE(); return doc; } static setupFieldInfos(doc:Doc, field="fieldInfos") { @@ -1009,12 +1018,7 @@ export class CurrentUserUtils { await Docs.Prototypes.initialize(); const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; Docs.newAccount &&(userDoc.activePage = "home"); - const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); - (await DocListCastAsync(Doc.LinkDBDoc()?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager - const a1 = await Cast(link?.anchor1, Doc, null); - const a2 = await Cast(link?.anchor2, Doc, null); - }); - return updated; + return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); }); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 2100b1195..d51cd350d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,7 +1,6 @@ -import { validationResult } from "express-validator/check"; import { action, observable, observe } from "mobx"; import { computedFn } from "mobx-utils"; -import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc"; +import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../fields/Doc"; import { List } from "../../fields/List"; import { ProxyField } from "../../fields/Proxy"; import { BoolCast, Cast, StrCast } from "../../fields/Types"; @@ -35,72 +34,74 @@ export class LinkManager { constructor() { LinkManager._instance = this; this.createLinkrelationshipLists(); - setTimeout(() => { - LinkManager.userLinkDBs = []; - const addLinkToDoc = (link: Doc) => { - const a1Prom = link?.anchor1; - const a2Prom = link?.anchor2; - Promise.all([a1Prom, a2Prom]).then((all) => { - const a1 = all[0]; - const a2 = all[1]; - const a1ProtoProm = (link?.anchor1 as Doc)?.proto; - const a2ProtoProm = (link?.anchor2 as Doc)?.proto; - Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => { - if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinksSym].add(link); - Doc.GetProto(a2)[DirectLinksSym].add(link); - Doc.GetProto(link)[DirectLinksSym].add(link); - } - })); - }); - }; - const remLinkFromDoc = (link: Doc) => { - const a1 = link?.anchor1; - const a2 = link?.anchor2; - Promise.all([a1, a2]).then(action(() => { + LinkManager.userLinkDBs = []; + const addLinkToDoc = (link: Doc) => { + const a1Prom = link?.anchor1; + const a2Prom = link?.anchor2; + Promise.all([a1Prom, a2Prom]).then((all) => { + const a1 = all[0]; + const a2 = all[1]; + const a1ProtoProm = (link?.anchor1 as Doc)?.proto; + const a2ProtoProm = (link?.anchor2 as Doc)?.proto; + Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => { if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinksSym].delete(link); - Doc.GetProto(a2)[DirectLinksSym].delete(link); - Doc.GetProto(link)[DirectLinksSym].delete(link); + Doc.GetProto(a1)[DirectLinksSym].add(link); + Doc.GetProto(a2)[DirectLinksSym].add(link); + Doc.GetProto(link)[DirectLinksSym].add(link); } })); - }; - const watchUserLinkDB = (userLinkDBDoc: Doc) => { - LinkManager.links.push(...DocListCast(userLinkDBDoc.data)); - const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields - observe(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) - switch (change.type as any) { - case "splice": - (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); - (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); - break; - case "update": //let oldValue = change.oldValue; - } - }, true); - observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link) - change => { - switch (change.type as any) { - case "update": - Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => { - const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length); - const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length); - - const added = newDocs?.filter(link => !(oldDocs || []).includes(link)); - const removed = oldDocs?.filter(link => !(newDocs || []).includes(link)); - added?.forEach((link: any) => addLinkToDoc(toRealField(link))); - removed?.forEach((link: any) => remLinkFromDoc(toRealField(link))); - }); - } - }, true); - }; - observe(LinkManager.userLinkDBs, change => { + }); + }; + const remLinkFromDoc = (link: Doc) => { + const a1 = link?.anchor1; + const a2 = link?.anchor2; + Promise.all([a1, a2]).then(action(() => { + if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { + Doc.GetProto(a1)[DirectLinksSym].delete(link); + Doc.GetProto(a2)[DirectLinksSym].delete(link); + Doc.GetProto(link)[DirectLinksSym].delete(link); + } + })); + }; + const watchUserLinkDB = (userLinkDBDoc: Doc) => { + LinkManager.links.push(...DocListCast(userLinkDBDoc.data)); + const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields + observe(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) switch (change.type as any) { - case "splice": (change as any).added.forEach(watchUserLinkDB); break; + case "splice": + (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); + (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); + break; case "update": //let oldValue = change.oldValue; } }, true); - LinkManager.addLinkDB(Doc.LinkDBDoc()); - }); + observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link) + change => { + switch (change.type as any) { + case "update": + Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => { + const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length); + const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length); + + const added = newDocs?.filter(link => !(oldDocs || []).includes(link)); + const removed = oldDocs?.filter(link => !(newDocs || []).includes(link)); + added?.forEach((link: any) => addLinkToDoc(toRealField(link))); + removed?.forEach((link: any) => remLinkFromDoc(toRealField(link))); + }); + } + }, true); + }; + observe(LinkManager.userLinkDBs, change => { + switch (change.type as any) { + case "splice": (change as any).added.forEach(watchUserLinkDB); break; + case "update": //let oldValue = change.oldValue; + } + }, true); + LinkManager.addLinkDB(Doc.LinkDBDoc()); + DocListCastAsync(Doc.LinkDBDoc()?.data).then(dblist => dblist?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager + const a1 = await Cast(link?.anchor1, Doc, null); + const a2 = await Cast(link?.anchor2, Doc, null); + })); } diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 49c2dcf34..17841c055 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -10,7 +10,6 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { LinkManager } from "../util/LinkManager"; import { RecordingApi } from "../util/RecordingApi"; import { CollectionView } from "./collections/CollectionView"; -import { DashboardView } from './DashboardView'; import { MainView } from "./MainView"; AssignAllExtensions(); @@ -24,6 +23,7 @@ AssignAllExtensions(); await CurrentUserUtils.loadUserDocument(info.id); } else { await Docs.Prototypes.initialize(); + new LinkManager(); } document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { @@ -37,7 +37,6 @@ AssignAllExtensions(); d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000)); const expires = "expires=" + d.toUTCString(); document.cookie = `loadtime=${loading};${expires};path=/`; - new LinkManager(); new RecordingApi; ReactDOM.render(, document.getElementById('root')); })(); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 081a1a924..17f1106d0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -517,7 +517,7 @@ export class MarqueeView extends React.Component Date: Mon, 27 Jun 2022 12:56:18 -0400 Subject: restored removeDashboard --- src/client/util/CurrentUserUtils.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f585277d8..d98eddb2e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1132,6 +1132,13 @@ export class CurrentUserUtils { CurrentUserUtils.ActiveDashboard = undefined; } + public static removeDashboard = async (dashboard:Doc) => { + const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); + if (dashboards && dashboards.length > 1) { + if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!); + Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); + } + } public static createNewDashboard = (id?: string, name?: string) => { const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true); const dashboards = CurrentUserUtils.MyDashboards; @@ -1223,14 +1230,7 @@ ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.creat ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); -ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { - const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); - if (dashboards && dashboards.length > 1) { - if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!); - Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); - } -}, - "Remove Dashboard from Dashboards"); +ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); -- cgit v1.2.3-70-g09d2 From 0b9f5fb60d5ee3b589e2e4628c5f1a737757492d Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Jun 2022 13:04:01 -0400 Subject: from last --- src/client/util/CurrentUserUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d98eddb2e..6e115ea3c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1029,6 +1029,7 @@ export class CurrentUserUtils { public static _urlState: HistoryUtil.DocUrl; public static openDashboard = (doc: Doc, fromHistory = false) => { + if (!doc) return; CurrentUserUtils.MainDocId = doc[Id]; Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); @@ -1134,9 +1135,10 @@ export class CurrentUserUtils { public static removeDashboard = async (dashboard:Doc) => { const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); - if (dashboards && dashboards.length > 1) { + if (dashboards?.length) { if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!); Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); + if (!dashboards.length) CurrentUserUtils.ActivePage = "home"; } } public static createNewDashboard = (id?: string, name?: string) => { -- cgit v1.2.3-70-g09d2 From b1eb13fc6264b1272daba824ff0e4cd2bca8a6fa Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 28 Jun 2022 12:23:44 -0400 Subject: cleaned up some more currentUserUtils - fixed some issues with prev/next key frame appearing. --- src/client/documents/Documents.ts | 29 +++++- src/client/util/CurrentUserUtils.ts | 111 ++++----------------- .../views/collections/CollectionDockingView.tsx | 37 +++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 19 +++- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/topbar/TopBar.tsx | 3 +- src/fields/Doc.ts | 25 +---- 7 files changed, 97 insertions(+), 129 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3780df5b9..4690d856d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -13,7 +13,7 @@ import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; -import { SharingPermissions } from "../../fields/util"; +import { inheritParentAcls, SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; import { aggregateBounds, OmitKeys, Utils } from "../../Utils"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; @@ -1266,7 +1266,7 @@ export namespace DocUtils { const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data)[0]?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ description: ":" + StrCast(dragDoc.title).replace("Untitled ",""), event: undoBatch((args: { x: number, y: number }) => { - const newDoc = Doc.copyDragFactory(dragDoc); + const newDoc = DocUtils.copyDragFactory(dragDoc); if (newDoc) { newDoc.author = Doc.CurrentUserEmail; newDoc.x = x; @@ -1479,9 +1479,34 @@ export namespace DocUtils { } return generatedDocuments; } + + // copies the specified drag factory document + export function copyDragFactory(dragFactory: Doc) { + if (!dragFactory) return undefined; + const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); + ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc)); + if (ndoc && dragFactory["dragFactory-count"] !== undefined) { + dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; + Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); + } + + if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); + + return ndoc; + } + export function delegateDragFactory(dragFactory: Doc) { + const ndoc = Doc.MakeDelegateWithProto(dragFactory); + if (ndoc && dragFactory["dragFactory-count"] !== undefined) { + dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; + Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(); + } + return ndoc; + } } ScriptingGlobals.add("Docs", Docs); +ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return DocUtils.copyDragFactory(dragFactory); }); +ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return DocUtils.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); ScriptingGlobals.add(function generateLinkTitle(self: Doc) { const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : ""; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 6e115ea3c..2a0702a58 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -755,8 +755,8 @@ export class CurrentUserUtils { CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid]), title: "Perspective", toolTip: "View", width: 100,btnType: ButtonType.DropdownList,ignoreClick: true, scripts: { script: 'setView(value, _readOnly_)'}}, - { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, - { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}}, + { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setBackgroundColor(value, _readOnly_)'},funcs: {hidden: '!selectedDocumentType()'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setHeaderColor(value, _readOnly_)'}, funcs: {hidden: '!selectedDocumentType()'}}, { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, scripts: { onClick: 'toggleOverlay(_readOnly_)'}, funcs: {hidden: '!selectedDocumentType(undefined, "freeform", true)'}}, // Only when floating document is selected in freeform @@ -1028,8 +1028,10 @@ export class CurrentUserUtils { public static _urlState: HistoryUtil.DocUrl; - public static openDashboard = (doc: Doc, fromHistory = false) => { - if (!doc) return; + /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). + /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) + public static openDashboard = (doc: Doc|undefined, fromHistory = false) => { + if (!doc) return false; CurrentUserUtils.MainDocId = doc[Id]; Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); @@ -1097,46 +1099,15 @@ export class CurrentUserUtils { }; input.click(); } + + public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(CurrentUserUtils.ActiveDashboard); } - public static CaptureDashboardThumbnail() { - const activeDashboard = CurrentUserUtils.ActiveDashboard; - const docView = CollectionDockingView.Instance.props.DocumentView?.(); - const content = docView?.ContentDiv; - if (docView && content && activeDashboard) { - const _width = Number(getComputedStyle(content).width.replace("px","")); - const _height = Number(getComputedStyle(content).height.replace("px","")); - return CollectionFreeFormView.UpdateIcon( - docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), - content, - _width, _height, - _width, _height, 0, 1, true, docView.layoutDoc[Id] + "-icon", - (iconFile, _nativeWidth, _nativeHeight) => { - const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); - const proto = Cast(img.proto, Doc, null)!; - proto["data-nativeWidth"] = _width; - proto["data-nativeHeight"] = _height; - Doc.GetProto(activeDashboard).thumb = img; - }); - } - - } - - public static async snapshotDashboard() { - if (CurrentUserUtils.ActiveDashboard) { - const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); - CurrentUserUtils.openDashboard(copy); - } - } - - public static closeActiveDashboard = () => { - CurrentUserUtils.ActiveDashboard = undefined; - } + public static closeActiveDashboard = () => { CurrentUserUtils.ActiveDashboard = undefined; } public static removeDashboard = async (dashboard:Doc) => { const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); if (dashboards?.length) { - if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!); + if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)); Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); if (!dashboards.length) CurrentUserUtils.ActivePage = "home"; } @@ -1214,14 +1185,6 @@ export class CurrentUserUtils { public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } } -ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { - const copy = Doc.copyDragFactory(dragFactory); - if (copy) { - CollectionDockingView.AddSplit(copy, "right"); - const view = DocumentManager.Instance.getFirstDocumentView(copy); - view && SelectionManager.SelectView(view, false); - } -}); ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); @@ -1232,52 +1195,14 @@ ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.creat ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); -ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); -ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { - const dashboardAlias = Doc.MakeAlias(dashboard); - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); - CurrentUserUtils.openDashboard(dashboardAlias); -}, - "adds Dashboard to set of Dashboards"); - -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); - selected = parentDoc; - } - if (selected && docType && selected.type === docType) return true; - else if (selected && colType && selected.viewType === colType) return true; - else if (selected && !colType && !docType) return true; - else return false; +ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); +ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { CurrentUserUtils.openDashboard( Doc.MakeAlias(dashboard)); }, "adds Dashboard to set of Dashboards"); +ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) { + let selected = (sel => checkContext ? DocCast(sel?.context) : sel)(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); + return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true; }); ScriptingGlobals.add(function makeTopLevelFolder() { - const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); - TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined }; - return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); -}); -ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { - if (readOnly) return; - const sel = SelectionManager.Views()[0]; - const col = (sel.ComponentView as CollectionFreeFormView); - const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); - if (currentFrame === undefined) { - sel.props.Document._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0); - } - CollectionFreeFormDocumentView.updateKeyframe(col.childDocs, currentFrame || 0); - sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); - sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame)); -}); -ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { - if (readOnly) return; - const sel = SelectionManager.Views()[0]; - const col = (sel.ComponentView as CollectionFreeFormView); - const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); - if (currentFrame === undefined) { - sel.props.Document._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0); - } - CollectionFreeFormDocumentView.gotoKeyframe(col.childDocs.slice()); - sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); + TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; + const opts = { title: "Untitled folder", _stayInCollection: true, isFolder: true }; + return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); }); \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 07fcd6a7d..0830b6fdf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -4,12 +4,12 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from "mo import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import * as GoldenLayout from "../../../client/goldenLayout"; -import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { ImageField } from '../../../fields/URLField'; import { inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from "../../DocServer"; @@ -19,14 +19,15 @@ import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DragManager } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; +import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { LightboxView } from '../LightboxView'; import "./CollectionDockingView.scss"; +import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require("react"); -import { SelectionManager } from '../../util/SelectionManager'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -373,13 +374,34 @@ export class CollectionDockingView extends CollectionSubView() { } } - public static async Copy(doc: Doc, clone = false) { + public CaptureThumbnail() { + const content = this.props.DocumentView?.()?.ContentDiv; + if (content) { + const _width = Number(getComputedStyle(content).width.replace("px","")); + const _height = Number(getComputedStyle(content).height.replace("px","")); + return CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + content, + _width, _height, + _width, _height, 0, 1, true, this.layoutDoc[Id] + "-icon", + (iconFile, _nativeWidth, _nativeHeight) => { + const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); + const proto = Cast(img.proto, Doc, null)!; + proto["data-nativeWidth"] = _width; + proto["data-nativeHeight"] = _height; + this.dataDoc.thumb = img; + }); + } + + } + public static async TakeSnapshot(doc: Doc|undefined, clone = false) { + if (!doc) return undefined; let json = StrCast(doc.dockingConfig); if (clone) { - const cloned = (await Doc.MakeClone(doc)); + const cloned = await Doc.MakeClone(doc); Array.from(cloned.map.entries()).map(entry => json = json.replace(entry[0], entry[1][Id])); Doc.GetProto(cloned.clone).dockingConfig = json; - return cloned.clone; + return CurrentUserUtils.openDashboard(cloned.clone); } const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); const origtabids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || []; @@ -395,7 +417,8 @@ export class CollectionDockingView extends CollectionSubView() { json = json.replace(origtab[Id], newtab[Id]); return newtab; }); - return Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); + const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); + return CurrentUserUtils.openDashboard(await copy); } @action diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3c2047db7..b9da4faa4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -153,6 +153,21 @@ export class CollectionFreeFormView extends CollectionSubView { + const currentFrame = Cast(this.Document._currentFrame, "number", null); + if (currentFrame === undefined) { + this.Document._currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); + } + if (back) { + CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); + this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1); + } else { + CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); + this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1); + this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); + } + } @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set; getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); @@ -2086,4 +2101,6 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY }); Doc.linkFollowHighlight(dv?.props.Document, false); } -ScriptingGlobals.add(CollectionBrowseClick); \ No newline at end of file +ScriptingGlobals.add(CollectionBrowseClick); +ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); }); +ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 17f1106d0..ab8a34d5a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -156,7 +156,7 @@ export class MarqueeView extends React.Component { - CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => { + CollectionDockingView.Instance.CaptureThumbnail()?.then(() => { CurrentUserUtils.ActivePage = "home"; CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use }); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index a5d952176..b30ca644d 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -23,7 +23,7 @@ import { listSpec } from "./Schema"; import { ComputedField, ScriptField } from "./ScriptField"; import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField"; -import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; +import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; import JSZip = require("jszip"); export namespace Field { @@ -1242,27 +1242,6 @@ export namespace Doc { return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; } - export function copyDragFactory(dragFactory: Doc) { - const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); - ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc)); - if (ndoc && dragFactory["dragFactory-count"] !== undefined) { - dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; - Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); - } - - if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); - - return ndoc; - } - export function delegateDragFactory(dragFactory: Doc) { - const ndoc = Doc.MakeDelegateWithProto(dragFactory); - if (ndoc && dragFactory["dragFactory-count"] !== undefined) { - dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; - Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(); - } - return ndoc; - } - export function toIcon(doc?: Doc, isOpen?: boolean) { switch (StrCast(doc?.type)) { case DocumentType.IMG: return "image"; @@ -1454,8 +1433,6 @@ ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); }); ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); ScriptingGlobals.add(function getAlias(doc: any) { return Doc.MakeAlias(doc); }); ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); }); -ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); -- cgit v1.2.3-70-g09d2 From aa979bcd302e48982707fb6f12403a48a721f147 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 29 Jun 2022 19:17:00 -0400 Subject: got basic file upload to work --- src/client/documents/Documents.ts | 12 +++++++++--- src/client/util/CurrentUserUtils.ts | 2 +- src/fields/URLField.ts | 1 + src/server/ApiManagers/UploadManager.ts | 1 + src/server/DashUploadUtils.ts | 18 +++++++++++++++++- src/server/DataVizUtils.ts | 13 +++++++++++++ 6 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 src/server/DataVizUtils.ts (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 59a1d41a8..b9d879c55 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -12,7 +12,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; +import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; import { aggregateBounds, OmitKeys, Utils } from "../../Utils"; @@ -940,8 +940,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } - export function DataVizDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), undefined, { title: "Data Viz", ...options }); + export function DataVizDocument(url: string, options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: "Data Viz", ...options }); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { @@ -1253,6 +1253,11 @@ export namespace DocUtils { if (!options._width) options._width = 400; if (!options._height) options._height = (options._width as number) * 1200 / 927; } + if (type.indexOf("csv") !== -1) { + ctor = Docs.Create.DataVizDocument; + if (!options._width) options._width = 400; + if (!options._height) options._height = (options._width as number) * 1200 / 927; + } //TODO:al+glr // if (type.indexOf("map") !== -1) { // ctor = Docs.Create.MapDocument; @@ -1278,6 +1283,7 @@ export namespace DocUtils { ctor = Docs.Create.WebDocument; options = { ...options, _width: 400, _height: 512, title: path, }; } + return ctor ? ctor(path, options) : undefined; } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 52fbda9a9..40268693e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -294,7 +294,7 @@ export class CurrentUserUtils { {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, - {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, + // {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}}, {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }}, diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 3e7542e46..36dd56a1a 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -59,6 +59,7 @@ export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short @scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } @scriptingGlobal @Deserializable("web") export class WebField extends URLField { } @scriptingGlobal @Deserializable("map") export class MapField extends URLField { } +@scriptingGlobal @Deserializable("csv") export class CsvField extends URLField { } @scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { } @scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index e7b7056a1..04a11f410 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -24,6 +24,7 @@ export enum Directory { text = "text", pdf_thumbnails = "pdf_thumbnails", audio = "audio", + csv = "csv", } export function serverPathToFile(directory: Directory, filename: string) { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 552ab57a5..5f46bcc88 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -17,6 +17,7 @@ import { resolvedServerUrl } from "./server_Initialization"; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); import formidable = require('formidable'); +import { csvParser } from './DataVizUtils'; const { exec } = require("child_process"); const parse = require('pdf-parse'); const ffmpeg = require("fluent-ffmpeg"); @@ -85,7 +86,7 @@ export namespace DashUploadUtils { const category = types[0]; let format = `.${types[1]}`; console.log(green(`Processing upload of file (${name}) and format (${format}) with upload type (${type}) in category (${category}).`)); - + switch (category) { case "image": if (imageFormats.includes(format)) { @@ -116,6 +117,11 @@ export namespace DashUploadUtils { if (audioFormats.includes(format)) { return UploadAudio(file, format); } + case "text": + if (types[1] == "csv") { + return UploadCsv(file); + } + } console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`)); @@ -135,6 +141,16 @@ export namespace DashUploadUtils { return MoveParsedFile(file, Directory.pdfs, undefined, result.text); } + async function UploadCsv(file: File) { + const { path: sourcePath } = file; + // read the file as a string + const data = readFileSync(sourcePath, 'utf8'); + // split the string into an array of lines + return MoveParsedFile(file, Directory.csv, undefined, data); + // console.log(csvParser(data)); + + } + const manualSuffixes = [".webm"]; async function UploadAudio(file: File, format: string) { diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts new file mode 100644 index 000000000..4fd0ca6ff --- /dev/null +++ b/src/server/DataVizUtils.ts @@ -0,0 +1,13 @@ +export function csvParser(csv: string) { + const lines = csv.split("\n"); + const headers = lines[0].split(","); + const data = lines.slice(1).map(line => { + const values = line.split(","); + const obj: any = {}; + for (let i = 0; i < headers.length; i++) { + obj[headers[i]] = values[i]; + } + return obj; + }); + return data; +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ea6e63648b21c46672b1b7cb1da0cbaa6857d0c1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Jun 2022 11:01:46 -0400 Subject: fixed up some more initialization --- src/client/util/CurrentUserUtils.ts | 37 +++++++--------------- src/client/util/DropConverter.ts | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 ++-- 3 files changed, 16 insertions(+), 29 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 2a0702a58..a39081d10 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -9,30 +9,25 @@ import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types"; -import { ImageField, nullAudio } from "../../fields/URLField"; +import { nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; import { TreeViewType } from "../views/collections/CollectionTreeView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; -import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; -import { DocumentView } from "../views/nodes/DocumentView"; import { OverlayView } from "../views/OverlayView"; -import { DocumentManager } from "./DocumentManager"; import { DragManager } from "./DragManager"; -import { makeTemplate, MakeTemplate } from "./DropConverter"; +import { MakeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; import { ScriptingGlobals } from "./ScriptingGlobals"; -import { SearchUtil } from "./SearchUtil"; import { SelectionManager } from "./SelectionManager"; import { ColorScheme } from "./SettingsManager"; import { SharingManager } from "./SharingManager"; @@ -149,11 +144,7 @@ export class CurrentUserUtils { } return templateBtn; }; - const makeTemp = (doc:Doc) => { - doc.isTemplateDoc = makeTemplate(doc); - return doc; - } - return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); + return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts); }); const reqdOpts:DocumentOptions = { @@ -176,11 +167,7 @@ export class CurrentUserUtils { const reqdNoteList = reqdTempOpts.map(opts => { const reqdOpts = {...opts, title: "text", system: true}; const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined; - const makeTemp = (doc:Doc, noteType?:string) => { - doc.isTemplateDoc = makeTemplate(doc, true, noteType??"Note"); - return doc; - } - return this.AssignOpts(noteType, reqdOpts) ?? makeTemp(Docs.Create.TextDocument("",reqdOpts), opts.noteType); + return this.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note"); }); const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, system: true }; @@ -280,8 +267,7 @@ export class CurrentUserUtils { // " " + // " " + // "
    "; - Doc.GetProto(header).isTemplateDoc = makeTemplate(Doc.GetProto(header), true, "headerView"); - Doc.GetProto(header).title = "Untitled Header"; + MakeTemplate(Doc.GetProto(header), true, "Untitled Header"); return header; } const emptyThings:{key:string, // the field name where the empty thing will be stored @@ -705,7 +691,7 @@ export class CurrentUserUtils { { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts: {script: '{ return setFontColor(value, _readOnly_); }'}}, { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, - { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick:'{ return toggleUnderline(_readOnly_);}'} }, + { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", scripts: {onClick: '{ return toggleUnderline(_readOnly_);}'} }, { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} }, { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} }, @@ -797,10 +783,11 @@ export class CurrentUserUtils { const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, linearViewSubMenu: true, linearViewExpandable: true, }; - return this.AssignScripts(this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? - (ctxtMenuBtnsDoc[StrCast(params.title)]= this.linearButtonList(reqdSubMenuOpts, params.subMenu.map(sub => - this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) - ))), undefined, params.funcs); + const items = params.subMenu?.map(sub => + this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) + ); + return this.AssignScripts( + this.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), undefined, params.funcs); } }); return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); @@ -985,7 +972,7 @@ export class CurrentUserUtils { switch (options.fieldType) { case "boolean": opts.fieldValues = new List(options.values as any); break; case "number": opts.fieldValues = new List(options.values as any); break; - case "Doc": opts.fieldValues = new List(options.values as any); break; + case Doc.name: opts.fieldValues = new List(options.values as any); break; default: opts.fieldValues = new List(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType } this.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 076afd3a0..256ab5c44 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -20,7 +20,7 @@ export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt = undefined): boolean { +function makeTemplate(doc: Doc, first: boolean = true, rename: Opt = undefined): boolean { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; if (layoutDoc.layout instanceof Doc) { // its already a template return true; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9ae604e9b..8e1698eba 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -29,7 +29,7 @@ import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from "../../../util/DragManager"; -import { makeTemplate } from '../../../util/DropConverter'; +import { MakeTemplate } from '../../../util/DropConverter'; import { LinkManager } from '../../../util/LinkManager'; import { SelectionManager } from "../../../util/SelectionManager"; import { SnappingManager } from '../../../util/SnappingManager'; @@ -682,7 +682,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!this.layoutDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); this.rootDoc.title = "text"; - this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); + MakeTemplate(this.rootDoc, true, title); } else if (!this.rootDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); this.rootDoc.title = "text"; @@ -691,7 +691,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.rootDoc.isTemplateDoc = false; this.rootDoc.isTemplateForField = ""; this.rootDoc.layoutKey = "layout"; - this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); + MakeTemplate(this.rootDoc, true, title); setTimeout(() => { this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template -- cgit v1.2.3-70-g09d2 From 06ce1d6cf0fb96bb45519c3128d2ab3033ddd2ae Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 30 Jun 2022 15:14:11 -0400 Subject: fixed errors merging with master --- src/client/util/DragManager.ts | 1 + src/client/views/collections/CollectionNoteTakingView.tsx | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 09b463c2f..b4c42a6f0 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -453,6 +453,7 @@ export namespace DragManager { SnappingManager.SetIsDragging(false); SnappingManager.clearSnapLines(); batch.end(); + docsBeingDragged = []; }); var startWindowDragTimer: any; const moveHandler = (e: PointerEvent) => { diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 9519e9aaa..eb81f6e5e 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -144,7 +144,7 @@ export class CollectionNoteTakingView extends CollectionSubView this.layoutDoc._autoHeight, - autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))))); } @@ -226,7 +226,6 @@ export class CollectionNoteTakingView extends CollectionSubView Number(getComputedStyle(r).height.replace("px", ""))))); if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { - this.props.setHeight(height); + this.props.setHeight?.(height); } } })); @@ -559,7 +557,6 @@ export class CollectionNoteTakingView extends CollectionSubView Date: Fri, 1 Jul 2022 12:23:28 -0700 Subject: readded removed docs --- src/client/util/RecordingApi.ts | 269 +++++++++++++++++++++++++++++++++++++ src/client/views/nodes/DataViz.tsx | 20 +++ 2 files changed, 289 insertions(+) create mode 100644 src/client/util/RecordingApi.ts create mode 100644 src/client/views/nodes/DataViz.tsx (limited to 'src/client/util') diff --git a/src/client/util/RecordingApi.ts b/src/client/util/RecordingApi.ts new file mode 100644 index 000000000..7bffb0379 --- /dev/null +++ b/src/client/util/RecordingApi.ts @@ -0,0 +1,269 @@ +import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; +import { IReactionDisposer, observable, reaction } from 'mobx'; +import { NumCast } from '../../fields/Types'; +import { Doc } from '../../fields/Doc'; +import { VideoBox } from '../views/nodes/VideoBox'; +import { scaleDiverging } from 'd3-scale'; +import { Transform } from './Transform'; + +type Movement = { + time: number; + panX: number; + panY: number; + scale: number; +}; + +type Presentation = { + movements: Array | null; + meta: Object; +}; + +export class RecordingApi { + private static NULL_PRESENTATION: Presentation = { + movements: null, + meta: {}, + }; + + // instance variables + private currentPresentation: Presentation; + private isRecording: boolean; + private absoluteStart: number; + + // create static instance and getter for global use + @observable static _instance: RecordingApi; + public static get Instance(): RecordingApi { + return RecordingApi._instance; + } + public constructor() { + // init the global instance + RecordingApi._instance = this; + + // init the instance variables + this.currentPresentation = RecordingApi.NULL_PRESENTATION; + this.isRecording = false; + this.absoluteStart = -1; + + // used for tracking movements in the view frame + this.disposeFunc = null; + this.recordingFFView = null; + + // for now, set playFFView + this.playFFView = null; + this.timers = null; + } + + // little helper :) + private get isInitPresenation(): boolean { + return this.currentPresentation.movements === null; + } + + public start = (meta?: Object): Error | undefined => { + // check if already init a presentation + if (!this.isInitPresenation) { + console.error('[recordingApi.ts] start() failed: current presentation data exists. please call clear() first.'); + return new Error('[recordingApi.ts] start()'); + } + + // update the presentation mode + Doc.UserDoc().presentationMode = 'recording'; + + // (1a) get start date for presenation + const startDate = new Date(); + // (1b) set start timestamp to absolute timestamp + this.absoluteStart = startDate.getTime(); + + // (2) assign meta content if it exists + this.currentPresentation.meta = meta || {}; + // (3) assign start date to currentPresenation + this.currentPresentation.movements = []; + // (4) set isRecording true to allow trackMovements + this.isRecording = true; + }; + + public clear = (): Error | Presentation => { + // TODO: maybe archive the data? + if (this.isRecording) { + console.error('[recordingApi.ts] clear() failed: currently recording presentation. call pause() first'); + return new Error('[recordingApi.ts] clear()'); + } + + // update the presentation mode + Doc.UserDoc().presentationMode = 'none'; + // set the previus recording view to the play view + this.playFFView = this.recordingFFView; + + const presCopy = { ...this.currentPresentation }; + + // clear presenation data + this.currentPresentation = RecordingApi.NULL_PRESENTATION; + // clear isRecording + this.isRecording = false; + // clear absoluteStart + this.absoluteStart = -1; + // clear the disposeFunc + this.removeRecordingFFView(); + + return presCopy; + }; + + public pause = (): Error | undefined => { + if (this.isInitPresenation) { + console.error('[recordingApi.ts] pause() failed: no presentation started. try calling init() first'); + return new Error('[recordingApi.ts] pause(): no presentation'); + } + // don't allow track movments + this.isRecording = false; + + // set adjust absoluteStart to add the time difference + const timestamp = new Date().getTime(); + this.absoluteStart = timestamp - this.absoluteStart; + }; + + public resume = () => { + this.isRecording = true; + // set absoluteStart to the difference in time + this.absoluteStart = new Date().getTime() - this.absoluteStart; + }; + + private trackMovements = (panX: number, panY: number, scale: number = 0): Error | undefined => { + // ensure we are recording + if (!this.isRecording) { + return new Error('[recordingApi.ts] trackMovements()'); + } + // check to see if the presetation is init + if (this.isInitPresenation) { + return new Error('[recordingApi.ts] trackMovements(): no presentation'); + } + + // get the time + const time = new Date().getTime() - this.absoluteStart; + // make new movement object + const movement: Movement = { time, panX, panY, scale }; + + // add that movement to the current presentation data's movement array + this.currentPresentation.movements && this.currentPresentation.movements.push(movement); + }; + + // instance variable for the FFView + private disposeFunc: IReactionDisposer | null; + private recordingFFView: CollectionFreeFormView | null; + + // set the FFView that will be used in a reaction to track the movements + public setRecordingFFView = (view: CollectionFreeFormView): void => { + // set the view to the current view + if (view === this.recordingFFView || view == null) return; + + // this.recordingFFView = view; + // set the reaction to track the movements + this.disposeFunc = reaction( + () => ({ x: NumCast(view.Document.panX, -1), y: NumCast(view.Document.panY, -1), scale: NumCast(view.Document.viewScale, -1) }), + res => res.x !== -1 && res.y !== -1 && this.isRecording && this.trackMovements(res.x, res.y, res.scale) + ); + + // for now, set the most recent recordingFFView to the playFFView + this.recordingFFView = view; + }; + + // call on dispose function to stop tracking movements + public removeRecordingFFView = (): void => { + this.disposeFunc?.(); + this.disposeFunc = null; + }; + + // TODO: extract this into different class with pause and resume recording + // TODO: store the FFview with the movements + private playFFView: CollectionFreeFormView | null; + private timers: NodeJS.Timeout[] | null; + + public setPlayFFView = (view: CollectionFreeFormView): void => { + this.playFFView = view; + }; + + // pausing movements will dispose all timers that are planned to replay the movements + // play movemvents will recreate them when the user resumes the presentation + public pauseMovements = (): undefined | Error => { + if (this.playFFView === null) { + return new Error('[recordingApi.ts] pauseMovements() failed: no view'); + } + + if (!this._isPlaying) { + //return new Error('[recordingApi.ts] pauseMovements() failed: not playing') + return; + } + this._isPlaying = false; + // TODO: set userdoc presentMode to browsing + this.timers?.map(timer => clearTimeout(timer)); + + // this.videoBox = null; + }; + + private videoBox: VideoBox | null = null; + + // by calling pause on the VideoBox, the pauseMovements will be called + public pauseVideoAndMovements = (): boolean => { + this.videoBox?.Pause(); + + this.pauseMovements(); + return this.videoBox == null; + }; + + public _isPlaying = false; + + public playMovements = (presentation: Presentation, timeViewed: number = 0, videoBox?: VideoBox): undefined | Error => { + if (presentation.movements === null || this.playFFView === null) { + return new Error('[recordingApi.ts] followMovements() failed: no presentation data or no view'); + } + if (this._isPlaying) return; + + this._isPlaying = true; + Doc.UserDoc().presentationMode = 'watching'; + + // TODO: consider this bug at the end of the clip on seek + this.videoBox = videoBox || null; + + // only get the movements that are remaining in the video time left + const filteredMovements = presentation.movements.filter(movement => movement.time > timeViewed * 1000); + + // helper to replay a movement + const document = this.playFFView; + let preScale = -1; + const zoomAndPan = (movement: Movement) => { + const { panX, panY, scale } = movement; + scale !== -1 && preScale !== scale && document.zoomSmoothlyAboutPt([panX, panY], scale, 0); + document.Document._panX = panX; + document.Document._panY = panY; + + preScale = scale; + }; + + // set the first frame to be at the start of the pres + zoomAndPan(filteredMovements[0]); + + // make timers that will execute each movement at the correct replay time + this.timers = filteredMovements.map(movement => { + const timeDiff = movement.time - timeViewed * 1000; + return setTimeout(() => { + // replay the movement + zoomAndPan(movement); + // if last movement, presentation is done -> set the instance var + if (movement === filteredMovements[filteredMovements.length - 1]) RecordingApi.Instance._isPlaying = false; + }, timeDiff); + }); + }; + + // Unfinished code for tracing multiple free form views + // export let pres: Map = new Map() + + // export function AddRecordingFFView(ffView: CollectionFreeFormView): void { + // pres.set(ffView, + // reaction(() => ({ x: ffView.panX, y: ffView.panY }), + // (pt) => RecordingApi.trackMovements(ffView, pt.x, pt.y))) + // ) + // } + + // export function RemoveRecordingFFView(ffView: CollectionFreeFormView): void { + // const disposer = pres.get(ffView); + // disposer?.(); + // pres.delete(ffView) + // } +} diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx new file mode 100644 index 000000000..df4c8f937 --- /dev/null +++ b/src/client/views/nodes/DataViz.tsx @@ -0,0 +1,20 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { ViewBoxBaseComponent } from '../DocComponent'; +import './DataViz.scss'; +import { FieldView, FieldViewProps } from './FieldView'; + +@observer +export class DataVizBox extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DataVizBox, fieldKey); + } + + render() { + return ( +
    +
    Hi
    +
    + ); + } +} -- cgit v1.2.3-70-g09d2 From 6a637ac37962dbb402bb3ec1172cadafef9eab20 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 2 Jul 2022 10:41:40 -0400 Subject: fixing many eslint errors --- .eslintrc.json | 23 +- src/client/util/CurrentUserUtils.ts | 1 - .../CollectionSchemaMovableRow.tsx | 102 +- .../collections/collectionSchema/SchemaTable.tsx | 660 +++++++------ src/client/views/nodes/button/FontIconBox.tsx | 12 +- .../nodes/formattedText/DashDocCommentView.tsx | 77 +- .../views/nodes/formattedText/DashDocView.tsx | 181 ++-- .../views/nodes/formattedText/DashFieldView.tsx | 229 +++-- .../views/nodes/formattedText/EquationView.tsx | 99 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 1008 +++++++++++--------- .../formattedText/FormattedTextBoxComment.tsx | 107 ++- .../formattedText/ProsemirrorExampleTransfer.ts | 213 +++-- .../views/nodes/formattedText/RichTextMenu.tsx | 361 ++++--- .../views/nodes/formattedText/RichTextRules.ts | 574 ++++++----- .../views/nodes/formattedText/SummaryView.tsx | 73 +- src/client/views/nodes/formattedText/marks_rts.ts | 382 ++++---- src/client/views/nodes/formattedText/nodes_rts.ts | 361 ++++--- src/fields/Doc.ts | 865 +++++++++-------- src/fields/RichTextUtils.ts | 230 +++-- 19 files changed, 3033 insertions(+), 2525 deletions(-) (limited to 'src/client/util') diff --git a/.eslintrc.json b/.eslintrc.json index 5cc0ab6dc..b9f8e1b7a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,14 @@ { - "extends": ["airbnb", "prettier", "plugin:node/recommended"], - "plugins": ["prettier"], - "rules": { - "prettier/prettier": "error", - "no-unused-vars": "warn", - "no-console": "off", - "func-names": "off", - "no-process-exit": "off", - "object-shorthand": "off", - "class-methods-use-this": "off" - } + "extends": ["airbnb", "prettier", "plugin:node/recommended"], + "plugins": ["prettier"], + "rules": { + "prettier/prettier": "error", + "no-unused-vars": "warn", + "no-console": "off", + "func-names": "off", + "no-process-exit": "off", + "object-shorthand": "off", + "class-methods-use-this": "off", + "single-quote": "off" + } } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index dca77250c..b2a5fddcd 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -905,7 +905,6 @@ export class CurrentUserUtils { return doc.clickFuncs as Doc; } - /// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be /// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field /// whether to revert to "default" values, or to leave them as the user/system last set them. diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx index 0e19ef3d9..f872637e5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx @@ -1,17 +1,16 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action } from "mobx"; -import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table"; -import { Doc } from "../../../../fields/Doc"; -import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { Cast, FieldValue, StrCast } from "../../../../fields/Types"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { DragManager, dropActionType, SetupDrag } from "../../../util/DragManager"; -import { SnappingManager } from "../../../util/SnappingManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { ContextMenu } from "../../ContextMenu"; -import "./CollectionSchemaView.scss"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action } from 'mobx'; +import * as React from 'react'; +import { ReactTableDefaults, RowInfo } from 'react-table'; +import { Doc } from '../../../../fields/Doc'; +import { Cast, FieldValue, StrCast } from '../../../../fields/Types'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { DragManager, dropActionType, SetupDrag } from '../../../util/DragManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { Transform } from '../../../util/Transform'; +import { undoBatch } from '../../../util/UndoManager'; +import { ContextMenu } from '../../ContextMenu'; +import './CollectionSchemaView.scss'; export interface MovableRowProps { rowInfo: RowInfo; @@ -25,7 +24,7 @@ export interface MovableRowProps { addDocTab: any; } -export class MovableRow extends React.Component { +export class MovableRow extends React.Component> { private _header?: React.RefObject = React.createRef(); private _rowDropDisposer?: DragManager.DragDropDisposer; @@ -33,28 +32,27 @@ export class MovableRow extends React.Component { // Create one when the mouse starts hovering... onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = "collectionSchema-row-wrapper"; - document.addEventListener("pointermove", this.onDragMove, true); + this._header!.current!.className = 'collectionSchema-row-wrapper'; + document.addEventListener('pointermove', this.onDragMove, true); } - } + }; // ... and delete it when the mouse leaves onPointerLeave = (e: React.PointerEvent): void => { - this._header!.current!.className = "collectionSchema-row-wrapper"; - document.removeEventListener("pointermove", this.onDragMove, true); - } + this._header!.current!.className = 'collectionSchema-row-wrapper'; + document.removeEventListener('pointermove', this.onDragMove, true); + }; // The method for the event listener, reorders columns when dragged to their new locations. onDragMove = (e: PointerEvent): void => { const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); const rect = this._header!.current!.getBoundingClientRect(); const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); const before = x[1] < bounds[1]; - this._header!.current!.className = "collectionSchema-row-wrapper"; - if (before) this._header!.current!.className += " row-above"; - if (!before) this._header!.current!.className += " row-below"; + this._header!.current!.className = 'collectionSchema-row-wrapper'; + if (before) this._header!.current!.className += ' row-above'; + if (!before) this._header!.current!.className += ' row-below'; e.stopPropagation(); - } + }; componentWillUnmount() { - this._rowDropDisposer?.(); } // @@ -63,7 +61,7 @@ export class MovableRow extends React.Component { if (ele) { this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } - } + }; // Controls what hppens when a row is dragged and dropped rowDrop = (e: Event, de: DragManager.DropEvent) => { this.onPointerLeave(e as any); @@ -81,34 +79,34 @@ export class MovableRow extends React.Component { if (docDragData.draggedDocuments[0] === rowDoc) return true; const addDocument = (doc: Doc | Doc[]) => this.props.addDoc(doc, rowDoc, before); const movedDocs = docDragData.draggedDocuments; - return (docDragData.dropAction || docDragData.userDropAction) ? - docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) - : (docDragData.moveDocument) ? - movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false) - : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); + return docDragData.dropAction || docDragData.userDropAction + ? docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) + : docDragData.moveDocument + ? movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false) + : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); } return false; - } + }; onRowContextMenu = (e: React.MouseEvent): void => { - const description = this.props.rowWrapped ? "Unwrap text on row" : "Text wrap row"; - ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" }); - } + const description = this.props.rowWrapped ? 'Unwrap text on row' : 'Text wrap row'; + ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: 'file-pdf' }); + }; @undoBatch @action move: DragManager.MoveFunction = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc) => { const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection); return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); - } + }; @action onKeyDown = (e: React.KeyboardEvent) => { - console.log("yes"); - if (e.key === "Backspace" || e.key === "Delete") { + console.log('yes'); + if (e.key === 'Backspace' || e.key === 'Delete') { undoBatch(() => this.props.removeDoc(this.props.rowInfo.original)); } - } + }; render() { const { children = null, rowInfo } = this.props; @@ -120,23 +118,29 @@ export class MovableRow extends React.Component { const { original } = rowInfo; const doc = FieldValue(Cast(original, Doc)); - if (!doc) return (null); + if (!doc) return null; const reference = React.createRef(); const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType); - let className = "collectionSchema-row"; - if (this.props.rowFocused) className += " row-focused"; - if (this.props.rowWrapped) className += " row-wrapped"; + let className = 'collectionSchema-row'; + if (this.props.rowFocused) className += ' row-focused'; + if (this.props.rowWrapped) className += ' row-wrapped'; return (
    - +
    -
    this.props.removeDoc(this.props.rowInfo.original))}>
    -
    -
    this.props.addDocTab(this.props.rowInfo.original, "add:right")}>
    +
    this.props.removeDoc(this.props.rowInfo.original))}> + +
    +
    + +
    +
    this.props.addDocTab(this.props.rowInfo.original, 'add:right')}> + +
    {children}
    @@ -144,4 +148,4 @@ export class MovableRow extends React.Component {
    ); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 43266a571..fafea5ce3 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -1,37 +1,47 @@ -import React = require("react"); import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, trace } from "mobx"; -import { observer } from "mobx-react"; -import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table"; -import { DateField } from "../../../../fields/DateField"; -import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { List } from "../../../../fields/List"; -import { listSpec } from "../../../../fields/Schema"; -import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { ComputedField } from "../../../../fields/ScriptField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; -import { ImageField } from "../../../../fields/URLField"; -import { GetEffectiveAcl } from "../../../../fields/util"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../../Utils"; -import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { CompileScript, Transformer, ts } from "../../../util/Scripting"; -import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; -import { ContextMenu } from "../../ContextMenu"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from 'react-table'; +import { DateField } from '../../../../fields/DateField'; +import { AclPrivate, AclReadonly, DataSym, Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; +import { listSpec } from '../../../../fields/Schema'; +import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { GetEffectiveAcl } from '../../../../fields/util'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../../Utils'; +import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { CompileScript, Transformer, ts } from '../../../util/Scripting'; +import { Transform } from '../../../util/Transform'; +import { undoBatch } from '../../../util/UndoManager'; import '../../../views/DocumentDecorations.scss'; -import { DocumentView } from "../../nodes/DocumentView"; -import { DefaultStyleProvider } from "../../StyleProvider"; -import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells"; -import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders"; -import { MovableColumn } from "./CollectionSchemaMovableColumn"; -import { MovableRow } from "./CollectionSchemaMovableRow"; -import "./CollectionSchemaView.scss"; -import { CollectionView } from "../CollectionView"; - +import { ContextMenu } from '../../ContextMenu'; +import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../global/globalCssVariables.scss'; +import { DocumentView } from '../../nodes/DocumentView'; +import { DefaultStyleProvider } from '../../StyleProvider'; +import { CollectionView } from '../CollectionView'; +import { + CellProps, + CollectionSchemaButtons, + CollectionSchemaCell, + CollectionSchemaCheckboxCell, + CollectionSchemaDateCell, + CollectionSchemaDocCell, + CollectionSchemaImageCell, + CollectionSchemaListCell, + CollectionSchemaNumberCell, + CollectionSchemaStringCell, +} from './CollectionSchemaCells'; +import { CollectionSchemaAddColumnHeader, KeysDropdown } from './CollectionSchemaHeaders'; +import { MovableColumn } from './CollectionSchemaMovableColumn'; +import { MovableRow } from './CollectionSchemaMovableRow'; +import './CollectionSchemaView.scss'; enum ColumnType { Any, @@ -41,15 +51,22 @@ enum ColumnType { Doc, Image, List, - Date + Date, } // this map should be used for keys that should have a const type of value const columnTypes: Map = new Map([ - ["title", ColumnType.String], - ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number], - ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], - ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number] + ['title', ColumnType.String], + ['x', ColumnType.Number], + ['y', ColumnType.Number], + ['_width', ColumnType.Number], + ['_height', ColumnType.Number], + ['_nativeWidth', ColumnType.Number], + ['_nativeHeight', ColumnType.Number], + ['isPrototype', ColumnType.Boolean], + ['_curPage', ColumnType.Number], + ['_currentTimecode', ColumnType.Number], + ['zIndex', ColumnType.Number], ]); export interface SchemaTableProps { @@ -92,18 +109,24 @@ export interface SchemaTableProps { @observer export class SchemaTable extends React.Component { @observable _cellIsEditing: boolean = false; - @observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 }; - @observable _openCollections: Set = new Set; + @observable _focusedCell: { row: number; col: number } = { row: 0, col: 0 }; + @observable _openCollections: Set = new Set(); @observable _showDoc: Doc | undefined; - @observable _showDataDoc: any = ""; + @observable _showDataDoc: any = ''; @observable _showDocPos: number[] = []; @observable _showTitleDropdown: boolean = false; - @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); } - @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; } - @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); } + @computed get previewWidth() { + return () => NumCast(this.props.Document.schemaPreviewWidth); + } + @computed get previewHeight() { + return () => this.props.PanelHeight() - 2 * this.borderWidth; + } + @computed get tableWidth() { + return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); + } @computed get childDocs() { if (this.props.childDocs) return this.props.childDocs; @@ -117,17 +140,17 @@ export class SchemaTable extends React.Component { } @computed get textWrappedRows() { - return Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []); + return Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); } set textWrappedRows(textWrappedRows: string[]) { this.props.Document.textwrappedSchemaRows = new List(textWrappedRows); } - @computed get resized(): { id: string, value: number }[] { + @computed get resized(): { id: string; value: number }[] { return this.props.columns.reduce((resized, shf) => { - (shf.width > -1) && resized.push({ id: shf.heading, value: shf.width }); + shf.width > -1 && resized.push({ id: shf.heading, value: shf.width }); return resized; - }, [] as { id: string, value: number }[]); + }, [] as { id: string; value: number }[]); } @computed get sorted(): SortingRule[] { return this.props.columns.reduce((sorted, shf) => { @@ -139,12 +162,14 @@ export class SchemaTable extends React.Component { @action changeSorting = (col: any) => { this.props.changeColumnSort(col, col.desc === true ? false : col.desc === false ? undefined : true); - } + }; @action - changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown + changeTitleMode = () => (this._showTitleDropdown = !this._showTitleDropdown); - @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } + @computed get borderWidth() { + return Number(COLLECTION_BORDER_WIDTH); + } @computed get tableColumns(): Column[] { const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); const columns: Column[] = []; @@ -154,149 +179,185 @@ export class SchemaTable extends React.Component { const isEditable = !this.props.headerIsEditing; columns.push({ - expander: true, Header: "", width: 58, - Expander: (rowInfo) => { - return rowInfo.original.type !== DocumentType.COL ? (null) : -
    (this._openCollections[rowInfo.isExpanded ? "delete" : "add"])(rowInfo.viewIndex))}> - -
    ; - } + expander: true, + Header: '', + width: 58, + Expander: rowInfo => { + return rowInfo.original.type !== DocumentType.COL ? null : ( +
    this._openCollections[rowInfo.isExpanded ? 'delete' : 'add'](rowInfo.viewIndex))}> + +
    + ); + }, }); - columns.push(...this.props.columns.map(col => { - const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : - this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : - this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : - this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; - - const keysDropdown = c.heading)} - canAddNew={true} - addNew={false} - onSelect={this.props.changeColumns} - setIsEditing={this.props.setHeaderIsEditing} - docs={this.props.childDocs} - Document={this.props.Document} - dataDoc={this.props.dataDoc} - fieldKey={this.props.fieldKey} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - ContainingCollectionView={this.props.ContainingCollectionView} - active={this.props.active} - openHeader={this.props.openHeader} - icon={icon} - col={col} - // try commenting this out - width={"100%"} - />; - - const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up"; - const header =
    - {keysDropdown} -
    this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "pointer" }}> - -
    - {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined :
    + new
    } */} -
    ; - - return { - Header: , - accessor: (doc: Doc) => doc ? Field.toString(doc[col.heading] as Field) : 0, - id: col.heading, - Cell: (rowProps: CellInfo) => { - const rowIndex = rowProps.index; - const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); - const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; - - const props: CellProps = { - row: rowIndex, - col: columnIndex, - rowProps: rowProps, - isFocused: isFocused, - changeFocusedCellByIndex: this.changeFocusedCellByIndex, - CollectionView: this.props.CollectionView, - ContainingCollection: this.props.ContainingCollectionView, - Document: this.props.Document, - fieldKey: this.props.fieldKey, - renderDepth: this.props.renderDepth, - addDocTab: this.props.addDocTab, - pinToPres: this.props.pinToPres, - moveDocument: this.props.moveDocument, - setIsEditing: this.setCellIsEditing, - isEditable: isEditable, - setPreviewDoc: this.props.setPreviewDoc, - setComputed: this.setComputed, - getField: this.getField, - showDoc: this.showDoc, - }; - - - switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) { - case ColumnType.Number: return ; - case ColumnType.String: return ; - case ColumnType.Boolean: return ; - case ColumnType.Doc: return ; - case ColumnType.Image: return ; - case ColumnType.List: return ; - case ColumnType.Date: return ; - default: - return ; - } - }, - minWidth: 200, - }; - })); + columns.push( + ...this.props.columns.map(col => { + const icon: IconProp = + this.getColumnType(col) === ColumnType.Number + ? 'hashtag' + : this.getColumnType(col) === ColumnType.String + ? 'font' + : this.getColumnType(col) === ColumnType.Boolean + ? 'check-square' + : this.getColumnType(col) === ColumnType.Doc + ? 'file' + : this.getColumnType(col) === ColumnType.Image + ? 'image' + : this.getColumnType(col) === ColumnType.List + ? 'list-ul' + : this.getColumnType(col) === ColumnType.Date + ? 'calendar' + : 'align-justify'; + + const keysDropdown = ( + c.heading)} + canAddNew={true} + addNew={false} + onSelect={this.props.changeColumns} + setIsEditing={this.props.setHeaderIsEditing} + docs={this.props.childDocs} + Document={this.props.Document} + dataDoc={this.props.dataDoc} + fieldKey={this.props.fieldKey} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + ContainingCollectionView={this.props.ContainingCollectionView} + active={this.props.active} + openHeader={this.props.openHeader} + icon={icon} + col={col} + // try commenting this out + width={'100%'} + /> + ); + + const sortIcon = col.desc === undefined ? 'caret-right' : col.desc === true ? 'caret-down' : 'caret-up'; + const header = ( +
    + {keysDropdown} +
    this.changeSorting(col)} style={{ width: 21, padding: 1, display: 'inline', zIndex: 1, background: 'inherit', cursor: 'pointer' }}> + +
    + {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined :
    + new
    } */} +
    + ); + + return { + Header: , + accessor: (doc: Doc) => (doc ? Field.toString(doc[col.heading] as Field) : 0), + id: col.heading, + Cell: (rowProps: CellInfo) => { + const rowIndex = rowProps.index; + const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); + const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; + + const props: CellProps = { + row: rowIndex, + col: columnIndex, + rowProps: rowProps, + isFocused: isFocused, + changeFocusedCellByIndex: this.changeFocusedCellByIndex, + CollectionView: this.props.CollectionView, + ContainingCollection: this.props.ContainingCollectionView, + Document: this.props.Document, + fieldKey: this.props.fieldKey, + renderDepth: this.props.renderDepth, + addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, + moveDocument: this.props.moveDocument, + setIsEditing: this.setCellIsEditing, + isEditable: isEditable, + setPreviewDoc: this.props.setPreviewDoc, + setComputed: this.setComputed, + getField: this.getField, + showDoc: this.showDoc, + }; + + switch (this.getColumnType(col, rowProps.original, rowProps.column.id)) { + case ColumnType.Number: + return ; + case ColumnType.String: + return ; + case ColumnType.Boolean: + return ; + case ColumnType.Doc: + return ; + case ColumnType.Image: + return ; + case ColumnType.List: + return ; + case ColumnType.Date: + return ; + default: + return ; + } + }, + minWidth: 200, + }; + }) + ); columns.push({ Header: , accessor: (doc: Doc) => 0, - id: "add", + id: 'add', Cell: (rowProps: CellInfo) => { const rowIndex = rowProps.index; const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; - return ; + return ( + + ); }, width: 28, - resizable: false + resizable: false, }); return columns; } - constructor(props: SchemaTableProps) { super(props); if (this.props.Document._schemaHeaders === undefined) { - this.props.Document._schemaHeaders = new List([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb", ColumnType.Date), - new SchemaHeaderField("text", "#f1efeb", ColumnType.String), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb", ColumnType.Doc)]); + this.props.Document._schemaHeaders = new List([ + new SchemaHeaderField('title', '#f1efeb'), + new SchemaHeaderField('author', '#f1efeb'), + new SchemaHeaderField('*lastModified', '#f1efeb', ColumnType.Date), + new SchemaHeaderField('text', '#f1efeb', ColumnType.String), + new SchemaHeaderField('type', '#f1efeb'), + new SchemaHeaderField('context', '#f1efeb', ColumnType.Doc), + ]); } } componentDidMount() { - document.addEventListener("keydown", this.onKeyDown); + document.addEventListener('keydown', this.onKeyDown); } componentWillUnmount() { - document.removeEventListener("keydown", this.onKeyDown); + document.removeEventListener('keydown', this.onKeyDown); } tableAddDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => { @@ -305,25 +366,27 @@ export class SchemaTable extends React.Component { if (effectiveAcl !== AclPrivate && effectiveAcl !== AclReadonly) { doc.context = this.props.Document; - tableDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + tableDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now())); return Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); } return false; - } + }; private getTrProps: ComponentPropsGetterR = (state, rowInfo) => { - return !rowInfo ? {} : { - ScreenToLocalTransform: this.props.ScreenToLocalTransform, - addDoc: this.tableAddDoc, - removeDoc: this.props.deleteDocument, - rowInfo, - rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), - textWrapRow: this.toggleTextWrapRow, - rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, - dropAction: StrCast(this.props.Document.childDropAction), - addDocTab: this.props.addDocTab - }; - } + return !rowInfo + ? {} + : { + ScreenToLocalTransform: this.props.ScreenToLocalTransform, + addDoc: this.tableAddDoc, + removeDoc: this.props.deleteDocument, + rowInfo, + rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), + textWrapRow: this.toggleTextWrapRow, + rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, + dropAction: StrCast(this.props.Document.childDropAction), + addDocTab: this.props.addDocTab, + }; + }; private getTdProps: ComponentPropsGetterR = (state, rowInfo, column, instance) => { if (!rowInfo || column) return {}; @@ -334,16 +397,17 @@ export class SchemaTable extends React.Component { const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true); // TODO: editing border doesn't work :( return { - style: { border: !this.props.headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb" } + style: { border: !this.props.headerIsEditing && isFocused ? '2px solid rgb(255, 160, 160)' : '1px solid #f1efeb' }, }; - } + }; - @action setCellIsEditing = (isEditing: boolean) => this._cellIsEditing = isEditing; + @action setCellIsEditing = (isEditing: boolean) => (this._cellIsEditing = isEditing); @action onKeyDown = (e: KeyboardEvent): void => { - if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {// && this.props.isSelected(true)) { - const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : ""; + if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) { + // && this.props.isSelected(true)) { + const direction = e.key === 'Tab' ? 'tab' : e.which === 39 ? 'right' : e.which === 37 ? 'left' : e.which === 38 ? 'up' : e.which === 40 ? 'down' : ''; this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); if (direction) { @@ -353,20 +417,25 @@ export class SchemaTable extends React.Component { } } else if (e.keyCode === 27) { this.props.setPreviewDoc(undefined); - e.stopPropagation(); // stopPropagation for left/right arrows + e.stopPropagation(); // stopPropagation for left/right arrows } - } + }; changeFocusedCellByDirection = (direction: string, curRow: number, curCol: number) => { switch (direction) { - case "tab": return { row: (curRow + 1 === this.childDocs.length ? 0 : curRow + 1), col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 }; - case "right": return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 }; - case "left": return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 }; - case "up": return { row: curRow === 0 ? curRow : curRow - 1, col: curCol }; - case "down": return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol }; + case 'tab': + return { row: curRow + 1 === this.childDocs.length ? 0 : curRow + 1, col: curCol + 1 === this.props.columns.length ? 0 : curCol + 1 }; + case 'right': + return { row: curRow, col: curCol + 1 === this.props.columns.length ? curCol : curCol + 1 }; + case 'left': + return { row: curRow, col: curCol === 0 ? curCol : curCol - 1 }; + case 'up': + return { row: curRow === 0 ? curRow : curRow - 1, col: curCol }; + case 'down': + return { row: curRow + 1 === this.childDocs.length ? curRow : curRow + 1, col: curCol }; } return this._focusedCell; - } + }; @action changeFocusedCellByIndex = (row: number, col: number): void => { @@ -374,25 +443,25 @@ export class SchemaTable extends React.Component { this._focusedCell = { row: row, col: col }; } this.props.setFocused(this.props.Document); - } + }; @undoBatch createRow = action(() => { - this.props.addDocument?.(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 })); + this.props.addDocument?.(Docs.Create.TextDocument('', { title: '', _width: 100, _height: 30 })); this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col }; }); @undoBatch @action createColumn = () => { - const newFieldName = (index: number) => `New field${index ? ` (${index})` : ""}`; + const newFieldName = (index: number) => `New field${index ? ` (${index})` : ''}`; for (let index = 0; index < 100; index++) { if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) { - this.props.columns.push(new SchemaHeaderField(newFieldName(index), "#f1efeb")); + this.props.columns.push(new SchemaHeaderField(newFieldName(index), '#f1efeb')); break; } } - } + }; @action getColumnType = (column: SchemaHeaderField, doc?: Doc, field?: string): ColumnType => { @@ -407,15 +476,15 @@ export class SchemaTable extends React.Component { return column.type; } if (columnTypes.get(column.heading)) { - return column.type = columnTypes.get(column.heading)!; + return (column.type = columnTypes.get(column.heading)!); } - return column.type = ColumnType.Any; - } + return (column.type = ColumnType.Any); + }; @undoBatch @action toggleTextwrap = async () => { - const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []); + const textwrappedRows = Cast(this.props.Document.textwrappedSchemaRows, listSpec('string'), []); if (textwrappedRows.length) { this.props.Document.textwrappedSchemaRows = new List([]); } else { @@ -423,7 +492,7 @@ export class SchemaTable extends React.Component { const allRows = docs instanceof Doc ? [docs[Id]] : docs.map(doc => doc[Id]); this.props.Document.textwrappedSchemaRows = new List(allRows); } - } + }; @action toggleTextWrapRow = (doc: Doc): void => { @@ -433,41 +502,50 @@ export class SchemaTable extends React.Component { index > -1 ? textWrapped.splice(index, 1) : textWrapped.push(doc[Id]); this.textWrappedRows = textWrapped; - } + }; @computed get reactTable() { const children = this.childDocs; const hasCollectionChild = children.reduce((found, doc) => found || doc.type === DocumentType.COL, false); const expanded: { [name: string]: any } = {}; - Array.from(this._openCollections.keys()).map(col => expanded[col.toString()] = true); + Array.from(this._openCollections.keys()).map(col => (expanded[col.toString()] = true)); const rerender = [...this.textWrappedRows]; // TODO: get component to rerender on text wrap change without needign to console.log :(((( - return (row.original.type !== DocumentType.COL) ? (null) : -
    } - - />; + return ( + + row.original.type !== DocumentType.COL ? null : ( +
    + +
    + ) + } + /> + ); } onContextMenu = (e: React.MouseEvent): void => { - ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" }); - } + ContextMenu.Instance.addItem({ description: 'Toggle text wrapping', event: this.toggleTextwrap, icon: 'table' }); + }; getField = (row: number, col?: number) => { const docs = this.childDocs; @@ -484,7 +562,7 @@ export class SchemaTable extends React.Component { return doc[column]; } return undefined; - } + }; createTransformer = (row: number, col: number): Transformer => { const self = this; @@ -498,11 +576,11 @@ export class SchemaTable extends React.Component { const isntPropAccess = !ts.isPropertyAccessExpression(node.parent) || node.parent.expression === node; const isntPropAssign = !ts.isPropertyAssignment(node.parent) || node.parent.name !== node; if (isntPropAccess && isntPropAssign) { - if (node.text === "$r") { + if (node.text === '$r') { return ts.createNumericLiteral(row.toString()); - } else if (node.text === "$c") { + } else if (node.text === '$c') { return ts.createNumericLiteral(col.toString()); - } else if (node.text === "$") { + } else if (node.text === '$') { if (ts.isCallExpression(node.parent)) { // captures.doc = self.props.Document; // captures.key = self.props.fieldKey; @@ -521,12 +599,11 @@ export class SchemaTable extends React.Component { // return { capturedVariables: captures }; // }; - return { transformer, /*getVars*/ }; - } + return { transformer /*getVars*/ }; + }; setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => { - script = - `const $ = (row:number, col?:number) => { + script = `const $ = (row:number, col?:number) => { const rval = (doc as any)[key][row + ${row}]; return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading]; } @@ -537,7 +614,7 @@ export class SchemaTable extends React.Component { return true; } return false; - } + }; @action showDoc = (doc: Doc | undefined, dataDoc?: Doc, screenX?: number, screenY?: number) => { @@ -545,55 +622,72 @@ export class SchemaTable extends React.Component { if (dataDoc && screenX && screenY) { this._showDocPos = this.props.ScreenToLocalTransform().transformPoint(screenX, screenY); } - } + }; onOpenClick = () => { - this._showDoc && this.props.addDocTab(this._showDoc, "add:right"); - } + this._showDoc && this.props.addDocTab(this._showDoc, 'add:right'); + }; getPreviewTransform = (): Transform => { - return this.props.ScreenToLocalTransform().translate(- this.borderWidth - 4 - this.tableWidth, - this.borderWidth); - } + return this.props.ScreenToLocalTransform().translate(-this.borderWidth - 4 - this.tableWidth, -this.borderWidth); + }; render() { - const preview = ""; - return
    this.props.active(true) && e.stopPropagation()} - onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > - {this.reactTable} - {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined :
    + new
    } - {!this._showDoc ? (null) : -
    - 150} - PanelHeight={() => 150} - ScreenToLocalTransform={this.getPreviewTransform} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.CollectionView?.props.Document} - ContainingCollectionView={this.props.CollectionView} - moveDocument={this.props.moveDocument} - whenChildContentsActiveChanged={emptyFunction} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - bringToFront={returnFalse}> - -
    } -
    ; + const preview = ''; + return ( +
    this.props.active(true) && e.stopPropagation()} + onDrop={e => this.props.onDrop(e, {})} + onContextMenu={this.onContextMenu}> + {this.reactTable} + {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : ( +
    + + new +
    + )} + {!this._showDoc ? null : ( +
    + 150} + PanelHeight={() => 150} + ScreenToLocalTransform={this.getPreviewTransform} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} + moveDocument={this.props.moveDocument} + whenChildContentsActiveChanged={emptyFunction} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + bringToFront={returnFalse}> +
    + )} +
    + ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 85efc67a5..fa3029a98 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -1,14 +1,13 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { StringIterator } from 'lodash'; +import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ColorState, SketchPicker } from 'react-color'; import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; -import { createSchema } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { WebField } from '../../../../fields/URLField'; @@ -33,9 +32,6 @@ import { RichTextMenu } from '../formattedText/RichTextMenu'; import { WebBox } from '../WebBox'; import { FontIconBadge } from './FontIconBadge'; import './FontIconBox.scss'; -const FontIconSchema = createSchema({ - icon: "string", -}); export enum ButtonType { TextButton = "textBtn", @@ -628,11 +624,7 @@ ScriptingGlobals.add(function setBulletList(mapStyle: "bullet" | "decimal", chec if (active === mapStyle) return Colors.MEDIUM_BLUE; return "transparent"; } - if (editorView) { - const active = editorView?.state && RichTextMenu.Instance.getActiveListStyle(); - editorView?.state && RichTextMenu.Instance.changeListType( - editorView.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle })); - } + editorView?.state && RichTextMenu.Instance.changeListType(mapStyle); }); // toggle: Set overlay status of selected document diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index 5c75a589a..40dd6fbc7 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -1,37 +1,44 @@ -import { TextSelection } from "prosemirror-state"; +import { TextSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom'; -import { Doc } from "../../../../fields/Doc"; -import { DocServer } from "../../../DocServer"; -import React = require("react"); - +import { Doc } from '../../../../fields/Doc'; +import { DocServer } from '../../../DocServer'; +import React = require('react'); // creates an inline comment in a note when '>>' is typed. // the comment sits on the right side of the note and vertically aligns with its anchor in the text. // the comment can be toggled on/off with the '<-' text anchor. export class DashDocCommentView { - _fieldWrapper: HTMLDivElement; // container for label and value + dom: HTMLDivElement; // container for label and value constructor(node: any, view: any, getPos: any) { - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; - - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.fontWeight = 'bold'; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; + + ReactDOM.render(, this.dom); + (this as any).dom = this.dom; } destroy() { - ReactDOM.unmountComponentAtNode(this._fieldWrapper); + ReactDOM.unmountComponentAtNode(this.dom); } - selectNode() { } + selectNode() {} } interface IDashDocCommentViewInternal { @@ -40,8 +47,7 @@ interface IDashDocCommentViewInternal { getPos: any; } -export class DashDocCommentViewInternal extends React.Component{ - +export class DashDocCommentViewInternal extends React.Component { constructor(props: IDashDocCommentViewInternal) { super(props); this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this); @@ -71,7 +77,9 @@ export class DashDocCommentViewInternal extends React.Component { expand && DocServer.GetRefField(this.props.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); - try { this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); } catch (e) { } + try { + this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1)))); + } catch (e) {} }, 0); } e.stopPropagation(); @@ -81,32 +89,35 @@ export class DashDocCommentViewInternal extends React.Component { // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor + targetNode = () => { + // search forward in the prosemirror doc for the attached dashDocNode that is the target of the comment anchor const state = this.props.view.state; for (let i = this.props.getPos() + 1; i < state.doc.content.size; i++) { const m = state.doc.nodeAt(i); if (m && m.type === state.schema.nodes.dashDoc && m.attrs.docid === this.props.docid) { - return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any, pos: number, hidden: boolean }; + return { node: m, pos: i, hidden: m.attrs.hidden } as { node: any; pos: number; hidden: boolean }; } } - const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: this.props.docid, float: "right" }); + const dashDoc = state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: this.props.docid, float: 'right' }); this.props.view.dispatch(state.tr.insert(this.props.getPos() + 1, dashDoc)); - setTimeout(() => { try { this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); } catch (e) { } }, 0); + setTimeout(() => { + try { + this.props.view.dispatch(state.tr.setSelection(TextSelection.create(state.tr.doc, this.props.getPos() + 2))); + } catch (e) {} + }, 0); return undefined; - } + }; render() { return ( - + onPointerDown={this.onPointerDownCollapsed}> ); } } diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 1d8e3a2cf..9d203b6cc 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -1,52 +1,54 @@ -import { IReactionDisposer, reaction, observable, action } from "mobx"; -import { NodeSelection } from "prosemirror-state"; -import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc"; -import { Cast, StrCast, NumCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { Transform } from "../../../util/Transform"; -import { DocumentView } from "../DocumentView"; -import { FormattedTextBox } from "./FormattedTextBox"; -import React = require("react"); +import { IReactionDisposer, reaction, observable, action } from 'mobx'; +import { NodeSelection } from 'prosemirror-state'; +import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; +import { Cast, StrCast, NumCast } from '../../../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from '../../../../Utils'; +import { DocServer } from '../../../DocServer'; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { Transform } from '../../../util/Transform'; +import { DocumentView } from '../DocumentView'; +import { FormattedTextBox } from './FormattedTextBox'; +import React = require('react'); import * as ReactDOM from 'react-dom'; -import { observer } from "mobx-react"; -import { ColorScheme } from "../../../util/SettingsManager"; +import { observer } from 'mobx-react'; +import { ColorScheme } from '../../../util/SettingsManager'; export class DashDocView { - _fieldWrapper: HTMLSpanElement; // container for label and value + dom: HTMLSpanElement; // container for label and value constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldWrapper = document.createElement("span"); - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.textIndent = "0"; - this._fieldWrapper.style.border = "1px solid " + StrCast(tbox.layoutDoc.color, (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "lightGray")); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.display = node.attrs.hidden ? "none" : "inline-block"; - (this._fieldWrapper.style as any).float = node.attrs.float; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; - - ReactDOM.render(
    + ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index bb3791f1e..940ed6386 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,52 +1,55 @@ -import { action, computed, IReactionDisposer, observable } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, IReactionDisposer, observable } from 'mobx'; +import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom'; -import { DataSym, Doc, DocListCast, Field } from "../../../../fields/Doc"; -import { List } from "../../../../fields/List"; -import { listSpec } from "../../../../fields/Schema"; -import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { ComputedField } from "../../../../fields/ScriptField"; -import { Cast, StrCast } from "../../../../fields/Types"; -import { DocServer } from "../../../DocServer"; -import { CollectionViewType } from "../../collections/CollectionView"; -import "./DashFieldView.scss"; -import { FormattedTextBox } from "./FormattedTextBox"; -import React = require("react"); -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; -import { Tooltip } from "@material-ui/core"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { DataSym, Doc, DocListCast, Field } from '../../../../fields/Doc'; +import { List } from '../../../../fields/List'; +import { listSpec } from '../../../../fields/Schema'; +import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { DocServer } from '../../../DocServer'; +import { CollectionViewType } from '../../collections/CollectionView'; +import './DashFieldView.scss'; +import { FormattedTextBox } from './FormattedTextBox'; +import React = require('react'); +import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { Tooltip } from '@material-ui/core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; export class DashFieldView { - _fieldWrapper: HTMLDivElement; // container for label and value + dom: HTMLDivElement; // container for label and value constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey); - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.fontWeight = "bold"; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.textContent = node.attrs.fieldKey.startsWith("#") ? node.attrs.fieldKey : node.attrs.fieldKey + " " + strVal; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.fontWeight = 'bold'; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.textContent = node.attrs.fieldKey.startsWith('#') ? node.attrs.fieldKey : node.attrs.fieldKey + ' ' + strVal; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; - setTimeout(() => ReactDOM.render(, this._fieldWrapper)); - (this as any).dom = this._fieldWrapper; + setTimeout(() => ReactDOM.render(, this.dom)); + (this as any).dom = this.dom; } - destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } - selectNode() { } + destroy() { + ReactDOM.unmountComponentAtNode(this.dom); + } + selectNode() {} } interface IDashFieldViewInternal { @@ -72,8 +75,7 @@ export class DashFieldViewInternal extends React.Component dashDoc instanceof Doc && (this._dashDoc = dashDoc))); + DocServer.GetRefField(this.props.docid).then(action(async dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); } else { this._dashDoc = this.props.tbox.rootDoc; } @@ -82,11 +84,11 @@ export class DashFieldViewInternal extends React.Component { - if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked; - Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true); - }} - />; - } - else // field value is a string, so display it as an editable span - { + return ( + { + if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked; + Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true); + }} + /> + ); + } // field value is a string, so display it as an editable span + else { // bcz: this is unfortunate, but since this React component is nested within a non-React text box (prosemirror), we can't // use React events. Essentially, React events occur after native events have been processed, so corresponding React events // will never fire because Prosemirror has handled the native events. So we add listeners for native events here. - return { - r?.addEventListener("keydown", e => this.fieldSpanKeyDown(e, r)); - r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); - r?.addEventListener("pointerdown", action(e => e.stopPropagation())); - }} > - {strVal} - ; + return ( + { + r?.addEventListener('keydown', e => this.fieldSpanKeyDown(e, r)); + r?.addEventListener('blur', e => r && this.updateText(r.textContent!, false)); + r?.addEventListener( + 'pointerdown', + action(e => e.stopPropagation()) + ); + }}> + {strVal} + + ); } } } @@ -126,11 +138,13 @@ export class DashFieldViewInternal extends React.Component { - if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. + if (e.key === 'Enter') { + // handle the enter key by "submitting" the current text to Dash's database. this.updateText(span.textContent!, true); - e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view + e.preventDefault(); // prevent default to avoid a newline from being generated and wiping out this field view } - if (e.key === "a" && (e.ctrlKey || e.metaKey)) { // handle ctrl-A to select all the text within the span + if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { + // handle ctrl-A to select all the text within the span if (window.getSelection) { const range = document.createRange(); range.selectNodeContents(span); @@ -139,44 +153,44 @@ export class DashFieldViewInternal extends React.Component { if (nodeText) { - const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; + const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText; // look for a document whose id === the fieldKey being displayed. If there's a match, then that document // holds the different enumerated values for the field in the titles of its collected documents. // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. DocServer.GetRefField(this._fieldKey).then(options => { - let modText = ""; - (options instanceof Doc) && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); + let modText = ''; + options instanceof Doc && DocListCast(options.data).forEach(opt => (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); if (modText) { // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true); } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key - else if (nodeText.startsWith(":=")) { + else if (nodeText.startsWith(':=')) { this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); - } else if (nodeText.startsWith("=:=")) { + } else if (nodeText.startsWith('=:=')) { Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); } else { if (Number(newText).toString() === newText) { - if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); + if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true); } else { const splits = newText.split(DashFieldViewInternal.multiValueDelimeter); - if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) { + if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) { const strVal = splits.length > 1 ? new List(splits) : newText; - if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; + if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true); } } } }); } - } + }; createPivotForField = (e: React.MouseEvent) => { let container = this.props.tbox.props.ContainingCollectionView; @@ -190,36 +204,39 @@ export class DashFieldViewInternal extends React.Component(); } - list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, "#f1efeb")); - list.map(c => c.heading).indexOf("text") === -1 && list.push(new SchemaHeaderField("text", "#f1efeb")); - alias._pivotField = this._fieldKey.startsWith("#") ? "#" : this._fieldKey; - this.props.tbox.props.addDocTab(alias, "add:right"); + list.map(c => c.heading).indexOf(this._fieldKey) === -1 && list.push(new SchemaHeaderField(this._fieldKey, '#f1efeb')); + list.map(c => c.heading).indexOf('text') === -1 && list.push(new SchemaHeaderField('text', '#f1efeb')); + alias._pivotField = this._fieldKey.startsWith('#') ? '#' : this._fieldKey; + this.props.tbox.props.addDocTab(alias, 'add:right'); } - } - + }; // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label onPointerDownLabelSpan = (e: any) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, (e) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { DashFieldViewMenu.createFieldView = this.createPivotForField; DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16); }); - } + }; render() { - return
    - {this.props.hideKey ? (null) : - - {this._fieldKey} - } - - {this.props.fieldKey.startsWith("#") ? (null) : this.fieldValueContent} + return ( +
    + {this.props.hideKey ? null : ( + + {this._fieldKey} + + )} -
    ; + {this.props.fieldKey.startsWith('#') ? null : this.fieldValueContent} +
    + ); } } @observer @@ -234,19 +251,19 @@ export class DashFieldViewMenu extends AntimodeMenu { showFields = (e: React.MouseEvent) => { DashFieldViewMenu.createFieldView(e); DashFieldViewMenu.Instance.fadeOut(true); - } + }; public show = (x: number, y: number) => { this.jumpTo(x, y, true); const hideMenu = () => { this.fadeOut(true); - document.removeEventListener("pointerdown", hideMenu); + document.removeEventListener('pointerdown', hideMenu); }; - document.addEventListener("pointerdown", hideMenu); - } + document.addEventListener('pointerdown', hideMenu); + }; render() { const buttons = [ - {"Remove Link Anchor"}
    }> + {'Remove Link Anchor'}
    }> @@ -255,4 +272,4 @@ export class DashFieldViewMenu extends AntimodeMenu { return this.getElement(buttons); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index 508500ab6..98d611ca6 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -1,38 +1,38 @@ -import EquationEditor from "equation-editor-react"; -import { IReactionDisposer } from "mobx"; -import { observer } from "mobx-react"; +import EquationEditor from 'equation-editor-react'; +import { IReactionDisposer } from 'mobx'; +import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom'; -import { Doc } from "../../../../fields/Doc"; -import { StrCast } from "../../../../fields/Types"; -import "./DashFieldView.scss"; -import { FormattedTextBox } from "./FormattedTextBox"; -import React = require("react"); +import { Doc } from '../../../../fields/Doc'; +import { StrCast } from '../../../../fields/Types'; +import './DashFieldView.scss'; +import { FormattedTextBox } from './FormattedTextBox'; +import React = require('react'); export class EquationView { - _fieldWrapper: HTMLDivElement; // container for label and value + dom: HTMLDivElement; // container for label and value constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - this._fieldWrapper = document.createElement("div"); - this._fieldWrapper.style.width = node.attrs.width; - this._fieldWrapper.style.height = node.attrs.height; - this._fieldWrapper.style.position = "relative"; - this._fieldWrapper.style.display = "inline-block"; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; + ReactDOM.render(, this.dom); + (this as any).dom = this.dom; } _editor: EquationEditor | undefined; - setEditor = (editor?: EquationEditor) => this._editor = editor; - destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } - selectNode() { this._editor?.mathField.focus(); } - deselectNode() { } + setEditor = (editor?: EquationEditor) => (this._editor = editor); + destroy() { + ReactDOM.unmountComponentAtNode(this.dom); + } + selectNode() { + this._editor?.mathField.focus(); + } + deselectNode() {} } interface IEquationViewInternal { @@ -56,24 +56,33 @@ export class EquationViewInternal extends React.Component this._textBoxDoc = this.props.tbox.props.Document; } - componentWillUnmount() { this._reactionDisposer?.(); } - componentDidMount() { this.props.setEditor(this._ref.current ?? undefined); } + componentWillUnmount() { + this._reactionDisposer?.(); + } + componentDidMount() { + this.props.setEditor(this._ref.current ?? undefined); + } render() { - return
    - -
    ; + return ( +
    + Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text - xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView + makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text + xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView yPadding?: number; noSidebar?: boolean; dontScale?: boolean; dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field) } -export const GoogleRef = "googleDocId"; +export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps)>() { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } +export class FormattedTextBox extends ViewBoxAnnotatableComponent() { + public static LayoutString(fieldStr: string) { + return FieldView.LayoutString(FormattedTextBox, fieldStr); + } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; public static LiveTextUndo: UndoManager.Batch | undefined; - static _globalHighlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + static _globalHighlights: string[] = ['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']; static _highlightStyleSheet: any = addStyleSheet(); static _bulletStyleSheet: any = addStyleSheet(); static _userStyleSheet: any = addStyleSheet(); @@ -93,7 +94,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _ref: React.RefObject = React.createRef(); private _scrollRef: React.RefObject = React.createRef(); private _editorView: Opt; - private _applyingChange: string = ""; + private _applyingChange: string = ''; private _searchIndex = 0; private _lastTimedMark: Mark | undefined = undefined; private _cachedLinks: Doc[] = []; @@ -102,7 +103,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _dropDisposer?: DragManager.DragDropDisposer; private _recordingStart: number = 0; private _ignoreScroll = false; - private _lastText = ""; + private _lastText = ''; private _focusSpeed: Opt; private _keymap: any = undefined; private _rules: RichTextRules | undefined; @@ -113,21 +114,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _downY = 0; private _break = true; public ProseRef?: HTMLDivElement; - public get EditorView() { return this._editorView; } - public get SidebarKey() { return this.fieldKey + "-sidebar"; } - @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } - - @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } - @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } - @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; } - @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); } - @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); } - @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); } - @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } - @computed get autoHeightMargins() { return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); } - @computed get _recording() { return this.dataDoc?.mediaState === "recording"; } + public get EditorView() { + return this._editorView; + } + public get SidebarKey() { + return this.fieldKey + '-sidebar'; + } + @computed get allSidebarDocs() { + return DocListCast(this.dataDoc[this.SidebarKey]); + } + + @computed get sidebarWidthPercent() { + return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); + } + @computed get sidebarColor() { + return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '-backgroundColor'], '#e4e4e4')); + } + @computed get autoHeight() { + return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; + } + @computed get textHeight() { + return NumCast(this.rootDoc[this.fieldKey + '-height']); + } + @computed get scrollHeight() { + return NumCast(this.rootDoc[this.fieldKey + '-scrollHeight']); + } + @computed get sidebarHeight() { + return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + '-height']); + } + @computed get titleHeight() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; + } + @computed get autoHeightMargins() { + return this.titleHeight + NumCast(this.layoutDoc._autoHeightMargins); + } + @computed get _recording() { + return this.dataDoc?.mediaState === 'recording'; + } set _recording(value) { - !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined); + !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? 'recording' : undefined); } @computed get config() { this._keymap = buildKeymap(schema, this.props); @@ -140,28 +165,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp history(), keymap(this._keymap), keymap(baseKeymap), - new Plugin({ props: { attributes: { class: "ProseMirror-example-setup-style" } } }), - new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } }) - ] + new Plugin({ props: { attributes: { class: 'ProseMirror-example-setup-style' } } }), + new Plugin({ + view(editorView) { + return new FormattedTextBoxComment(editorView); + }, + }), + ], }; } public static PasteOnLoad: ClipboardEvent | undefined; - public static SelectOnLoad = ""; + public static SelectOnLoad = ''; public static DontSelectInitialText = false; // whether initial text should be selected or not - public static SelectOnLoadChar = ""; - public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } + public static SelectOnLoadChar = ''; + public static IsFragment(html: string) { + return html.indexOf('data-pm-slice') !== -1; + } public static GetHref(html: string): string { const parser = new DOMParser(); const parsedHtml = parser.parseFromString(html, 'text/html'); - if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && - (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { + if (parsedHtml.body.childNodes.length === 1 && parsedHtml.body.childNodes[0].childNodes.length === 1 && (parsedHtml.body.childNodes[0].childNodes[0] as any).href) { return (parsedHtml.body.childNodes[0].childNodes[0] as any).href; } - return ""; + return ''; } public static GetDocFromUrl(url: string) { - return url.startsWith(document.location.origin) ? new URL(url).pathname.split("doc/").lastElement() : ""; // docid + return url.startsWith(document.location.origin) ? new URL(url).pathname.split('doc/').lastElement() : ''; // docid } constructor(props: any) { @@ -172,7 +202,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } // removes all hyperlink anchors for the removed linkDoc - // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. + // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing. public RemoveLinkFromDoc(linkDoc?: Doc) { this.unhighlightSearchTerms(); @@ -195,9 +225,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - // removes all the specified link references from the selection. + // removes all the specified link references from the selection. // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references. - public RemoveAnchorFromSelection(allAnchors: { href: string, title: string, linkId: string, targetId: string }[]) { + public RemoveAnchorFromSelection(allAnchors: { href: string; title: string; linkId: string; targetId: string }[]) { const state = this._editorView?.state; if (state && this._editorView) { this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allAnchors })); @@ -205,11 +235,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - getAnchor = () => this.makeLinkAnchor(undefined, "add:right", undefined, "Anchored Selection"); + getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection'); @action setupAnchorMenu = () => { - AnchorMenu.Instance.Status = "marquee"; + AnchorMenu.Instance.Status = 'marquee'; AnchorMenu.Instance.OnClick = (e: PointerEvent) => { !this.layoutDoc.showSidebar && this.toggleSidebar(); @@ -222,14 +252,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp AnchorMenu.Instance.onMakeAnchor = this.getAnchor; AnchorMenu.Instance.StartCropDrag = unimplementedFunction; /** - * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. + * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. */ AnchorMenu.Instance.StartDrag = action(async (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); const targetCreator = (annotationOn?: Doc) => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); + const target = CurrentUserUtils.GetNewTextDoc('Note linked to ' + this.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn); FormattedTextBox.SelectOnLoad = target[Id]; return target; }; @@ -238,55 +268,56 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); - } + }; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box - const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype + const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); + const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box + const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); const effectiveAcl = GetEffectiveAcl(this.dataDoc); - const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ? - json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); + const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) { const accumTags = [] as string[]; state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => { - if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) { + if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) { accumTags.push(node.attrs.fieldKey); } }); - const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#")); + const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith('#')); const added = accumTags.filter(tag => !curTags.includes(tag)); const removed = curTags.filter(tag => !accumTags.includes(tag)); - removed.forEach(r => this.dataDoc[r] = undefined); - added.forEach(a => this.dataDoc[a] = a); + removed.forEach(r => (this.dataDoc[r] = undefined)); + added.forEach(a => (this.dataDoc[a] = a)); let unchanged = true; if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { this._applyingChange = this.fieldKey; - (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); - if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) + curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text && (this.dataDoc[this.props.fieldKey + '-lastModified'] = new DateField(new Date(Date.now()))); + if ((!curTemp && !curProto) || curText || json.includes('dash')) { + // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (removeSelection(json) !== removeSelection(curLayout?.Data)) { this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = true;//(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + this.dataDoc[this.props.fieldKey + '-noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; } - } else { // if we've deleted all the text in a note driven by a template, then restore the template data + } else { + // if we've deleted all the text in a note driven by a template, then restore the template data this.dataDoc[this.props.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have + this.dataDoc[this.props.fieldKey + '-noTemplate'] = undefined; // mark the data field as not being split from any template it might have ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; } - this._applyingChange = ""; + this._applyingChange = ''; if (!unchanged) { this.updateTitle(); this.tryUpdateScrollHeight(); @@ -304,16 +335,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp AnchorMenu.Instance.fadeOut(true); } } - } + }; - // for inserting timestamps + // for inserting timestamps insertTime = () => { let linkTime; let linkAnchor; let link; DocListCast(this.dataDoc.links).forEach((l, i) => { - const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; - if (anchor && (anchor.annotationOn as Doc).mediaState === "recording") { + const anchor = (l.anchor1 as Doc).annotationOn ? (l.anchor1 as Doc) : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; + if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') { linkTime = NumCast(anchor._timecodeToShow /* audioStart */); linkAnchor = anchor; link = l; @@ -344,7 +375,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1)))); } } - } + }; autoLink = () => { if (this._editorView?.state.doc.textContent) { @@ -355,38 +386,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp var tr = this._editorView.state.tr as any; const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; tr = tr.removeMark(0, tr.doc.content.size, autoAnch); - DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); + DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); } - } + }; updateTitle = () => { const title = StrCast(this.dataDoc.title); - if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing - (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] && - (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { + if ( + !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing + (title.startsWith('-') || title.startsWith('@')) && + this._editorView && + !this.dataDoc['title-custom'] && + (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === 'text') + ) { let node = this._editorView.state.doc; - while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; + while (node.firstChild && node.firstChild.type.name !== 'text') node = node.firstChild; const str = node.textContent; - const prefix = str.startsWith("@") ? "" : "-"; + const prefix = str.startsWith('@') ? '' : '-'; const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title)); if (!(cfield instanceof ComputedField)) { - this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); - if (str.startsWith("@") && str.length > 1) { + this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : ''); + if (str.startsWith('@') && str.length > 1) { Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, this.rootDoc); } } } - } + }; // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ''); const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { @@ -397,14 +432,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(sel.from, sel.to, splitter); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { - alink = alink ?? (DocListCast(this.Document.links).find(link => - Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && - Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, - LinkManager.AutoKeywords)!); + alink = + alink ?? + (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || + DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!); newAutoLinks.add(alink); - const allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }]; + const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); - const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" }); + const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' }); tr = tr.addMark(pos, pos + node.nodeSize, link); } }); @@ -412,13 +447,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); } return tr; - } + }; @action search = (searchString: string, bwd?: boolean, clear: boolean = false) => { if (clear) this.unhighlightSearchTerms(); else this.highlightSearchTerms([searchString], bwd!); return true; - } + }; highlightSearchTerms = (terms: string[], backward: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); @@ -432,21 +467,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (backward === true) { if (this._searchIndex > 1) { this._searchIndex += -2; - } - else if (this._searchIndex === 1) { + } else if (this._searchIndex === 1) { this._searchIndex = length - 1; - } - else if (this._searchIndex === 0 && length !== 1) { + } else if (this._searchIndex === 0 && length !== 1) { this._searchIndex = length - 2; } - } const lastSel = Math.min(flattened.length - 1, this._searchIndex); - flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); + flattened.forEach((h: TextSelection, ind: number) => (tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark))); flattened[lastSel] && this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); } - } + }; unhighlightSearchTerms = () => { if (window.screen.width < 600) null; @@ -455,20 +487,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const end = this._editorView.state.doc.nodeSize - 2; this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); - } if (FormattedTextBox.PasteOnLoad) { - const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin"); - const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfRegion"); + const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfOrigin'); + const pdfRegionId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfRegion'); FormattedTextBox.PasteOnLoad = undefined; setTimeout(() => pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, undefined), 10); } - } + }; adoptAnnotation = (start: number, end: number, mark: Mark) => { const view = this._editorView!; const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail }); view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); - } + }; protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); this.ProseRef = ele; @@ -476,8 +507,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.setupEditor(this.config, this.props.fieldKey); this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc); } - // if (this.autoHeight) this.tryUpdateScrollHeight(); - } + // if (this.autoHeight) this.tryUpdateScrollHeight(); + }; @undoBatch @action @@ -499,18 +530,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", + title: 'dashDoc', docid: target[Id], - float: "unset" + float: 'unset', }); const view = this._editorView!; view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); e.stopPropagation(); } // otherwise, fall through to outer collection to handle drop } - } + }; - getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { + getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null { let offset = 0; if (context === node) return { from: offset, to: offset + node.nodeSize }; @@ -521,8 +552,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const result = this.getNodeEndpoints((context.content as any).content[i], node); if (result) { return { - from: result.from + offset + (context.type.name === "doc" ? 0 : 1), - to: result.to + offset + (context.type.name === "doc" ? 0 : 1) + from: result.from + offset + (context.type.name === 'doc' ? 0 : 1), + to: result.to + offset + (context.type.name === 'doc' ? 0 : 1), }; } offset += (context.content as any).content[i].nodeSize; @@ -538,9 +569,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let ret: TextSelection[] = []; if (node.isTextblock) { - let index = 0, foundAt; + let index = 0, + foundAt; const ep = this.getNodeEndpoints(pm.state.doc, node); - const regexp = new RegExp(find.replace("*", ""), "i"); + const regexp = new RegExp(find.replace('*', ''), 'i'); if (regexp) { while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) { const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); @@ -549,7 +581,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } else { - node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); + node.content.forEach((child, i) => (ret = ret.concat(this.findInNode(pm, child, find)))); } return ret; } @@ -557,162 +589,178 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp updateHighlights = () => { const highlights = FormattedTextBox._globalHighlights; clearStyleSheetRules(FormattedTextBox._userStyleSheet); - if (highlights.indexOf("Audio Tags") === -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, ""); + if (highlights.indexOf('Audio Tags') === -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'audiotag', { display: 'none' }, ''); } - if (highlights.indexOf("Text from Others") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); + if (highlights.indexOf('Text from Others') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-remote', { background: 'yellow' }); } - if (highlights.indexOf("My Text") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); + if (highlights.indexOf('My Text') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { background: 'moccasin' }); } - if (highlights.indexOf("Todo Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "todo", { outline: "black solid 1px" }); + if (highlights.indexOf('Todo Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'todo', { outline: 'black solid 1px' }); } - if (highlights.indexOf("Important Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "important", { "font-size": "larger" }); + if (highlights.indexOf('Important Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'important', { 'font-size': 'larger' }); } - if (highlights.indexOf("Bold Text") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror strong > span", { "font-size": "large" }, ""); - addStyleSheetRule(FormattedTextBox._userStyleSheet, ".formattedTextBox-inner-selected .ProseMirror :not(strong > span)", { "font-size": "0px" }, ""); + if (highlights.indexOf('Bold Text') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror strong > span', { 'font-size': 'large' }, ''); + addStyleSheetRule(FormattedTextBox._userStyleSheet, '.formattedTextBox-inner-selected .ProseMirror :not(strong > span)', { 'font-size': '0px' }, ''); } - if (highlights.indexOf("Disagree Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "disagree", { "text-decoration": "line-through" }); + if (highlights.indexOf('Disagree Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'disagree', { 'text-decoration': 'line-through' }); } - if (highlights.indexOf("Ignore Items") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UT-" + "ignore", { "font-size": "1" }); + if (highlights.indexOf('Ignore Items') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UT-' + 'ignore', { 'font-size': '1' }); } - if (highlights.indexOf("By Recent Minute") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + if (highlights.indexOf('By Recent Minute') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); const min = Math.round(Date.now() / 1000 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-min-' + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); setTimeout(this.updateHighlights); } - if (highlights.indexOf("By Recent Hour") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + if (highlights.indexOf('By Recent Hour') !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-' + Doc.CurrentUserEmail.replace('.', '').replace('@', ''), { opacity: '0.1' }); const hr = Math.round(Date.now() / 1000 / 60 / 60); - numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } - } + }; @observable _showSidebar = false; - @computed get SidebarShown() { return this._showSidebar || this.layoutDoc._showSidebar ? true : false; } + @computed get SidebarShown() { + return this._showSidebar || this.layoutDoc._showSidebar ? true : false; + } @action toggleSidebar = (preview: boolean = false) => { const prevWidth = this.sidebarWidth(); if (preview) this._showSidebar = true; - else this.layoutDoc._showSidebar = ((this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, "0%") === "0%" ? "50%" : "0%")) !== "0%"; + else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); - } + }; sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); - } + }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { const bounds = this._ref.current!.getBoundingClientRect(); - this.layoutDoc._sidebarWidthPercent = "" + 100 * Math.max(0, (1 - (e.clientX - bounds.left) / bounds.width)) + "%"; - this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== "0%"; + this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; + this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%'; e.preventDefault(); return false; - } + }; specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; const changeItems: ContextMenuProps[] = []; changeItems.push({ - description: "plain", event: undoBatch(() => { + description: 'plain', + event: undoBatch(() => { Doc.setNativeView(this.rootDoc); this.layoutDoc.autoHeightMargins = undefined; - }), icon: "eye" + }), + icon: 'eye', }); changeItems.push({ - description: "metadata", event: undoBatch(() => { + description: 'metadata', + event: undoBatch(() => { this.dataDoc.layout_meta = Cast(Doc.UserDoc().emptyHeader, Doc, null)?.layout; - this.rootDoc.layoutKey = "layout_meta"; - setTimeout(() => this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50, 50); - }), icon: "eye" + this.rootDoc.layoutKey = 'layout_meta'; + setTimeout(() => (this.rootDoc._headerHeight = this.rootDoc._autoHeightMargins = 50), 50); + }), + icon: 'eye', }); - const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); + const noteTypesDoc = Cast(Doc.UserDoc()['template-notes'], Doc, null); DocListCast(noteTypesDoc?.data).forEach(note => { const icon: IconProp = StrCast(note.icon) as IconProp; changeItems.push({ - description: StrCast(note.title), event: undoBatch(() => { + description: StrCast(note.title), + event: undoBatch(() => { this.layoutDoc.autoHeightMargins = undefined; Doc.setNativeView(this.rootDoc); DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); - }), icon: icon + }), + icon: icon, }); }); const highlighting: ContextMenuProps[] = []; - const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"]; - const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; + const noviceHighlighting = ['Audio Tags', 'My Text', 'Text from Others', 'Bold Text']; + const expertHighlighting = [...noviceHighlighting, 'Important Items', 'Ignore Items', 'Disagree Items', 'By Recent Minute', 'By Recent Hour']; (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => highlighting.push({ - description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { + description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? 'Highlight ' : 'Unhighlight ') + option, + event: () => { e.stopPropagation(); if (FormattedTextBox._globalHighlights.indexOf(option) === -1) { FormattedTextBox._globalHighlights.push(option); } else { FormattedTextBox._globalHighlights.splice(FormattedTextBox._globalHighlights.indexOf(option), 1); } - runInAction(() => this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join("")); + runInAction(() => (this.layoutDoc._highlights = FormattedTextBox._globalHighlights.join(''))); this.updateHighlights(); - }, icon: "expand-arrows-alt" - })); + }, + icon: 'expand-arrows-alt', + }) + ); const uicontrols: ContextMenuProps[] = []; - !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); - uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); - uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); - uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); - !Doc.noviceMode && uicontrols.push({ - description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => - proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" - }); - cm.addItem({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); + !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' }); + uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' }); + uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' }); + uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); + !Doc.noviceMode && + uicontrols.push({ + description: 'Broadcast Message', + event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), + icon: 'expand-arrows-alt', + }); + cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' }); - const appearance = cm.findByDescription("Appearance..."); - const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; - appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); + const appearance = cm.findByDescription('Appearance...'); + const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; + appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' }); // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); - !Doc.noviceMode && appearanceItems.push({ - description: "Make Default Layout", event: () => { - if (!this.layoutDoc.isTemplateDoc) { - const title = StrCast(this.rootDoc.title); - this.rootDoc.title = "text"; - MakeTemplate(this.rootDoc, true, title); - } else if (!this.rootDoc.isTemplateDoc) { - const title = StrCast(this.rootDoc.title); - this.rootDoc.title = "text"; - this.rootDoc.layout = this.layoutDoc.layout as string; - this.rootDoc.title = this.layoutDoc.isTemplateForField as string; - this.rootDoc.isTemplateDoc = false; - this.rootDoc.isTemplateForField = ""; - this.rootDoc.layoutKey = "layout"; - MakeTemplate(this.rootDoc, true, title); - setTimeout(() => { - this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height - this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template - this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields - this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); - this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, "string", null); - }, 10); - } - Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc); - Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); - }, icon: "eye" - }); - cm.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + !Doc.noviceMode && + appearanceItems.push({ + description: 'Make Default Layout', + event: () => { + if (!this.layoutDoc.isTemplateDoc) { + const title = StrCast(this.rootDoc.title); + this.rootDoc.title = 'text'; + MakeTemplate(this.rootDoc, true, title); + } else if (!this.rootDoc.isTemplateDoc) { + const title = StrCast(this.rootDoc.title); + this.rootDoc.title = 'text'; + this.rootDoc.layout = this.layoutDoc.layout as string; + this.rootDoc.title = this.layoutDoc.isTemplateForField as string; + this.rootDoc.isTemplateDoc = false; + this.rootDoc.isTemplateForField = ''; + this.rootDoc.layoutKey = 'layout'; + MakeTemplate(this.rootDoc, true, title); + setTimeout(() => { + this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height + this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template + this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields + this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, 'string', null); + this.rootDoc.backgroundColor = Cast(this.layoutDoc.backgroundColor, 'string', null); + }, 10); + } + Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc); + Doc.AddDocToList(Cast(Doc.UserDoc()['template-notes'], Doc, null), 'data', this.rootDoc); + }, + icon: 'eye', + }); + cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); - const options = cm.findByDescription("Options..."); - const optionItems = options && "subitems" in options ? options.subitems : []; - optionItems.push({ description: !this.Document._singleLine ? "Make Single Line" : "Make Multi Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - optionItems.push({ description: `${this.Document._autoHeight ? "Lock" : "Auto"} Height`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); - !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); + const options = cm.findByDescription('Options...'); + const optionItems = options && 'subitems' in options ? options.subitems : []; + optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: 'expand-arrows-alt' }); + optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: 'plus' }); + !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; - } + }; breakupDictation = () => { if (this._editorView && this._recording) { @@ -721,12 +769,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const state = this._editorView.state; const to = state.selection.to; const updated = TextSelection.create(state.doc, to, to); - this._editorView.dispatch(state.tr.setSelection(updated).insertText("\n", to)); + this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to)); if (this._recording) { this.recordDictation(); } } - } + }; recordDictation = () => { DictationManager.Controls.listen({ interimHandler: this.setDictationContent, @@ -736,26 +784,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DictationManager.Controls.stop(); } }); - } + }; stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort); setDictationContent = (value: string) => { if (this._editorView && this._recordingStart) { if (this._break) { - const textanchor = Docs.Create.TextanchorDocument({ title: "dictation anchor" }); + const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' }); this.addDocument(textanchor); const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement(); link && (Doc.GetProto(link).isDictation = true); if (!link) return; const audioanchor = Cast(link.anchor2, Doc, null); if (!audioanchor) return; - audioanchor.backgroundColor = "tan"; + audioanchor.backgroundColor = 'tan'; const audiotag = this._editorView.state.schema.nodes.audiotag.create({ timeCode: NumCast(audioanchor._timecodeToShow), audioId: audioanchor[Id], - textId: textanchor[Id] + textId: textanchor[Id], }); - Doc.GetProto(textanchor).title = "dictation:" + audiotag.attrs.timeCode; + Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode; const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag); const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size)); this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size))); @@ -765,7 +813,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const tr = this._editorView.state.tr.insertText(value); this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, from, tr.doc.content.size)).scrollIntoView()); } - } + }; // TODO: nda -- Look at how link anchors are added makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string) { @@ -775,7 +823,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); if (sel.from !== sel.to) { - const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: "#" + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); + const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); const href = targetHref ?? Doc.localServerPath(anchor); if (anchor !== anchorDoc) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { @@ -786,7 +834,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(pos, pos + node.nodeSize, link); } }); - this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents + this.dataDoc[ForceServerWrite] = this.dataDoc[UpdatingFromServer] = true; // need to allow permissions for adding links to readonly/augment only documents this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; return anchor; @@ -797,7 +845,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } scrollFocus = (textAnchor: Doc, smooth: boolean) => { - if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) { + if (DocListCast(this.Document[this.fieldKey + '-sidebar']).includes(textAnchor) && !this.SidebarShown) { this.toggleSidebar(!smooth); } const textAnchorId = textAnchor[Id]; @@ -827,7 +875,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor); - return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, "")) ? { node, start: 0 } : undefined; + return linkIndex !== -1 && marks[linkIndex].attrs.allAnchors.find((item: { href: string }) => textAnchorId === item.href.replace(/.*\/doc\//, '')) ? { node, start: 0 } : undefined; }; let start = 0; @@ -845,59 +893,69 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); const escAnchorId = textAnchorId[0] >= '0' && textAnchorId[0] <= '9' ? `\\3${textAnchorId[0]} ${textAnchorId.substr(1)}` : textAnchorId; - addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow", transform: "scale(3)", "transform-origin": "left bottom" }); - setTimeout(() => this._focusSpeed = undefined, this._focusSpeed); + addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: 'yellow', transform: 'scale(3)', 'transform-origin': 'left bottom' }); + setTimeout(() => (this._focusSpeed = undefined), this._focusSpeed); setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this._focusSpeed || 0, 3000)); } } return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed - } + }; getScrollHeight = () => this.scrollHeight; // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. // Since we also monitor all component height changes, this will update the document's height. resetNativeHeight = (scrollHeight: number) => { const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.layoutDoc._nativeHeight); - this.rootDoc[this.fieldKey + "-height"] = scrollHeight; + this.rootDoc[this.fieldKey + '-height'] = scrollHeight; if (nh) this.layoutDoc._nativeHeight = scrollHeight; - } + }; - @computed get contentScaling() { return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;} + @computed get contentScaling() { + return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1; + } componentDidMount() { !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._cachedLinks = DocListCast(this.Document.links); this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation); - this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight()); - this._disposers.scrollHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), + this._disposers.autoHeight = reaction( + () => this.autoHeight, + autoHeight => autoHeight && this.tryUpdateScrollHeight() + ); + this._disposers.scrollHeight = reaction( + () => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }), ({ width, scrollHeight, autoHeight }) => { width && autoHeight && this.resetNativeHeight(scrollHeight); - }, { fireImmediately: true } + }, + { fireImmediately: true } ); - this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on + this._disposers.componentHeights = reaction( + // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight))); - }, { fireImmediately: true }); - this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks + }, + { fireImmediately: true } + ); + this._disposers.links = reaction( + () => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); this._cachedLinks = newLinks; - }); + } + ); this._disposers.buttonBar = reaction( () => DocumentButtonBar.Instance, instance => { if (instance) { this.pullFromGoogleDoc(this.checkState); - this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => instance.isAnimatingFetch = true); + this.dataDoc[GoogleRef] && this.dataDoc.googleDocUnchanged && runInAction(() => (instance.isAnimatingFetch = true)); } } ); this._disposers.editorState = reaction( () => { - const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : - this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey] ? - this.dataDoc : this.layoutDoc; + const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : this.dataDoc?.[this.props.fieldKey + '-noTemplate'] || !this.layoutDoc[this.props.fieldKey] ? this.dataDoc : this.layoutDoc; return !whichDoc ? undefined : { data: Cast(whichDoc[this.props.fieldKey], RichTextField, null), str: StrCast(whichDoc[this.props.fieldKey]) }; }, incomingValue => { @@ -912,7 +970,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); } } - }, + } ); this._disposers.pullDoc = reaction( () => this.props.Document[Pulls], @@ -933,13 +991,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } ); - this._disposers.search = reaction(() => Doc.IsSearchMatch(this.rootDoc), - search => search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms(), - { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false }); + this._disposers.search = reaction( + () => Doc.IsSearchMatch(this.rootDoc), + search => (search ? this.highlightSearchTerms([Doc.SearchQuery()], search.searchMatch < 0) : this.unhighlightSearchTerms()), + { fireImmediately: Doc.IsSearchMatchUnmemoized(this.rootDoc) ? true : false } + ); - this._disposers.selected = reaction(() => this.props.isSelected(), + this._disposers.selected = reaction( + () => this.props.isSelected(), action(selected => { - this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join("") : ""; + this.layoutDoc._highlights = selected ? FormattedTextBox._globalHighlights.join('') : ''; if (RichTextMenu.Instance?.view === this._editorView && !selected) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } @@ -947,21 +1008,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); this.autoLink(); } - }), { fireImmediately: true }); + }), + { fireImmediately: true } + ); if (!this.props.dontRegisterView) { - this._disposers.record = reaction(() => this._recording, + this._disposers.record = reaction( + () => this._recording, () => { this.stopDictation(true); if (this._recording) { this.recordDictation(); } - }, + } ); if (this._recording) setTimeout(this.recordDictation); } - var quickScroll: string | undefined = ""; - this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop), + var quickScroll: string | undefined = ''; + this._disposers.scroll = reaction( + () => NumCast(this.layoutDoc._scrollTop), pos => { if (!this._ignoreScroll && this._scrollRef.current && !this.props.dontSelectOnLoad) { const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); @@ -974,7 +1039,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._scrollRef.current.scrollTo({ top: pos }); } } - }, { fireImmediately: true } + }, + { fireImmediately: true } ); quickScroll = undefined; this.tryUpdateScrollHeight(); @@ -984,7 +1050,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.pullFromGoogleDoc(async (exportState: Opt, dataDoc: Doc) => { const modes = GoogleApiClientUtils.Docs.WriteMode; let mode = modes.Replace; - let reference: Opt = Cast(this.dataDoc[GoogleRef], "string"); + let reference: Opt = Cast(this.dataDoc[GoogleRef], 'string'); if (!reference) { mode = modes.Insert; reference = { title: StrCast(this.dataDoc.title) }; @@ -994,7 +1060,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const content = await RichTextUtils.GoogleDocs.Export(this._editorView.state); const response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); response && (this.dataDoc[GoogleRef] = response.documentId); - const pushSuccess = response !== undefined && !("errors" in response); + const pushSuccess = response !== undefined && !('errors' in response); dataDoc.googleDocUnchanged = pushSuccess; DocumentButtonBar.Instance.startPushOutcome(pushSuccess); } @@ -1003,15 +1069,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (exportState && reference) { const content: GoogleApiClientUtils.Docs.Content = { text: exportState.text, - requests: [] + requests: [], }; GoogleApiClientUtils.Docs.write({ reference, content, mode }); } }; - UndoManager.AddEvent({ undo, redo, prop: "" }); + UndoManager.AddEvent({ undo, redo, prop: '' }); redo(); }); - } + }; pullFromGoogleDoc = async (handler: PullHandler) => { const dataDoc = this.dataDoc; @@ -1021,7 +1087,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp exportState = await RichTextUtils.GoogleDocs.Import(documentId, dataDoc); } exportState && UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); - } + }; updateState = (exportState: Opt, dataDoc: Doc) => { let pullSuccess = false; @@ -1036,13 +1102,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } }, 0); dataDoc.title = exportState.title; - this.dataDoc["title-custom"] = true; + this.dataDoc['title-custom'] = true; dataDoc.googleDocUnchanged = true; } else { delete dataDoc[GoogleRef]; } DocumentButtonBar.Instance.startPullOutcome(pullSuccess); - } + }; checkState = (exportState: Opt, dataDoc: Doc) => { if (exportState && this._editorView) { @@ -1052,56 +1118,61 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp dataDoc.googleDocUnchanged = unchanged; DocumentButtonBar.Instance.setPullState(unchanged); } - } + }; clipboardTextSerializer = (slice: Slice): string => { - let text = "", separated = true; - const from = 0, to = slice.content.size; - slice.content.nodesBetween(from, to, (node, pos) => { - if (node.isText) { - text += node.text!.slice(Math.max(from, pos) - pos, to - pos); - separated = false; - } else if (!separated && node.isBlock) { - text += "\n"; - separated = true; - } else if (node.type.name === "hard_break") { - text += "\n"; - } - }, 0); + let text = '', + separated = true; + const from = 0, + to = slice.content.size; + slice.content.nodesBetween( + from, + to, + (node, pos) => { + if (node.isText) { + text += node.text!.slice(Math.max(from, pos) - pos, to - pos); + separated = false; + } else if (!separated && node.isBlock) { + text += '\n'; + separated = true; + } else if (node.type.name === 'hard_break') { + text += '\n'; + } + }, + 0 + ); return text; - } + }; handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { const cbe = event as ClipboardEvent; - const pdfDocId = cbe.clipboardData?.getData("dash/pdfOrigin"); - const pdfRegionId = cbe.clipboardData?.getData("dash/pdfRegion"); + const pdfDocId = cbe.clipboardData?.getData('dash/pdfOrigin'); + const pdfRegionId = cbe.clipboardData?.getData('dash/pdfRegion'); return pdfDocId && pdfRegionId && this.addPdfReference(pdfDocId, pdfRegionId, slice) ? true : false; - } + }; addPdfReference = (pdfDocId: string, pdfRegionId: string, slice?: Slice) => { const view = this._editorView!; if (pdfDocId && pdfRegionId) { DocServer.GetRefField(pdfDocId).then(pdfDoc => { DocServer.GetRefField(pdfRegionId).then(pdfRegion => { - if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { + if (pdfDoc instanceof Doc && pdfRegion instanceof Doc) { setTimeout(async () => { const targetField = Doc.LayoutFieldKey(pdfDoc); - const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + "-annotations"]);// bcz: better to have the PDF's view handle updating its own annotations + const targetAnnotations = await DocListCastAsync(pdfDoc[DataSym][targetField + '-annotations']); // bcz: better to have the PDF's view handle updating its own annotations if (targetAnnotations) targetAnnotations.push(pdfRegion); - else Doc.AddDocToList(pdfDoc[DataSym], targetField + "-annotations", pdfRegion); + else Doc.AddDocToList(pdfDoc[DataSym], targetField + '-annotations', pdfRegion); }); - const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, "PDF pasted"); + const link = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: pdfRegion }, 'PDF pasted'); if (link) { const linkId = link[Id]; - const quote = view.state.schema.nodes.blockquote.create(); - quote.content = addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); + const quote = view.state.schema.nodes.blockquote.create({ content: addMarkToFrag(slice?.content || view.state.doc.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)) }); const newSlice = new Slice(Fragment.from(quote), slice?.openStart || 0, slice?.openEnd || 0); if (slice) { - view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); + view.dispatch(view.state.tr.replaceSelection(newSlice).scrollIntoView().setMeta('paste', true).setMeta('uiEvent', 'paste')); } else { selectAll(view.state, (tx: Transaction) => view.dispatch(tx.replaceSelection(newSlice).scrollIntoView())); - } } } @@ -1111,31 +1182,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } return false; - function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { const nodes: Node[] = []; frag.forEach(node => nodes.push(marker(node))); return Fragment.fromArray(nodes); } - function addLinkMark(node: Node, title: string, linkId: string) { if (!node.isText) { const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); return node.copy(content); } const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type.name === "link"); + const linkIndex = marks.findIndex(mark => mark.type.name === 'link'); const allLinks = [{ href: Doc.globalServerPath(linkId), title, linkId }]; - const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "add:right", title, docref: true }); + const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: 'add:right', title, docref: true }); marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); return node.mark(marks); } - } + }; isActiveTab(el: Element | null | undefined) { while (el && el !== document.body) { - if (getComputedStyle(el).display === "none") return false; + if (getComputedStyle(el).display === 'none') return false; el = el.parentNode as any; } return true; @@ -1147,7 +1216,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp view(newView) { runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); return new RichTextMenuPlugin({ editorProps: this.props }); - } + }, }); } _didScroll = false; @@ -1159,7 +1228,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView?.destroy(); this._editorView = new EditorView(this.ProseRef, { state: rtfField?.Data ? EditorState.fromJSON(config, JSON.parse(rtfField.Data)) : EditorState.create(config), - handleScrollToSelection: (editorView) => { + handleScrollToSelection: editorView => { const docPos = editorView.coordsAtPos(editorView.state.selection.to); const viewRect = self._ref.current!.getBoundingClientRect(); const scrollRef = self._scrollRef.current; @@ -1179,13 +1248,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, dispatchTransaction: this.dispatchTransaction, nodeViews: { - dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, - dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, self); }, - dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, - equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, - summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, - ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, - footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); } + dashComment(node: any, view: any, getPos: any) { + return new DashDocCommentView(node, view, getPos); + }, + dashDoc(node: any, view: any, getPos: any) { + return new DashDocView(node, view, getPos, self); + }, + dashField(node: any, view: any, getPos: any) { + return new DashFieldView(node, view, getPos, self); + }, + equation(node: any, view: any, getPos: any) { + return new EquationView(node, view, getPos, self); + }, + summary(node: any, view: any, getPos: any) { + return new SummaryView(node, view, getPos); + }, + //ordered_list(node: any, view: any, getPos: any) { return new OrderedListView(); }, + footnote(node: any, view: any, getPos: any) { + return new FootnoteView(node, view, getPos); + }, }, clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, @@ -1196,9 +1277,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (startupText) { dispatch(state.tr.insertText(startupText)); } - const textAlign = StrCast(this.dataDoc["text-align"], StrCast(Doc.UserDoc().textAlign, "left")); - if (textAlign !== "left") { - selectAll(this._editorView.state, (tr) => { + const textAlign = StrCast(this.dataDoc['text-align'], StrCast(Doc.UserDoc().textAlign, 'left')); + if (textAlign !== 'left') { + selectAll(this._editorView.state, tr => { this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: textAlign }))); }); } @@ -1209,14 +1290,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())); if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { const selLoadChar = FormattedTextBox.SelectOnLoadChar; - FormattedTextBox.SelectOnLoad = ""; + FormattedTextBox.SelectOnLoad = ''; this.props.select(false); if (selLoadChar && this._editorView) { const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; - const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks); + const tr = this._editorView.state.tr + .setStoredMarks(storedMarks) + .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size) + .setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { selectAll(this._editorView.state, this._editorView?.dispatch); @@ -1229,14 +1313,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selectOnLoad && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) { - this._editorView.state.storedMarks = [...(this._editorView.state.storedMarks ?? []), - schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), - ...(Doc.UserDoc().fontColor !== "transparent" && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), - ...(Doc.UserDoc().fontStyle === "italics" ? [schema.mark(schema.marks.em)] : []), - ...(Doc.UserDoc().textDecoration === "underline" ? [schema.mark(schema.marks.underline)] : []), - ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), - ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), - ...(Doc.UserDoc().fontWeight === "bold" ? [schema.mark(schema.marks.strong)] : [])]; + this._editorView.state.storedMarks = [ + ...(this._editorView.state.storedMarks ?? []), + schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), + ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), + ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), + ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), + ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), + ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), + ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), + ]; } } @@ -1247,13 +1333,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.unhighlightSearchTerms(); this._editorView?.destroy(); RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); - FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); + FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = 'none'); } onPointerDown = (e: React.PointerEvent): void => { if (this.Document.forceActive) e.stopPropagation(); this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view. - if ((e.target as any).tagName === "AUDIOTAG") { + if ((e.target as any).tagName === 'AUDIOTAG') { e.preventDefault(); e.stopPropagation(); const timecode = Number((e.target as any)?.dataset?.timecode); @@ -1264,10 +1350,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const func = () => { const docView = DocumentManager.Instance.getDocumentView(audiodoc); if (!docView) { - this.props.addDocTab(audiodoc, "add:bottom"); + this.props.addDocTab(audiodoc, 'add:bottom'); setTimeout(func); - } - else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that + } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, 'number', null)); // bcz: would be nice to find the next audio tag in the doc and play until that }; func(); } @@ -1283,29 +1368,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._downEvent = true; FormattedTextBoxComment.textBox = this; if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { + // stop propagation if not in sidebar // bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView, // but that's changed, so this shouldn't be needed. //e.stopPropagation(); // if the text box is selected, then it consumes all down events - document.addEventListener("pointerup", this.onSelectEnd); - document.addEventListener("pointermove", this.onSelectMove); + document.addEventListener('pointerup', this.onSelectEnd); + document.addEventListener('pointermove', this.onSelectMove); } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { e.preventDefault(); } - } + }; onSelectMove = (e: PointerEvent) => e.stopPropagation(); onSelectEnd = (e: PointerEvent) => { - document.removeEventListener("pointerup", this.onSelectEnd); - document.removeEventListener("pointermove", this.onSelectMove); - } + document.removeEventListener('pointerup', this.onSelectEnd); + document.removeEventListener('pointermove', this.onSelectMove); + }; onPointerUp = (e: React.PointerEvent): void => { if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; if ((e.nativeEvent as any).formattedHandled) { - console.log("handled"); + console.log('handled'); } if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) { const editor = this._editorView!; @@ -1320,13 +1406,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } - } + }; @action onDoubleClick = (e: React.MouseEvent): void => { FormattedTextBoxComment.textBox = this; if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { - if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar - e.stopPropagation(); // if the text box is selected, then it consumes all click events + if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { + // stop propagation if not in sidebar + e.stopPropagation(); // if the text box is selected, then it consumes all click events } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { @@ -1339,18 +1426,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } - } + }; setFocus = () => { const pos = this._editorView?.state.selection.$from.pos || 1; (this.ProseRef?.children?.[0] as any).focus(); setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); - } + }; @action onFocused = (e: React.FocusEvent): void => { //applyDevTools.applyDevTools(this._editorView); FormattedTextBox.Focused = this; this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); - } + }; @observable public static Focused: FormattedTextBox | undefined; onClick = (e: React.MouseEvent): void => { @@ -1358,7 +1445,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._forceDownNode = undefined; return; } - if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. + if (!this._forceUncollapse || (this._editorView!.root as any).getSelection().isCollapsed) { + // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { @@ -1368,13 +1456,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!node && this.ProseRef) { const lastNode = this.ProseRef.children[this.ProseRef.children.length - 1].children[this.ProseRef.children[this.ProseRef.children.length - 1].children.length - 1]; // get the last prosemirror div const boundsRect = lastNode?.getBoundingClientRect(); - if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && - e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document + if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && e.clientY > boundsRect.bottom) { + // if we clicked below the last prosemirror div, then set the selection to be the end of the document this._editorView?.focus(); this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size))); } - } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) && - node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) { + } else if (node && [this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node.type) && node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos))); } } @@ -1382,7 +1469,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); return; } - if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events + if (this.props.isSelected(true)) { + // if text box is selected, then it consumes all click events (e.nativeEvent as any).formattedHandled = true; if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it. // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment) @@ -1390,7 +1478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed; this._forceDownNode = (this._editorView!.state.selection as NodeSelection)?.node; - } + }; // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean, downNode: Node | undefined = undefined, selectOrderedList: boolean = false) { this._forceUncollapse = false; @@ -1420,7 +1508,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr.setSelection(TextSelection.create(tr.doc, clickPos.pos))); } } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ':hover:before', { background: 'lightgray' }); } } } @@ -1432,20 +1520,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. if (view.mouseDown) { const originalUpHandler = view.mouseDown.up; - view.root.removeEventListener("mouseup", originalUpHandler); + view.root.removeEventListener('mouseup', originalUpHandler); view.mouseDown.up = (e: MouseEvent) => { if (!(e as any).formattedHandled) { originalUpHandler(e); (e as any).formattedHandled = true; } else { - console.log("prehandled"); + console.log('prehandled'); } }; - view.root.addEventListener("mouseup", view.mouseDown.up); + view.root.addEventListener('mouseup', view.mouseDown.up); } - } + }; startUndoTypingBatch() { - !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); + !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping')); } public endUndoTypingBatch() { const wasUndoing = this._undoTyping; @@ -1461,33 +1549,39 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } - FormattedTextBox._hadSelection = window.getSelection()?.toString() !== ""; + FormattedTextBox._hadSelection = window.getSelection()?.toString() !== ''; this.endUndoTypingBatch(); FormattedTextBox.LiveTextUndo?.end(); FormattedTextBox.LiveTextUndo = undefined; const state = this._editorView!.state; - const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) { + const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); + if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) { try { - translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => { - setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => { - this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0]; - }), 1000); + translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => { + setTimeout( + () => + translateGoogleApi(result1[0], { from: 'es', to: 'en' }).then((result: any) => { + this.dataDoc[this.fieldKey + '-translation'] = result1 + '\r\n\r\n' + result[0]; + }), + 1000 + ); }); - } catch (e: any) { console.log(e.message); } + } catch (e: any) { + console.log(e.message); + } this._lastText = curText; } - if (StrCast(this.rootDoc.title).startsWith("@") && !this.dataDoc["title-custom"]) { + if (StrCast(this.rootDoc.title).startsWith('@') && !this.dataDoc['title-custom']) { UndoManager.RunInBatch(() => { - this.dataDoc["title-custom"] = true; - this.dataDoc.showTitle = "title"; + this.dataDoc['title-custom'] = true; + this.dataDoc.showTitle = 'title'; const tr = this._editorView!.state.tr; this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection()); - }, "titler"); + }, 'titler'); } - } + }; onKeyDown = (e: React.KeyboardEvent) => { if (e.altKey) { @@ -1495,7 +1589,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return; } const state = this._editorView!.state; - if (!state.selection.empty && e.key === "%") { + if (!state.selection.empty && e.key === '%') { this._rules!.EnteringStyle = true; e.preventDefault(); e.stopPropagation(); @@ -1508,32 +1602,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); for (var i = state.selection.from; i <= state.selection.to; i++) { const node = state.doc.resolve(i); - if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && - mark.attrs.userid !== Doc.CurrentUserEmail) && - [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) { + if (state.doc.content.size - 1 > i && node?.marks?.().some(mark => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail) && [AclAugment, AclSelfEdit].includes(GetEffectiveAcl(this.rootDoc))) { e.preventDefault(); } } switch (e.key) { - case "Escape": + case 'Escape': this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); (document.activeElement as any).blur?.(); SelectionManager.DeselectAll(); RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); return; - case "Enter": this.insertTime(); - case "Tab": e.preventDefault(); break; - default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; - case " ": - [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})) - .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); + case 'Enter': + this.insertTime(); + case 'Tab': + e.preventDefault(); + break; + default: + if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; + case ' ': + [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } this.startUndoTypingBatch(); - } + }; ondrop = (e: React.DragEvent) => { this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); e.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. - } + }; onScroll = (e: React.UIEvent) => { if (!LinkDocPreview.LinkInfo && this._scrollRef.current) { if (!this.props.dontSelectOnLoad) { @@ -1542,15 +1638,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._ignoreScroll = false; } } - } + }; tryUpdateScrollHeight = () => { const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; if (children) { - const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); + const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins); const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); - if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; + if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { + // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight); if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); } else { @@ -1558,7 +1655,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - } + }; fitContentsToBox = () => this.props.Document._fitContentsToBox; sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { @@ -1566,40 +1663,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // console.log("printting allSideBarDocs"); // console.log(this.allSidebarDocs); return this.addDocument(doc, sidebarKey); - } + }; sidebarMoveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.moveDocument(doc, targetCollection, addDocument, this.SidebarKey); sidebarRemDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, this.SidebarKey); - setSidebarHeight = (height: number) => this.rootDoc[this.SidebarKey + "-height"] = height; - sidebarWidth = () => Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); - sidebarScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0).scale(1 / NumCast(this.layoutDoc._viewScale, 1)); + setSidebarHeight = (height: number) => (this.rootDoc[this.SidebarKey + '-height'] = height); + sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this.props.PanelWidth(); + sidebarScreenToLocal = () => + this.props + .ScreenToLocalTransform() + .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0) + .scale(1 / NumCast(this.layoutDoc._viewScale, 1)); @computed get audioHandle() { - return
    this._recording = !this._recording)} > - -
    ; + return ( +
    (this._recording = !this._recording))}> + +
    + ); } @computed get sidebarHandle() { TraceMobx(); const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; const color = !annotated ? Colors.WHITE : Colors.BLACK; - const backgroundColor = !annotated ? this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")); + const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : '')); - return (!annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging())) ? (null) : -
    - -
    ; + opacity: annotated ? 1 : undefined, + }}> + +
    + ); } @computed get sidebarCollection() { const renderComponent = (tag: string) => { - const ComponentTag = tag === "freeform" ? CollectionFreeFormView : tag === "translation" ? FormattedTextBox : CollectionStackingView; - return ComponentTag === CollectionStackingView ? - : + /> + ) : ( ; + fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} + /> + ); }; - return
    - {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} -
    ; + return ( +
    + {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} +
    + ); } render() { TraceMobx(); const selected = this.props.isSelected() || this.Document.forceActive; const active = this.props.isContentActive(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); - const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : ''; const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); - const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0); - const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : ""; - const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., - return (styleFromString?.height === "0px" ? (null) : -
    = 10 ? '-selected' : ''; + const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., + return styleFromString?.height === '0px' ? null : ( +
    this.props.isContentActive() && e.stopPropagation()} style={{ transform: this.props.dontScale ? undefined : `scale(${scale})`, - transformOrigin: this.props.dontScale ? undefined : "top left", + transformOrigin: this.props.dontScale ? undefined : 'top left', width: this.props.dontScale ? undefined : `${100 / scale}%`, height: this.props.dontScale ? undefined : `${100 / scale}%`, // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, - ...styleFromString + ...styleFromString, }}> -
    -
    +
    -
    +
    - {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === "0%" ? (null) : this.sidebarCollection} - {(this.props.noSidebar || this.Document._noSidebar) || this.props.dontSelectOnLoad || this.Document._singleLine ? (null) : this.sidebarHandle} - {!this.layoutDoc._showAudio ? (null) : this.audioHandle} + {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} + {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} + {!this.layoutDoc._showAudio ? null : this.audioHandle}
    -
    +
    ); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 3e673c0b2..bdf59863b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -1,18 +1,25 @@ -import { Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../../fields/Doc"; -import { DocServer } from "../../../DocServer"; -import { LinkDocPreview } from "../LinkDocPreview"; -import { FormattedTextBox } from "./FormattedTextBox"; +import { Mark, ResolvedPos } from 'prosemirror-model'; +import { EditorState } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view'; +import { Doc } from '../../../../fields/Doc'; +import { DocServer } from '../../../DocServer'; +import { LinkDocPreview } from '../LinkDocPreview'; +import { FormattedTextBox } from './FormattedTextBox'; import './FormattedTextBoxComment.scss'; -import { schema } from "./schema_rts"; +import { schema } from './schema_rts'; -export function findOtherUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); } -export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); } -export function findLinkMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); } -export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let before = 0, nbef = rpos.nodeBefore; +export function findOtherUserMark(marks: readonly Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid && m.attrs.userid !== Doc.CurrentUserEmail); +} +export function findUserMark(marks: readonly Mark[]): Mark | undefined { + return marks.find(m => m.attrs.userid); +} +export function findLinkMark(marks: readonly Mark[]): Mark | undefined { + return marks.find(m => m.type === schema.marks.autoLinkAnchor || m.type === schema.marks.linkAnchor); +} +export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) { + let before = 0, + nbef = rpos.nodeBefore; while (nbef && finder(nbef.marks)) { before += nbef.nodeSize; rpos = view.state.doc.resolve(rpos.pos - nbef.nodeSize); @@ -20,8 +27,9 @@ export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (ma } return before; } -export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { - let after = 0, naft = rpos.nodeAfter; +export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: readonly Mark[]) => Mark | undefined) { + let after = 0, + naft = rpos.nodeAfter; while (naft && finder(naft.marks)) { after += naft.nodeSize; rpos = view.state.doc.resolve(rpos.pos + naft.nodeSize); @@ -32,7 +40,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark // this view appears when clicking on text that has a hyperlink which is configured to show a preview of its target. // this will also display metadata information about text when the view is configured to display things like other people who authored text. -// +// export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; @@ -43,11 +51,11 @@ export class FormattedTextBoxComment { constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { - const tooltip = FormattedTextBoxComment.tooltip = document.createElement("div"); - const tooltipText = FormattedTextBoxComment.tooltipText = document.createElement("div"); - tooltip.className = "FormattedTextBox-tooltip"; - tooltipText.className = "FormattedTextBox-tooltipText"; - tooltip.style.display = "none"; + const tooltip = (FormattedTextBoxComment.tooltip = document.createElement('div')); + const tooltipText = (FormattedTextBoxComment.tooltipText = document.createElement('div')); + tooltip.className = 'FormattedTextBox-tooltip'; + tooltipText.className = 'FormattedTextBox-tooltipText'; + tooltip.style.display = 'none'; tooltip.appendChild(tooltipText); tooltip.onpointerdown = (e: PointerEvent) => { const { textBox, startUserMarkRegion, endUserMarkRegion, userMark } = FormattedTextBoxComment; @@ -55,38 +63,47 @@ export class FormattedTextBoxComment { e.stopPropagation(); e.preventDefault(); }; - document.getElementById("root")?.appendChild(tooltip); + document.getElementById('root')?.appendChild(tooltip); } } public static Hide() { FormattedTextBoxComment.textBox = undefined; - FormattedTextBoxComment.tooltip.style.display = "none"; + FormattedTextBoxComment.tooltip.style.display = 'none'; } public static saveMarkRegion(textBox: any, start: number, end: number, mark: Mark) { FormattedTextBoxComment.textBox = textBox; FormattedTextBoxComment.startUserMarkRegion = start; FormattedTextBoxComment.endUserMarkRegion = end; FormattedTextBoxComment.userMark = mark; - FormattedTextBoxComment.tooltip.style.display = ""; + FormattedTextBoxComment.tooltip.style.display = ''; } static showCommentbox(view: EditorView, nbef: number) { const state = view.state; // These are in screen coordinates - const start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + const start = view.coordsAtPos(state.selection.from - nbef), + end = view.coordsAtPos(state.selection.from - nbef); // The box in which the tooltip is positioned, to use as base - const box = (document.getElementsByClassName("mainView-container") as any)[0].getBoundingClientRect(); + const box = (document.getElementsByClassName('mainView-container') as any)[0].getBoundingClientRect(); // Find a center-ish x position from the selection endpoints (when crossing lines, end may be more to the left) const left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; - FormattedTextBoxComment.tooltip.style.display = ""; + FormattedTextBoxComment.tooltip.style.left = left - box.left + 'px'; + FormattedTextBoxComment.tooltip.style.bottom = box.bottom - start.top + 'px'; + FormattedTextBoxComment.tooltip.style.display = ''; } - static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "", linkDoc: string = "") { + static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '') { FormattedTextBoxComment.textBox = textBox; - if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { - FormattedTextBoxComment.setupPreview(view, textBox, hrefs?.trim().split(" ").filter(h => h), linkDoc); + if (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection)) { + FormattedTextBoxComment.setupPreview( + view, + textBox, + hrefs + ?.trim() + .split(' ') + .filter(h => h), + linkDoc + ); } } @@ -104,25 +121,27 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.saveMarkRegion(textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } if (mark && child && ((nbef && naft) || !noselection)) { - FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " on " + (new Date(mark.attrs.modified * 1000)).toLocaleString(); + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + ' on ' + new Date(mark.attrs.modified * 1000).toLocaleString(); FormattedTextBoxComment.showCommentbox(view, nbef); } else FormattedTextBoxComment.Hide(); } - // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. if (state.selection.$from && hrefs?.length) { const nbef = findStartOfMark(state.selection.$from, view, findLinkMark); const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; - nbef && naft && LinkDocPreview.SetLinkInfo({ - docProps: textBox.props, - linkSrc: textBox.rootDoc, - linkDoc: linkDoc ? DocServer.GetCachedRefField(linkDoc) as Doc : undefined, - location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))), - hrefs, - showHeader: true - }); + nbef && + naft && + LinkDocPreview.SetLinkInfo({ + docProps: textBox.props, + linkSrc: textBox.rootDoc, + linkDoc: linkDoc ? (DocServer.GetCachedRefField(linkDoc) as Doc) : undefined, + location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))), + hrefs, + showHeader: true, + }); } } - destroy() { } -} \ No newline at end of file + destroy() {} +} diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index c66cb502e..31552cf1b 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -1,17 +1,17 @@ -import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands"; -import { redo, undo } from "prosemirror-history"; -import { Schema } from "prosemirror-model"; -import { splitListItem, wrapInList } from "prosemirror-schema-list"; -import { EditorState, TextSelection, Transaction } from "prosemirror-state"; -import { liftTarget } from "prosemirror-transform"; -import { AclAugment, AclSelfEdit, Doc } from "../../../../fields/Doc"; -import { GetEffectiveAcl } from "../../../../fields/util"; -import { Utils } from "../../../../Utils"; -import { Docs } from "../../../documents/Documents"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { liftListItem, sinkListItem } from "./prosemirrorPatches.js"; - -const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false; +import { chainCommands, deleteSelection, exitCode, joinBackward, joinDown, joinUp, lift, newlineInCode, selectNodeBackward, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from 'prosemirror-commands'; +import { redo, undo } from 'prosemirror-history'; +import { Schema } from 'prosemirror-model'; +import { splitListItem, wrapInList } from 'prosemirror-schema-list'; +import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; +import { liftTarget } from 'prosemirror-transform'; +import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc'; +import { GetEffectiveAcl } from '../../../../fields/util'; +import { Utils } from '../../../../Utils'; +import { Docs } from '../../../documents/Documents'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { liftListItem, sinkListItem } from './prosemirrorPatches.js'; + +const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; export type KeyMap = { [key: string]: any }; @@ -20,12 +20,12 @@ export let updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle?: tx2.doc.descendants((node: any, offset: any, index: any) => { if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) { const path = (tx2.doc.resolve(offset) as any).path; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) { if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle; depth++; } - tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth, }, node.marks); + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle, bulletStyle: depth }, node.marks); } }); return tx2; @@ -45,7 +45,8 @@ export function buildKeymap>(schema: S, props: any, mapKey const canEdit = (state: any) => { switch (GetEffectiveAcl(props.DataDoc)) { - case AclAugment: return false; + case AclAugment: + return false; case AclSelfEdit: for (var i = state.selection.from; i < state.selection.to; i++) { const marks = state.doc.resolve(i)?.marks?.(); @@ -58,95 +59,102 @@ export function buildKeymap>(schema: S, props: any, mapKey return true; }; - const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch); + const toggleEditableMark = (mark: any) => (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && toggleMark(mark)(state, dispatch); //History commands - bind("Mod-z", undo); - bind("Shift-Mod-z", redo); - !mac && bind("Mod-y", redo); + bind('Mod-z', undo); + bind('Shift-Mod-z', redo); + !mac && bind('Mod-y', redo); //Commands to modify Mark - bind("Mod-b", toggleEditableMark(schema.marks.strong)); - bind("Mod-B", toggleEditableMark(schema.marks.strong)); + bind('Mod-b', toggleEditableMark(schema.marks.strong)); + bind('Mod-B', toggleEditableMark(schema.marks.strong)); - bind("Mod-e", toggleEditableMark(schema.marks.em)); - bind("Mod-E", toggleEditableMark(schema.marks.em)); + bind('Mod-e', toggleEditableMark(schema.marks.em)); + bind('Mod-E', toggleEditableMark(schema.marks.em)); - bind("Mod-*", toggleEditableMark(schema.marks.code)); + bind('Mod-*', toggleEditableMark(schema.marks.code)); - bind("Mod-u", toggleEditableMark(schema.marks.underline)); - bind("Mod-U", toggleEditableMark(schema.marks.underline)); + bind('Mod-u', toggleEditableMark(schema.marks.underline)); + bind('Mod-U', toggleEditableMark(schema.marks.underline)); //Commands for lists - bind("Ctrl-i", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); + bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); - bind("Ctrl-Tab", () => props.onKey?.(event, props) ? true : true); - bind("Alt-Tab", () => props.onKey?.(event, props) ? true : true); - bind("Meta-Tab", () => props.onKey?.(event, props) ? true : true); - bind("Meta-Enter", () => props.onKey?.(event, props) ? true : true); - bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true)); + bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true)); + bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true)); + bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true)); + bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const ref = state.selection; const range = ref.$from.blockRange(ref.$to); const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { // couldn't sink into an existing list, so wrap in a new one - const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); - if (!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => { + if ( + !sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { const tx3 = updateBullets(tx2, schema); - // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); dispatch(tx3); - })) { - console.log("bullet promote fail"); + }) + ) { + // couldn't sink into an existing list, so wrap in a new one + const newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end))); + if ( + !wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + // when promoting to a list, assume list will format things so don't copy the stored marks. + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + }) + ) { + console.log('bullet promote fail'); } } }); - bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { - console.log("bullet demote fail"); + if ( + !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + }) + ) { + console.log('bullet demote fail'); } }); //Command to create a new Tab with a PDF of all the command shortcuts - bind("Mod-/", (state: EditorState, dispatch: (tx: Transaction) => void) => { - const newDoc = Docs.Create.PdfDocument(Utils.prepend("/assets/cheat-sheet.pdf"), { _width: 300, _height: 300 }); - props.addDocTab(newDoc, "add:right"); + bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => { + const newDoc = Docs.Create.PdfDocument(Utils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); + props.addDocTab(newDoc, 'add:right'); }); //Commands to modify BlockType - bind("Ctrl->", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit((state) && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any))); - bind("Alt-\\", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any)); - bind("Shift-Ctrl-\\", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any)); + bind('Ctrl->', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state && wrapIn(schema.nodes.blockquote)(state as any, dispatch as any))); + bind('Alt-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.paragraph)(state as any, dispatch as any)); + bind('Shift-Ctrl-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.code_block)(state as any, dispatch as any)); - bind("Ctrl-m", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: "math" + Utils.GenerateGuid() })))); + bind('Ctrl-m', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(schema.nodes.equation.create({ fieldKey: 'math' + Utils.GenerateGuid() })))); for (let i = 1; i <= 6; i++) { - bind("Shift-Ctrl-" + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any)); + bind('Shift-Ctrl-' + i, (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && setBlockType(schema.nodes.heading, { level: i })(state as any, dispatch as any)); } //Command to create a horizontal break line const hr = schema.nodes.horizontal_rule; - bind("Mod-_", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())); + bind('Mod-_', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView())); //Command to unselect all - bind("Escape", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => { dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); (document.activeElement as any).blur?.(); SelectionManager.DeselectAll(); @@ -158,24 +166,29 @@ export function buildKeymap>(schema: S, props: any, mapKey return tx; }; - - bind("Alt-Enter", () => props.onKey?.(event, props) ? true : true); - bind("Ctrl-Enter", () => props.onKey?.(event, props) ? true : true); + bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true)); + bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true)); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); - bind("Backspace", (state: EditorState, dispatch: (tx: Transaction>) => void) => { + bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; - if (!deleteSelection(state, (tx: Transaction) => { - dispatch(updateBullets(tx, schema)); - })) { - if (!joinBackward(state, (tx: Transaction) => { + if ( + !deleteSelection(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); - })) { - if (!selectNodeBackward(state, (tx: Transaction) => { + }) + ) { + if ( + !joinBackward(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); - })) { + }) + ) { + if ( + !selectNodeBackward(state, (tx: Transaction) => { + dispatch(updateBullets(tx, schema)); + }) + ) { return false; } } @@ -185,8 +198,7 @@ export function buildKeymap>(schema: S, props: any, mapKey //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock //command to break line - bind("Enter", (state: EditorState, dispatch: (tx: Transaction>) => void) => { - + bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; @@ -200,25 +212,29 @@ export function buildKeymap>(schema: S, props: any, mapKey } const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - const cr = state.selection.$from.node().textContent.endsWith("\n"); + const cr = state.selection.$from.node().textContent.endsWith('\n'); if (cr || !newlineInCode(state, dispatch as any)) { - if (!splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { - const tx3 = updateBullets(tx2, schema); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); - })) { + if ( + !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { + const tx3 = updateBullets(tx2, schema); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + dispatch(tx3); + }) + ) { const fromattrs = state.selection.$from.node().attrs; - if (!splitBlockKeepMarks(state, (tx3: Transaction) => { - const tonode = tx3.selection.$to.node(); - if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { - const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); - splitMetadata(marks, tx4); - if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as ((tx: Transaction>) => void))) { - dispatch(tx4); - } - } else dispatch(tx3.insertText("\r\n")); - })) { + if ( + !splitBlockKeepMarks(state, (tx3: Transaction) => { + const tonode = tx3.selection.$to.node(); + if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { + const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); + splitMetadata(marks, tx4); + if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) { + dispatch(tx4); + } + } else dispatch(tx3.insertText('\r\n')); + }) + ) { return false; } } @@ -227,16 +243,16 @@ export function buildKeymap>(schema: S, props: any, mapKey }); //Command to create a blank space - bind("Space", (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); dispatch(splitMetadata(marks, state.tr)); return false; }); - bind("Alt-ArrowUp", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any)); - bind("Alt-ArrowDown", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any)); - bind("Mod-BracketLeft", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any)); + bind('Alt-ArrowUp', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinUp(state, dispatch as any)); + bind('Alt-ArrowDown', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && joinDown(state, dispatch as any)); + bind('Mod-BracketLeft', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && lift(state, dispatch as any)); const cmd = chainCommands(exitCode, (state, dispatch) => { if (dispatch) { @@ -246,8 +262,7 @@ export function buildKeymap>(schema: S, props: any, mapKey return false; }); - bind("Shift-Enter", cmd); + bind('Shift-Enter', cmd); return keys; } - diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 98343a261..22ca76b2e 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,36 +1,35 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { lift, wrapIn } from "prosemirror-commands"; -import { Mark, MarkType, Node as ProsNode, ResolvedPos } from "prosemirror-model"; -import { wrapInList } from "prosemirror-schema-list"; -import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; -import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../../fields/Doc"; -import { Cast, StrCast } from "../../../../fields/Types"; -import { DocServer } from "../../../DocServer"; -import { LinkManager } from "../../../util/LinkManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu"; -import { FieldViewProps } from "../FieldView"; -import { FormattedTextBox, FormattedTextBoxProps } from "./FormattedTextBox"; -import { updateBullets } from "./ProsemirrorExampleTransfer"; -import "./RichTextMenu.scss"; -import { schema } from "./schema_rts"; -const { toggleMark } = require("prosemirror-commands"); - +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { lift, wrapIn } from 'prosemirror-commands'; +import { Mark, MarkType, Node as ProsNode, ResolvedPos } from 'prosemirror-model'; +import { wrapInList } from 'prosemirror-schema-list'; +import { EditorState, NodeSelection, TextSelection } from 'prosemirror-state'; +import { EditorView } from 'prosemirror-view'; +import { Doc } from '../../../../fields/Doc'; +import { Cast, StrCast } from '../../../../fields/Types'; +import { DocServer } from '../../../DocServer'; +import { LinkManager } from '../../../util/LinkManager'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { FieldViewProps } from '../FieldView'; +import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox'; +import { updateBullets } from './ProsemirrorExampleTransfer'; +import './RichTextMenu.scss'; +import { schema } from './schema_rts'; +const { toggleMark } = require('prosemirror-commands'); @observer -export class RichTextMenu extends AntimodeMenu { +export class RichTextMenu extends AntimodeMenu { @observable static Instance: RichTextMenu; public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable private _linkToRef = React.createRef(); @observable public view?: EditorView; - public editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined; public _brushMap: Map> = new Map(); @@ -43,21 +42,21 @@ export class RichTextMenu extends AntimodeMenu { @observable private _subscriptActive: boolean = false; @observable private _superscriptActive: boolean = false; - @observable private _activeFontSize: string = "13px"; - @observable private _activeFontFamily: string = ""; - @observable private activeListType: string = ""; - @observable private _activeAlignment: string = "left"; + @observable private _activeFontSize: string = '13px'; + @observable private _activeFontFamily: string = ''; + @observable private activeListType: string = ''; + @observable private _activeAlignment: string = 'left'; @observable private brushMarks: Set = new Set(); @observable private showBrushDropdown: boolean = false; - @observable private _activeFontColor: string = "black"; + @observable private _activeFontColor: string = 'black'; @observable private showColorDropdown: boolean = false; - @observable private activeHighlightColor: string = "transparent"; + @observable private activeHighlightColor: string = 'transparent'; @observable private showHighlightDropdown: boolean = false; - @observable private currentLink: string | undefined = ""; + @observable private currentLink: string | undefined = ''; @observable private showLinkDropdown: boolean = false; _reaction: IReactionDisposer | undefined; @@ -72,24 +71,44 @@ export class RichTextMenu extends AntimodeMenu { } componentDidMount() { - this._reaction = reaction(() => SelectionManager.Views(), - () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true)); + this._reaction = reaction( + () => SelectionManager.Views(), + () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true) + ); } componentWillUnmount() { this._reaction?.(); } - @computed get noAutoLink() { return this._noLinkActive; } - @computed get bold() { return this._boldActive; } - @computed get underline() { return this._underlineActive; } - @computed get italics() { return this._italicsActive; } - @computed get strikeThrough() { return this._strikethroughActive; } - @computed get fontColor() { return this._activeFontColor; } - @computed get fontFamily() { return this._activeFontFamily; } - @computed get fontSize() { return this._activeFontSize; } - @computed get textAlign() { return this._activeAlignment; } + @computed get noAutoLink() { + return this._noLinkActive; + } + @computed get bold() { + return this._boldActive; + } + @computed get underline() { + return this._underlineActive; + } + @computed get italics() { + return this._italicsActive; + } + @computed get strikeThrough() { + return this._strikethroughActive; + } + @computed get fontColor() { + return this._activeFontColor; + } + @computed get fontFamily() { + return this._activeFontFamily; + } + @computed get fontSize() { + return this._activeFontSize; + } + @computed get textAlign() { + return this._activeAlignment; + } - public delayHide = () => this._delayHide = true; + public delayHide = () => (this._delayHide = true); @action public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) { @@ -118,16 +137,16 @@ export class RichTextMenu extends AntimodeMenu { this.activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? "Arial" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various"; - this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, "10px")) : activeSizes[0]; - this._activeFontColor = !activeColors.length ? "black" : activeColors.length > 0 ? String(activeColors[0]) : "..."; - this.activeHighlightColor = !activeHighlights.length ? "" : activeHighlights.length > 0 ? String(activeHighlights[0]) : "..."; + this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; + this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...'; + this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection this.getTextLinkTargetTitle().then(targetTitle => this.setCurrentLink(targetTitle)); } - setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => { + setMark = (mark: Mark, state: EditorState, dispatch: any, dontToggle: boolean = false) => { if (mark) { const node = (state.selection as NodeSelection).node; if (node?.type === schema.nodes.ordered_list) { @@ -140,7 +159,8 @@ export class RichTextMenu extends AntimodeMenu { } else if (dontToggle) { toggleMark(mark.type, mark.attrs)(state, (tx: any) => { const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { // hack -- should have just set the mark in the first place + if (!tx.doc.rangeHasMark(from, to, mark.type)) { + // hack -- should have just set the mark in the first place toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); } else dispatch(tx); }); @@ -148,7 +168,7 @@ export class RichTextMenu extends AntimodeMenu { toggleMark(mark.type, mark.attrs)(state, dispatch); } } - } + }; // finds font sizes and families in selection getActiveAlignment() { @@ -156,11 +176,11 @@ export class RichTextMenu extends AntimodeMenu { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { - return path[i].attrs.align || "left"; + return path[i].attrs.align || 'left'; } } } - return "left"; + return 'left'; } // finds font sizes and families in selection @@ -176,7 +196,7 @@ export class RichTextMenu extends AntimodeMenu { return this.view.state.selection.$from.nodeAfter?.attrs.mapStyle; } } - return ""; + return ''; } // finds font sizes and families in selection @@ -190,7 +210,7 @@ export class RichTextMenu extends AntimodeMenu { if (this.TextView.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; - const marks: Mark[] = [...(state.storedMarks ?? [])]; + const marks: Mark[] = [...(state.storedMarks ?? [])]; if (state.selection.empty) { const ref_node = this.reference_node(pos); marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); @@ -209,10 +229,10 @@ export class RichTextMenu extends AntimodeMenu { return { activeFamilies, activeSizes, activeColors, activeHighlights }; } - getMarksInSelection(state: EditorState) { + getMarksInSelection(state: EditorState) { const found = new Set(); const { from, to } = state.selection as TextSelection; - state.doc.nodesBetween(from, to, (node) => node.marks.forEach(m => found.add(m))); + state.doc.nodesBetween(from, to, node => node.marks.forEach(m => found.add(m))); return found; } @@ -234,14 +254,12 @@ export class RichTextMenu extends AntimodeMenu { } return false; }); - } - else { + } else { const pos = this.view.state.selection.$from; const ref_node: ProsNode | null = this.reference_node(pos); if (ref_node !== null && ref_node !== this.view.state.doc) { if (ref_node.isText) { - } - else { + } else { return []; } activeMarks = markGroup.filter(mark_type => { @@ -275,13 +293,27 @@ export class RichTextMenu extends AntimodeMenu { activeMarks.forEach(mark => { switch (mark.name) { - case "noAutoLinkAnchor": this._noLinkActive = true; break; - case "strong": this._boldActive = true; break; - case "em": this._italicsActive = true; break; - case "underline": this._underlineActive = true; break; - case "strikethrough": this._strikethroughActive = true; break; - case "subscript": this._subscriptActive = true; break; - case "superscript": this._superscriptActive = true; break; + case 'noAutoLinkAnchor': + this._noLinkActive = true; + break; + case 'strong': + this._boldActive = true; + break; + case 'em': + this._italicsActive = true; + break; + case 'underline': + this._underlineActive = true; + break; + case 'strikethrough': + this._strikethroughActive = true; + break; + case 'subscript': + this._subscriptActive = true; + break; + case 'superscript': + this._superscriptActive = true; + break; } }); } @@ -293,14 +325,14 @@ export class RichTextMenu extends AntimodeMenu { this.TextView.autoLink(); this.view.focus(); } - } + }; toggleBold = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.strong); this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } + }; toggleUnderline = () => { if (this.view) { @@ -308,7 +340,7 @@ export class RichTextMenu extends AntimodeMenu { this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } + }; toggleItalics = () => { if (this.view) { @@ -316,13 +348,11 @@ export class RichTextMenu extends AntimodeMenu { this.setMark(mark, this.view.state, this.view.dispatch, false); this.view.focus(); } - } - + }; setFontSize = (fontSize: string) => { if (this.view) { - if (this.view.state.selection.from === 1 && this.view.state.selection.empty && - (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { + if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { this.TextView.dataDoc.fontSize = fontSize; this.view.focus(); this.updateMenu(this.view, undefined, this.props); @@ -333,7 +363,7 @@ export class RichTextMenu extends AntimodeMenu { this.updateMenu(this.view, undefined, this.props); } } - } + }; setFontFamily = (family: string) => { if (this.view) { @@ -342,7 +372,7 @@ export class RichTextMenu extends AntimodeMenu { this.view.focus(); this.updateMenu(this.view, undefined, this.props); } - } + }; setHighlight(color: String, view: EditorView, dispatch: any) { const highlightMark = view.state.schema.mark(view.state.schema.marks.marker, { highlight: color }); @@ -362,8 +392,10 @@ export class RichTextMenu extends AntimodeMenu { // TODO: remove doesn't work // remove all node type and apply the passed-in one to the selected text - changeListType = (nodeType: Node | undefined) => { - if (!this.view || (nodeType as any)?.attrs.mapStyle === "") return; + changeListType = (mapStyle: string) => { + const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle(); + const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }); + if (!this.view || nodeType?.attrs.mapStyle === '') return; const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list; let inList: any = undefined; @@ -377,17 +409,19 @@ export class RichTextMenu extends AntimodeMenu { } const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); - if (inList || !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); + if ( + inList || + !wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); - this.view!.dispatch(tx2); - })) { + this.view!.dispatch(tx2); + }) + ) { const tx2 = this.view.state.tr; if (nodeType && (inList || nextIsOL)) { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, - inList ? fromList + inList.nodeSize : this.view.state.selection.to); + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, inList ? fromList : this.view.state.selection.from, inList ? fromList + inList.nodeSize : this.view.state.selection.to); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); this.view.dispatch(tx3); @@ -395,9 +429,9 @@ export class RichTextMenu extends AntimodeMenu { } this.view.focus(); this.updateMenu(this.view, undefined, this.props); - } + }; - insertSummarizer(state: EditorState, dispatch: any) { + insertSummarizer(state: EditorState, dispatch: any) { if (state.selection.empty) return false; const mark = state.schema.marks.summarize.create(); const tr = state.tr; @@ -408,7 +442,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - align = (view: EditorView, dispatch: any, alignment: "left" | "right" | "center") => { + align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { if (this.TextView.props.isSelected(true)) { var tr = view.state.tr; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { @@ -422,9 +456,9 @@ export class RichTextMenu extends AntimodeMenu { view.focus(); dispatch?.(tr); } - } + }; - insetParagraph(state: EditorState, dispatch: any) { + insetParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -437,7 +471,7 @@ export class RichTextMenu extends AntimodeMenu { dispatch?.(tr); return true; } - outsetParagraph(state: EditorState, dispatch: any) { + outsetParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -451,7 +485,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - indentParagraph(state: EditorState, dispatch: any) { + indentParagraph(state: EditorState, dispatch: any) { var tr = state.tr; const heading = false; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { @@ -467,7 +501,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - hangingIndentParagraph(state: EditorState, dispatch: any) { + hangingIndentParagraph(state: EditorState, dispatch: any) { var tr = state.tr; state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { @@ -482,7 +516,7 @@ export class RichTextMenu extends AntimodeMenu { return true; } - insertBlockquote(state: EditorState, dispatch: any) { + insertBlockquote(state: EditorState, dispatch: any) { const path = (state.selection.$from as any).path; if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) { lift(state, dispatch); @@ -492,20 +526,22 @@ export class RichTextMenu extends AntimodeMenu { return true; } - insertHorizontalRule(state: EditorState, dispatch: any) { + insertHorizontalRule(state: EditorState, dispatch: any) { dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView()); return true; } - @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } + @action toggleBrushDropdown() { + this.showBrushDropdown = !this.showBrushDropdown; + } // todo: add brushes to brushMap to save with a style name onBrushNameKeyPress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks); - this._brushNameRef.current!.style.background = "lightGray"; + this._brushNameRef.current!.style.background = 'lightGray'; } - } + }; _brushNameRef = React.createRef(); @action @@ -514,7 +550,7 @@ export class RichTextMenu extends AntimodeMenu { } @action - fillBrush(state: EditorState, dispatch: any) { + fillBrush(state: EditorState, dispatch: any) { if (!this.view) return; if (!Array.from(this.brushMarks.keys()).length) { @@ -522,68 +558,81 @@ export class RichTextMenu extends AntimodeMenu { if (selected_marks.size >= 0) { this.brushMarks = selected_marks; } - } - else { + } else { const { from, to, $from } = this.view.state.selection; if (!this.view.state.selection.empty && $from && $from.nodeAfter) { if (to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this.brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { - this.setMark(mark, this.view!.state, this.view!.dispatch); - }); + Array.from(this.brushMarks) + .filter(m => m.type !== schema.marks.user_mark) + .forEach((mark: Mark) => { + this.setMark(mark, this.view!.state, this.view!.dispatch); + }); } } } } - get TextView() { return (this.view as any)?.TextView as FormattedTextBox; } - get TextViewFieldKey() { return this.TextView?.props.fieldKey; } - - - - @action setActiveHighlight(color: string) { this.activeHighlightColor = color; } + get TextView() { + return (this.view as any)?.TextView as FormattedTextBox; + } + get TextViewFieldKey() { + return this.TextView?.props.fieldKey; + } + @action setActiveHighlight(color: string) { + this.activeHighlightColor = color; + } - @action setCurrentLink(link: string) { this.currentLink = link; } + @action setCurrentLink(link: string) { + this.currentLink = link; + } createLinkButton() { const self = this; function onLinkChange(e: React.ChangeEvent) { self.TextView?.endUndoTypingBatch(); - UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change"); + UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), 'link change'); } - const link = this.currentLink ? this.currentLink : ""; + const link = this.currentLink ? this.currentLink : ''; - const button = set hyperlink
    } placement="bottom"> - - ; + const button = ( + set hyperlink
    } placement="bottom"> + + + ); - const dropdownContent = + const dropdownContent = (

    Linked to:

    - +
    - -
    ; + +
    + ); - return ; + return ; } async getTextLinkTargetTitle() { if (!this.view) return; const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); + const link = node && node.marks.find(m => m.type.name === 'link'); if (link) { const href = link.attrs.allAnchors.length > 0 ? link.attrs.allAnchors[0].href : undefined; if (href) { if (href.indexOf(Doc.localServerPath()) === 0) { - const linkclicked = href.replace(Doc.localServerPath(), "").split("?")[0]; + const linkclicked = href.replace(Doc.localServerPath(), '').split('?')[0]; if (linkclicked) { const linkDoc = await DocServer.GetRefField(linkclicked); if (linkDoc instanceof Doc) { @@ -612,8 +661,8 @@ export class RichTextMenu extends AntimodeMenu { // TODO: should check for valid URL @undoBatch makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target); - } + ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target); + }; @undoBatch @action @@ -624,13 +673,15 @@ export class RichTextMenu extends AntimodeMenu { const allAnchors = linkAnchor.attrs.allAnchors.slice(); this.TextView.RemoveAnchorFromSelection(allAnchors); // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected. - allAnchors.filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0).forEach((aref: any) => { - const anchorId = aref.href.replace(Doc.localServerPath(), "").split("?")[0]; - anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); - }); + allAnchors + .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0) + .forEach((aref: any) => { + const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0]; + anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); + }); } } - } + }; linkExtend($start: ResolvedPos, href: string) { const mark = this.view!.state.schema.marks.linkAnchor; @@ -651,7 +702,7 @@ export class RichTextMenu extends AntimodeMenu { return { from: startPos, to: endPos }; } - reference_node(pos: ResolvedPos): ProsNode | null { + reference_node(pos: ResolvedPos): ProsNode | null { if (!this.view) return null; let ref_node: ProsNode = this.view.state.doc; @@ -671,7 +722,6 @@ export class RichTextMenu extends AntimodeMenu { ref_node = node; skip = true; } - }); } } @@ -755,21 +805,19 @@ interface ButtonDropdownProps { openDropdownOnButton?: boolean; link?: boolean; pdf?: boolean; - } @observer export class ButtonDropdown extends React.Component { - @observable private showDropdown: boolean = false; private ref: HTMLDivElement | null = null; componentDidMount() { - document.addEventListener("pointerdown", this.onBlur); + document.addEventListener('pointerdown', this.onBlur); } componentWillUnmount() { - document.removeEventListener("pointerdown", this.onBlur); + document.removeEventListener('pointerdown', this.onBlur); } @action @@ -785,7 +833,7 @@ export class ButtonDropdown extends React.Component { e.preventDefault(); e.stopPropagation(); this.toggleDropdown(); - } + }; onBlur = (e: PointerEvent) => { setTimeout(() => { @@ -793,37 +841,40 @@ export class ButtonDropdown extends React.Component { this.setShowDropdown(false); } }, 0); - } - + }; render() { return ( -
    this.ref = node}> - {!this.props.pdf ? +
    (this.ref = node)}> + {!this.props.pdf ? (
    {this.props.button} -
    +
    - : + ) : ( <> {this.props.button} - } - {this.showDropdown ? this.props.dropdownContent : (null)} + + )} + {this.showDropdown ? this.props.dropdownContent : null}
    ); } } - interface RichTextMenuPluginProps { editorProps: any; } export class RichTextMenuPlugin extends React.Component { - render() { return null; } - update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); } -} \ No newline at end of file + render() { + return null; + } + update(view: EditorView, lastState: EditorState | undefined) { + RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps); + } +} diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 8851d52e4..1916b94bf 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,17 +1,17 @@ -import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from "prosemirror-inputrules"; -import { NodeSelection, TextSelection } from "prosemirror-state"; -import { DataSym, Doc } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { ComputedField } from "../../../../fields/ScriptField"; -import { NumCast, StrCast } from "../../../../fields/Types"; -import { normalizeEmail } from "../../../../fields/util"; -import { returnFalse, Utils } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; -import { FormattedTextBox } from "./FormattedTextBox"; -import { wrappingInputRule } from "./prosemirrorPatches"; -import { RichTextMenu } from "./RichTextMenu"; -import { schema } from "./schema_rts"; +import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; +import { NodeSelection, TextSelection } from 'prosemirror-state'; +import { DataSym, Doc } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { ComputedField } from '../../../../fields/ScriptField'; +import { NumCast, StrCast } from '../../../../fields/Types'; +import { normalizeEmail } from '../../../../fields/util'; +import { returnFalse, Utils } from '../../../../Utils'; +import { DocServer } from '../../../DocServer'; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { FormattedTextBox } from './FormattedTextBox'; +import { wrappingInputRule } from './prosemirrorPatches'; +import { RichTextMenu } from './RichTextMenu'; +import { schema } from './schema_rts'; export class RichTextRules { public Document: Doc; @@ -34,9 +34,9 @@ export class RichTextRules { wrappingInputRule( /^1\.\s$/, schema.nodes.ordered_list, - () => ({ mapStyle: "decimal", bulletStyle: 1 }), + () => ({ mapStyle: 'decimal', bulletStyle: 1 }), (match: any, node: any) => node.childCount + node.attrs.order === +match[1], - ((type: any) => ({ type: type, attrs: { mapStyle: "decimal", bulletStyle: 1 } })) as any + ((type: any) => ({ type: type, attrs: { mapStyle: 'decimal', bulletStyle: 1 } })) as any ), // A. create alphabetical ordered list @@ -45,360 +45,324 @@ export class RichTextRules { schema.nodes.ordered_list, // match => { () => { - return ({ mapStyle: "multi", bulletStyle: 1 }); + return { mapStyle: 'multi', bulletStyle: 1 }; // return ({ order: +match[1] }) }, (match: any, node: any) => { return node.childCount + node.attrs.order === +match[1]; }, - ((type: any) => ({ type: type, attrs: { mapStyle: "multi", bulletStyle: 1 } })) as any + ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any ), // * + - create bullet list - wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list, + wrappingInputRule( + /^\s*([-+*])\s$/, + schema.nodes.ordered_list, // match => { - () => ({ mapStyle: "bullet" }), // ({ order: +match[1] }) + () => ({ mapStyle: 'bullet' }), // ({ order: +match[1] }) (match: any, node: any) => node.childCount + node.attrs.order === +match[1], - ((type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })) as any + ((type: any) => ({ type: type, attrs: { mapStyle: 'bullet' } })) as any ), // ``` create code block textblockTypeInputRule(/^```$/, schema.nodes.code_block), - // % set the font size - new InputRule( - new RegExp(/%([0-9]+)\s$/), - (state, match, start, end) => { - const size = Number(match[1]); - return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); - }), + // % set the font size + new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => { + const size = Number(match[1]); + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); + }), //Create annotation to a field on the text document - new InputRule( - new RegExp(/>>$/), - (state, match, start, end) => { - const textDoc = this.Document[DataSym]; - const numInlines = NumCast(textDoc.inlineTextCount); - textDoc.inlineTextCount = numInlines + 1; - const inlineFieldKey = "inline" + numInlines; // which field on the text document this annotation will write to - const inlineLayoutKey = "layout_" + inlineFieldKey; // the field holding the layout string that will render the inline annotation - const textDocInline = Docs.Create.TextDocument("", { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: "9px", title: "inline comment" }); - textDocInline.title = inlineFieldKey; // give the annotation its own title - textDocInline["title-custom"] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc - textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point - textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] - textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); - textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text - textDoc[inlineFieldKey] = ""; // set a default value for the annotation - const node = (state.doc.resolve(start) as any).nodeAfter; - const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] }); - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: textDocInline[Id], float: "right" }); - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.insert(start, newNode).replaceRangeWith(start + 1, end + 1, dashDoc).insertText(" ", start + 2).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced; - }), - + new InputRule(new RegExp(/>>$/), (state, match, start, end) => { + const textDoc = this.Document[DataSym]; + const numInlines = NumCast(textDoc.inlineTextCount); + textDoc.inlineTextCount = numInlines + 1; + const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to + const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation + const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' }); + textDocInline.title = inlineFieldKey; // give the annotation its own title + textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc + textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point + textDocInline.proto = textDoc; // make the annotation inherit from the outer text doc so that it can resolve any nested field references, e.g., [[field]] + textDocInline._textContext = ComputedField.MakeFunction(`copyField(self.${inlineFieldKey})`); + textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text + textDoc[inlineFieldKey] = ''; // set a default value for the annotation + const node = (state.doc.resolve(start) as any).nodeAfter; + const newNode = schema.nodes.dashComment.create({ docid: textDocInline[Id] }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docid: textDocInline[Id], float: 'right' }); + const sm = state.storedMarks || undefined; + const replaced = node + ? state.tr + .insert(start, newNode) + .replaceRangeWith(start + 1, end + 1, dashDoc) + .insertText(' ', start + 2) + .setStoredMarks([...node.marks, ...(sm ? sm : [])]) + : state.tr; + return replaced; + }), // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/(%d|d)$/), - (state, match, start, end) => { - if (!match[0].startsWith("%") && !this.EnteringStyle) return null; - const pos = (state.doc.resolve(start) as any); - for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { - const node = pos.node(depth); - if (node.type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); - const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); - return match[0].startsWith("%") ? result.deleteRange(start, end) : result; - } + new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => { + if (!match[0].startsWith('%') && !this.EnteringStyle) return null; + const pos = state.doc.resolve(start) as any; + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith('%') ? result.deleteRange(start, end) : result; } - return null; - }), + } + return null; + }), // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/(%h|h)$/), - (state, match, start, end) => { - if (!match[0].startsWith("%") && !this.EnteringStyle) return null; - const pos = (state.doc.resolve(start) as any); - for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { - const node = pos.node(depth); - if (node.type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); - const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); - return match[0].startsWith("%") ? result.deleteRange(start, end) : result; - } + new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => { + if (!match[0].startsWith('%') && !this.EnteringStyle) return null; + const pos = state.doc.resolve(start) as any; + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith('%') ? result.deleteRange(start, end) : result; } - return null; - }), + } + return null; + }), // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/(%q|q)$/), - (state, match, start, end) => { - if (!match[0].startsWith("%") && !this.EnteringStyle) return null; - const pos = (state.doc.resolve(start) as any); - if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { - const node = state.selection.node; - return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); - } - for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { - const node = pos.node(depth); - if (node.type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); - const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); - return match[0].startsWith("%") ? result.deleteRange(start, end) : result; - } + new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => { + if (!match[0].startsWith('%') && !this.EnteringStyle) return null; + const pos = state.doc.resolve(start) as any; + if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { + const node = state.selection.node; + return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); + } + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith('%') ? result.deleteRange(start, end) : result; } - return null; - }), + } + return null; + }), // center justify text - new InputRule( - new RegExp(/%\^/), - (state, match, start, end) => { - const resolved = state.doc.resolve(start) as any; - if (resolved?.parent.type.name === "paragraph") { - return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "center" }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); - } - }), + new InputRule(new RegExp(/%\^/), (state, match, start, end) => { + const resolved = state.doc.resolve(start) as any; + if (resolved?.parent.type.name === 'paragraph') { + return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + } + }), // left justify text - new InputRule( - new RegExp(/%\[/), - (state, match, start, end) => { - const resolved = state.doc.resolve(start) as any; - if (resolved?.parent.type.name === "paragraph") { - return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "left" }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); - } - }), + new InputRule(new RegExp(/%\[/), (state, match, start, end) => { + const resolved = state.doc.resolve(start) as any; + if (resolved?.parent.type.name === 'paragraph') { + return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + } + }), // right justify text - new InputRule( - new RegExp(/%\]/), - (state, match, start, end) => { - const resolved = state.doc.resolve(start) as any; - if (resolved?.parent.type.name === "paragraph") { - return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: "right" }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : - state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); - } - }), - + new InputRule(new RegExp(/%\]/), (state, match, start, end) => { + const resolved = state.doc.resolve(start) as any; + if (resolved?.parent.type.name === 'paragraph') { + return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); + } else { + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + } + }), // %f create footnote - new InputRule( - new RegExp(/%f$/), - (state, match, start, end) => { - const newNode = schema.nodes.footnote.create({}); - const tr = state.tr; - tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. - return tr.setSelection(new NodeSelection( // select the footnote node to open its display - tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) - tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)))); - }), + new InputRule(new RegExp(/%f$/), (state, match, start, end) => { + const newNode = schema.nodes.footnote.create({}); + const tr = state.tr; + tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote. + return tr.setSelection( + new NodeSelection( // select the footnote node to open its display + tr.doc.resolve( + // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) + tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0) + ) + ) + ); + }), // activate a style by name using prefix '%' - new InputRule( - new RegExp(/%[a-z]+$/), - (state, match, start, end) => { - - const color = match[0].substring(1, match[0].length); - const marks = RichTextMenu.Instance._brushMap.get(color); + new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => { + const color = match[0].substring(1, match[0].length); + const marks = RichTextMenu.Instance._brushMap.get(color); - if (marks) { - const tr = state.tr.deleteRange(start, end); - return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; - } + if (marks) { + const tr = state.tr.deleteRange(start, end); + return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; + } - const isValidColor = (strColor: string) => { - const s = new Option().style; - s.color = strColor; - return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned - }; + const isValidColor = (strColor: string) => { + const s = new Option().style; + s.color = strColor; + return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned + }; - if (isValidColor(color)) { - return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); - } + if (isValidColor(color)) { + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); + } - return null; - }), + return null; + }), // stop using active style - new InputRule( - new RegExp(/%%$/), - (state, match, start, end) => { - - const tr = state.tr.deleteRange(start, end); - const marks = state.tr.selection.$anchor.nodeBefore?.marks; - - return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr; - }), - - // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document - // [[ : ]] - // [[:Doc]] => hyperlink - // [[fieldKey]] => show field + new InputRule(new RegExp(/%%$/), (state, match, start, end) => { + const tr = state.tr.deleteRange(start, end); + const marks = state.tr.selection.$anchor.nodeBefore?.marks; + + return marks + ? Array.from(marks) + .filter(m => m.type !== state.schema.marks.user_mark) + .reduce((tr, m) => tr.removeStoredMark(m), tr) + : tr; + }), + + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // [[ : ]] + // [[:Doc]] => hyperlink + // [[fieldKey]] => show field // [[fieldKey=value]] => show field and also set its value // [[fieldKey:Doc]] => show field of doc - new InputRule( - new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), - (state, match, start, end) => { - const fieldKey = match[1]; - const rawdocid = match[3]; - const docid = rawdocid ? normalizeEmail((!rawdocid.includes("@") ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1))) : undefined; - const value = match[2]?.substring(1); - if (!fieldKey) { - if (docid) { - DocServer.GetRefField(docid).then(docx => { - const rstate = this.TextBox.EditorView?.state; - const selection = rstate?.selection.$from.pos; - if (rstate) { - this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); - } - const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid); - DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to:portal from", undefined); - - const fstate = this.TextBox.EditorView?.state; - if (fstate && selection) { - this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); - } - }); - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); - } - return state.tr; + new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { + const fieldKey = match[1]; + const rawdocid = match[3]; + const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined; + const value = match[2]?.substring(1); + if (!fieldKey) { + if (docid) { + DocServer.GetRefField(docid).then(docx => { + const rstate = this.TextBox.EditorView?.state; + const selection = rstate?.selection.$from.pos; + if (rstate) { + this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); + } + const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid); + DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined); + + const fstate = this.TextBox.EditorView?.state; + if (fstate && selection) { + this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); + } + }); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); } - if (value !== "" && value !== undefined) { - const num = value.match(/^[0-9.]$/); - this.Document[DataSym][fieldKey] = value === "true" ? true : value === "false" ? false : (num ? Number(value) : value); - } - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); - return state.tr.deleteRange(start, end).insert(start, fieldView); - }), - + return state.tr; + } + if (value !== '' && value !== undefined) { + const num = value.match(/^[0-9.]$/); + this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + } + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); + return state.tr.deleteRange(start, end).insert(start, fieldView); + }), - // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // wiki:title - new InputRule( - new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), - (state, match, start, end) => { - const title = match[1]; - this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))); - - this.TextBox.makeLinkAnchor(undefined, "add:right", `https://en.wikipedia.org/wiki/${title.trim()}`, "wikipedia reference"); - - const fstate = this.TextBox.EditorView?.state; - if (fstate) { - const tr = fstate?.tr.deleteRange(start, start + 5); - return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(" "); - } - return state.tr; - }), + new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => { + const title = match[1]; + this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))); + + this.TextBox.makeLinkAnchor(undefined, 'add:right', `https://en.wikipedia.org/wiki/${title.trim()}`, 'wikipedia reference'); + + const fstate = this.TextBox.EditorView?.state; + if (fstate) { + const tr = fstate?.tr.deleteRange(start, start + 5); + return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(' '); + } + return state.tr; + }), - // create an inline view of a document {{ : }} - // {{:Doc}} => show default view of document - // {{}} => show layout for this doc + // create an inline view of a document {{ : }} + // {{:Doc}} => show default view of document + // {{}} => show layout for this doc // {{ : Doc}} => show layout for another doc - new InputRule( - new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), - (state, match, start, end) => { - const fieldKey = match[1] || ""; - const fieldParam = match[2]?.replace("…", "...") || ""; - const rawdocid = match[3]?.substring(1); - const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined; - if (!fieldKey && !docid) return state.tr; - docid && DocServer.GetRefField(docid).then(docx => { + new InputRule(new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(\([a-zA-Z0-9…._/\-]*\))?(:[a-zA-Z_@\.\? \-0-9]+)?\}\}$/), (state, match, start, end) => { + const fieldKey = match[1] || ''; + const fieldParam = match[2]?.replace('…', '...') || ''; + const rawdocid = match[3]?.substring(1); + const docid = rawdocid ? (!rawdocid.includes('@') ? normalizeEmail(Doc.CurrentUserEmail) + '@' + rawdocid : rawdocid) : undefined; + if (!fieldKey && !docid) return state.tr; + docid && + DocServer.GetRefField(docid).then(docx => { if (!(docx instanceof Doc && docx)) { Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500 }, docid); } }); - const node = (state.doc.resolve(start) as any).nodeAfter; - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey: fieldKey + fieldParam, float: "unset", alias: Utils.GenerateGuid() }); - const sm = state.storedMarks || undefined; - return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - }), + const node = (state.doc.resolve(start) as any).nodeAfter; + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: 'dashDoc', docid, fieldKey: fieldKey + fieldParam, float: 'unset', alias: Utils.GenerateGuid() }); + const sm = state.storedMarks || undefined; + return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + }), // create an inline view of a tag stored under the '#' field - new InputRule( - new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), - (state, match, start, end) => { - const tag = match[1]; - if (!tag) return state.tr; - this.Document[DataSym]["#" + tag] = "#" + tag; - const tags = StrCast(this.Document[DataSym].tags, ":"); - if (!tags.includes(`#${tag}:`)) { - this.Document[DataSym].tags = `${tags + "#" + tag + ':'}`; - } - const fieldView = state.schema.nodes.dashField.create({ fieldKey: "#" + tag }); - return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(" "); - }), - + new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { + const tag = match[1]; + if (!tag) return state.tr; + this.Document[DataSym]['#' + tag] = '#' + tag; + const tags = StrCast(this.Document[DataSym].tags, ':'); + if (!tags.includes(`#${tag}:`)) { + this.Document[DataSym].tags = `${tags + '#' + tag + ':'}`; + } + const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); + return state.tr.deleteRange(start, end).insert(start, fieldView).insertText(' '); + }), // # heading - textblockTypeInputRule( - new RegExp(/^(#{1,6})\s$/), - schema.nodes.heading, - match => { - return ({ level: match[1].length }); - } - ), + textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => { + return { level: match[1].length }; + }), // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) - new InputRule( - new RegExp(/[ti!x]$/), - (state, match, start, end) => { - - if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; - - const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??"; - const node = (state.doc.resolve(start) as any).nodeAfter; + new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => { + if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); + const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??'; + const node = (state.doc.resolve(start) as any).nodeAfter; - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), + if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - new InputRule( - new RegExp(/%\(/), - (state, match, start, end) => { - const node = (state.doc.resolve(start) as any).nodeAfter; - const sm = state.storedMarks || []; - const mark = state.schema.marks.summarizeInclusive.create(); + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + }), - sm.push(mark); - const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); - const content = selected.selection.content(); - const replaced = node ? selected.replaceRangeWith(start, end, - schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : - state.tr; + new InputRule(new RegExp(/%\(/), (state, match, start, end) => { + const node = (state.doc.resolve(start) as any).nodeAfter; + const sm = state.storedMarks?.slice() || []; + const mark = state.schema.marks.summarizeInclusive.create(); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]); - }), + sm.push(mark); + const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); + const content = selected.selection.content(); + const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr; - new InputRule( - new RegExp(/%\)/), - (state, match, start, end) => { - return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); - }), + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]); + }), - ] + new InputRule(new RegExp(/%\)/), (state, match, start, end) => { + return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); + }), + ], }; } diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index c017db034..01acc3de9 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -1,35 +1,49 @@ -import { TextSelection } from "prosemirror-state"; -import { Fragment, Node, Slice } from "prosemirror-model"; +import { TextSelection } from 'prosemirror-state'; +import { Fragment, Node, Slice } from 'prosemirror-model'; import * as ReactDOM from 'react-dom'; -import React = require("react"); +import React = require('react'); // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked. // this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't // really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering // method instead of changing prosemirror's text when the expand/elide buttons are clicked. export class SummaryView { - _fieldWrapper: HTMLSpanElement; // container for label and value + dom: HTMLSpanElement; // container for label and value constructor(node: any, view: any, getPos: any) { const self = this; - this._fieldWrapper = document.createElement("span"); - this._fieldWrapper.className = this.className(node.attrs.visibility); - this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); }; - this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; - this._fieldWrapper.onmousedown = function (e: any) { e.stopPropagation(); }; + this.dom = document.createElement('span'); + this.dom.className = this.className(node.attrs.visibility); + this.dom.onpointerdown = function (e: any) { + self.onPointerDown(e, node, view, getPos); + }; + this.dom.onkeypress = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeydown = function (e: any) { + e.stopPropagation(); + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; const js = node.toJSON; - node.toJSON = function () { return js.apply(this, arguments); }; + node.toJSON = function () { + return js.apply(this, arguments); + }; - ReactDOM.render(, this._fieldWrapper); - (this as any).dom = this._fieldWrapper; + ReactDOM.render(, this.dom); + (this as any).dom = this.dom; } - className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); - destroy() { ReactDOM.unmountComponentAtNode(this._fieldWrapper); } - selectNode() { } + className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed'); + destroy() { + ReactDOM.unmountComponentAtNode(this.dom); + } + selectNode() {} updateSummarizedText(start: any, view: any) { const mtype = view.state.schema.marks.summarize; @@ -44,8 +58,7 @@ export class SummaryView { if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { visited.add(node); endPos = i + node.nodeSize - 1; - } - else skip = true; + } else skip = true; } }); } @@ -56,26 +69,28 @@ export class SummaryView { const visible = !node.attrs.visibility; const attrs = { ...node.attrs, visibility: visible }; let textSelection = TextSelection.create(view.state.doc, getPos() + 1); - if (!visible) { // update summarized text and save in attrs + if (!visible) { + // update summarized text and save in attrs textSelection = this.updateSummarizedText(getPos() + 1, view); attrs.text = textSelection.content(); attrs.textslice = attrs.text.toJSON(); } - view.dispatch(view.state.tr. - setSelection(textSelection). // select the current summarized text (or where it will be if its collapsed) - replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text). // collapse/expand it - setNodeMarkup(getPos(), undefined, attrs)); // update the attrs + view.dispatch( + view.state.tr + .setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed) + .replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it + .setNodeMarkup(getPos(), undefined, attrs) + ); // update the attrs e.preventDefault(); e.stopPropagation(); - this._fieldWrapper.className = this.className(visible); - } + this.dom.className = this.className(visible); + }; } -interface ISummaryView { -} +interface ISummaryView {} // currently nothing needs to be rendered for the internal view of a summary. export class SummaryViewInternal extends React.Component { render() { return <> ; } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 2fde5c7ba..00c41e187 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -1,66 +1,70 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { Doc } from "../../../../fields/Doc"; +import React = require('react'); +import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model'; +import { Doc } from '../../../../fields/Doc'; - -const emDOM: DOMOutputSpecArray = ["em", 0]; -const strongDOM: DOMOutputSpecArray = ["strong", 0]; -const codeDOM: DOMOutputSpecArray = ["code", 0]; +const emDOM: DOMOutputSpec = ['em', 0]; +const strongDOM: DOMOutputSpec = ['strong', 0]; +const codeDOM: DOMOutputSpec = ['code', 0]; // :: Object [Specs](#model.MarkSpec) for the marks in the schema. export const marks: { [index: string]: MarkSpec } = { splitter: { attrs: { - id: { default: "" } + id: { default: '' }, }, toDOM(node: any) { - return ["div", { className: "dummy" }, 0]; - } + return ['div', { className: 'dummy' }, 0]; + }, }, - // :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the - // published document's title. + // published document's title. // NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because - // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since + // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since // multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to // disambiguate links from one another. // Rendered and parsed as an `` // element. autoLinkAnchor: { attrs: { - allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] }, + allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, location: { default: null }, title: { default: null }, }, inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { - location: dom.getAttribute("location"), - title: dom.getAttribute("title") - }; - } - }], + parseDOM: [ + { + tag: 'a[href]', + getAttrs(dom: any) { + return { + location: dom.getAttribute('location'), + title: dom.getAttribute('title'), + }; + }, + }, + ], toDOM(node: any) { - const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, ""); - const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, ""); - return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; - } + const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), ''); + const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); + return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; + }, }, noAutoLinkAnchor: { attrs: {}, inclusive: false, - parseDOM: [{ - tag: "div", getAttrs(dom: any) { - return { - noAutoLink: dom.getAttribute("data-noAutoLink"), - }; - } - }], + parseDOM: [ + { + tag: 'div', + getAttrs(dom: any) { + return { + noAutoLink: dom.getAttribute('data-noAutoLink'), + }; + }, + }, + ], toDOM(node: any) { - return ["span", { "data-noAutoLink": "true" }, 0]; - } + return ['span', { 'data-noAutoLink': 'true' }, 0]; + }, }, // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text, // and a title for use in menus and hover. `title` @@ -68,31 +72,46 @@ export const marks: { [index: string]: MarkSpec } = { // element. linkAnchor: { attrs: { - allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] }, + allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, location: { default: null }, title: { default: null }, - docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text + docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text }, inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { - location: dom.getAttribute("location"), - title: dom.getAttribute("title") - }; - } - }], + parseDOM: [ + { + tag: 'a[href]', + getAttrs(dom: any) { + return { + location: dom.getAttribute('location'), + title: dom.getAttribute('title'), + }; + }, + }, + ], toDOM(node: any) { - const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.href : item.href, ""); - const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string, title: string, anchorId: string }) => p ? p + " " + item.anchorId : item.anchorId, ""); - return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { - ...node.attrs, - class: "prosemirror-attribution", - href: node.attrs.allAnchors[0].href, - }, node.attrs.title], ["br"]] : - //node.attrs.allLinks.length === 1 ? - ["a", { class: anchorids, "data-targethrefs": targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0]; + const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), ''); + const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); + return node.attrs.docref && node.attrs.title + ? [ + 'div', + ['span', `"`], + ['span', 0], + ['span', `"`], + ['br'], + [ + 'a', + { + ...node.attrs, + class: 'prosemirror-attribution', + href: node.attrs.allAnchors[0].href, + }, + node.attrs.title, + ], + ['br'], + ] + : //node.attrs.allLinks.length === 1 ? + ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0]; // ["div", { class: "prosemirror-anchor" }, // ["span", { class: "prosemirror-linkBtn" }, // ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0], @@ -102,254 +121,273 @@ export const marks: { [index: string]: MarkSpec } = { // ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title] // )] // ]; - } + }, }, /** FONT SIZES */ pFontSize: { - attrs: { fontSize: { default: "10px" } }, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : "" }; - } - }], - toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0] + attrs: { fontSize: { default: '10px' } }, + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + return { fontSize: dom.style.fontSize ? dom.style.fontSize.toString() : '' }; + }, + }, + ], + toDOM: node => (node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize};` }] : ['span', 0]), }, /* FONTS */ pFontFamily: { - attrs: { family: { default: "" } }, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - const cstyle = getComputedStyle(dom); - if (cstyle.font) { - if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; - if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; - if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; - if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; - if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; - if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; - } - } - }], - toDOM: (node) => node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0] + attrs: { family: { default: '' } }, + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + const cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' }; + if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' }; + if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' }; + if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' }; + if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' }; + if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' }; + } + return { family: '' }; + }, + }, + ], + toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]), }, // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. pFontColor: { - attrs: { color: { default: "" } }, + attrs: { color: { default: '' } }, inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { color: dom.getAttribute("color") }; - } - }], - toDOM: (node) => node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0] + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + return { color: dom.getAttribute('color') }; + }, + }, + ], + toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]), }, marker: { attrs: { - highlight: { default: "transparent" } + highlight: { default: 'transparent' }, }, inclusive: true, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - return { highlight: dom.getAttribute("backgroundColor") }; - } - }], + parseDOM: [ + { + tag: 'span', + getAttrs(dom: any) { + return { highlight: dom.getAttribute('backgroundColor') }; + }, + }, + ], toDOM(node: any) { return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; - } + }, }, // :: MarkSpec An emphasis mark. Rendered as an `` element. // Has parse rules that also match `` and `font-style: italic`. em: { - parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }], - toDOM() { return emDOM; } + parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style: italic' }], + toDOM() { + return emDOM; + }, }, // :: MarkSpec A strong mark. Rendered as ``, parse rules // also match `` and `font-weight: bold`. strong: { - parseDOM: [{ tag: "strong" }, - { tag: "b" }, - { style: "font-weight" }], - toDOM() { return strongDOM; } + parseDOM: [{ tag: 'strong' }, { tag: 'b' }, { style: 'font-weight' }], + toDOM() { + return strongDOM; + }, }, strikethrough: { - parseDOM: [ - { tag: 'strike' }, - { style: 'text-decoration=line-through' }, - { style: 'text-decoration-line=line-through' } + parseDOM: [{ tag: 'strike' }, { style: 'text-decoration=line-through' }, { style: 'text-decoration-line=line-through' }], + toDOM: () => [ + 'span', + { + style: 'text-decoration-line:line-through', + }, ], - toDOM: () => ['span', { - style: 'text-decoration-line:line-through' - }] }, subscript: { excludes: 'superscript', - parseDOM: [ - { tag: 'sub' }, - { style: 'vertical-align=sub' } - ], - toDOM: () => ['sub'] + parseDOM: [{ tag: 'sub' }, { style: 'vertical-align=sub' }], + toDOM: () => ['sub'], }, superscript: { excludes: 'subscript', - parseDOM: [ - { tag: 'sup' }, - { style: 'vertical-align=super' } - ], - toDOM: () => ['sup'] + parseDOM: [{ tag: 'sup' }, { style: 'vertical-align=super' }], + toDOM: () => ['sup'], }, mbulletType: { attrs: { - bulletType: { default: "decimal" } + bulletType: { default: 'decimal' }, }, toDOM(node: any) { - return ['span', { - style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}` - }]; - } + return [ + 'span', + { + style: `background: ${node.attrs.bulletType === 'decimal' ? 'yellow' : node.attrs.bulletType === 'upper-alpha' ? 'blue' : 'green'}`, + }, + ]; + }, }, metadata: { toDOM() { return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; - } + }, }, metadataKey: { toDOM() { return ['span', { style: 'font-style:italic; ' }]; - } + }, }, metadataVal: { toDOM() { return ['span']; - } + }, }, summarizeInclusive: { parseDOM: [ { - tag: "span", + tag: 'span', getAttrs: (p: any) => { - if (typeof (p) !== "string") { + if (typeof p !== 'string') { const style = getComputedStyle(p); - if (style.textDecoration === "underline") return null; - if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) { + if (style.textDecoration === 'underline') return null; + if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: solid') !== -1) { return null; } } return false; - } + }, }, ], inclusive: true, toDOM() { - return ['span', { - style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' - }]; - } + return [ + 'span', + { + style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)', + }, + ]; + }, }, summarize: { inclusive: false, parseDOM: [ { - tag: "span", + tag: 'span', getAttrs: (p: any) => { - if (typeof (p) !== "string") { + if (typeof p !== 'string') { const style = getComputedStyle(p); - if (style.textDecoration === "underline") return null; - if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { + if (style.textDecoration === 'underline') return null; + if (p.parentElement.outerHTML.indexOf('text-decoration: underline') !== -1 && p.parentElement.outerHTML.indexOf('text-decoration-style: dotted') !== -1) { return null; } } return false; - } + }, }, ], toDOM() { - return ['span', { - style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' - }]; - } + return [ + 'span', + { + style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)', + }, + ]; + }, }, underline: { parseDOM: [ { - tag: "span", + tag: 'span', getAttrs: (p: any) => { - if (typeof (p) !== "string") { + if (typeof p !== 'string') { const style = getComputedStyle(p); - if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) { + if (style.textDecoration === 'underline' || p.parentElement.outerHTML.indexOf('text-decoration-style:line') !== -1) { return null; } } return false; - } - } + }, + }, // { style: "text-decoration=underline" } ], - toDOM: () => ['span', { - style: 'text-decoration:underline;text-decoration-style:line' - }] + toDOM: () => [ + 'span', + { + style: 'text-decoration:underline;text-decoration-style:line', + }, + ], }, search_highlight: { attrs: { - selected: { default: false } + selected: { default: false }, }, parseDOM: [{ style: 'background: yellow' }], toDOM(node: any) { - return ['span', { style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }]; - } + return ['span', { style: `background: ${node.attrs.selected ? 'orange' : 'yellow'}` }]; + }, }, // the id of the user who entered the text user_mark: { attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 + userid: { default: '' }, + modified: { default: 'when?' }, // 1 second intervals since 1970 }, - excludes: "user_mark", - group: "inline", + excludes: 'user_mark', + group: 'inline', toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); + const uid = node.attrs.userid.replace('.', '').replace('@', ''); const min = Math.round(node.attrs.modified / 12); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); - const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " UM-remote" : ""; - return ['span', { class: "UM-" + uid + remote + " UM-min-" + min + " UM-hr-" + hr + " UM-day-" + day }, 0]; - } + const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : ''; + return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0]; + }, }, // the id of the user who entered the text user_tag: { attrs: { - userid: { default: "" }, - modified: { default: "when?" }, // 1 second intervals since 1970 - tag: { default: "" } + userid: { default: '' }, + modified: { default: 'when?' }, // 1 second intervals since 1970 + tag: { default: '' }, }, - group: "inline", + group: 'inline', inclusive: false, toDOM(node: any) { - const uid = node.attrs.userid.replace(".", "").replace("@", ""); - return ['span', { class: "UT-" + uid + " UT-" + node.attrs.tag }, 0]; - } + const uid = node.attrs.userid.replace('.', '').replace('@', ''); + return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0]; + }, }, - // :: MarkSpec Code font mark. Represented as a `` element. code: { - parseDOM: [{ tag: "code" }], - toDOM() { return codeDOM; } + parseDOM: [{ tag: 'code' }], + toDOM() { + return codeDOM; + }, }, }; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 2fe0a67cb..5142b7da6 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -1,15 +1,18 @@ -import React = require("react"); -import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; -import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./ParagraphNodeSpec"; +import React = require('react'); +import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model'; +import { listItem, orderedList } from 'prosemirror-schema-list'; +import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec'; -const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], - preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; +const blockquoteDOM: DOMOutputSpec = ['blockquote', 0], + hrDOM: DOMOutputSpec = ['hr'], + preDOM: DOMOutputSpec = ['pre', ['code', 0]], + brDOM: DOMOutputSpec = ['br'], + ulDOM: DOMOutputSpec = ['ul', 0]; function formatAudioTime(time: number) { time = Math.round(time); const hours = Math.floor(time / 60 / 60); - const minutes = Math.floor(time / 60) - (hours * 60); + const minutes = Math.floor(time / 60) - hours * 60; const seconds = time % 60; return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0'); @@ -19,67 +22,70 @@ function formatAudioTime(time: number) { export const nodes: { [index: string]: NodeSpec } = { // :: NodeSpec The top level document node. doc: { - content: "block+" + content: 'block+', }, paragraph: ParagraphNodeSpec, audiotag: { - group: "block", + group: 'block', attrs: { timeCode: { default: 0 }, - audioId: { default: "" }, - textId: { default: "" } + audioId: { default: '' }, + textId: { default: '' }, }, toDOM(node) { - return ['audiotag', + return [ + 'audiotag', { class: node.attrs.textId, // style: see FormattedTextBox.scss - "data-timecode": node.attrs.timeCode, - "data-audioid": node.attrs.audioId, - "data-textid": node.attrs.textId, + 'data-timecode': node.attrs.timeCode, + 'data-audioid': node.attrs.audioId, + 'data-textid': node.attrs.textId, }, - formatAudioTime(node.attrs.timeCode.toString()) + formatAudioTime(node.attrs.timeCode.toString()), ]; }, parseDOM: [ { - tag: "audiotag", getAttrs(dom: any) { + tag: 'audiotag', + getAttrs(dom: any) { return { - timeCode: dom.getAttribute("data-timecode"), - audioId: dom.getAttribute("data-audioid"), - textId: dom.getAttribute("data-textid") + timeCode: dom.getAttribute('data-timecode'), + audioId: dom.getAttribute('data-audioid'), + textId: dom.getAttribute('data-textid'), }; - } + }, }, - ] + ], }, footnote: { - group: "inline", - content: "inline*", + group: 'inline', + content: 'inline*', inline: true, attrs: { - visibility: { default: false } + visibility: { default: false }, }, // This makes the view treat the node as a leaf, even though it // technically has content atom: true, - toDOM: () => ["footnote", 0], - parseDOM: [{ tag: "footnote" }] + toDOM: () => ['footnote', 0], + parseDOM: [{ tag: 'footnote' }], }, // :: NodeSpec A blockquote (`
    `) wrapping one or more blocks. blockquote: { - content: "block*", - group: "block", + content: 'block*', + group: 'block', defining: true, - parseDOM: [{ tag: "blockquote" }], - toDOM() { return blockquoteDOM; } + parseDOM: [{ tag: 'blockquote' }], + toDOM() { + return blockquoteDOM; + }, }, - // blockquote: { // ...ParagraphNodeSpec, // defining: true, @@ -97,9 +103,11 @@ export const nodes: { [index: string]: NodeSpec } = { // :: NodeSpec A horizontal rule (`
    `). horizontal_rule: { - group: "block", - parseDOM: [{ tag: "hr" }], - toDOM() { return hrDOM; } + group: 'block', + parseDOM: [{ tag: 'hr' }], + toDOM() { + return hrDOM; + }, }, // :: NodeSpec A heading textblock, with a `level` attribute that @@ -112,12 +120,14 @@ export const nodes: { [index: string]: NodeSpec } = { level: { default: 1 }, }, defining: true, - parseDOM: [{ tag: "h1", attrs: { level: 1 } }, - { tag: "h2", attrs: { level: 2 } }, - { tag: "h3", attrs: { level: 3 } }, - { tag: "h4", attrs: { level: 4 } }, - { tag: "h5", attrs: { level: 5 } }, - { tag: "h6", attrs: { level: 6 } }], + parseDOM: [ + { tag: 'h1', attrs: { level: 1 } }, + { tag: 'h2', attrs: { level: 2 } }, + { tag: 'h3', attrs: { level: 3 } }, + { tag: 'h4', attrs: { level: 4 } }, + { tag: 'h5', attrs: { level: 5 } }, + { tag: 'h6', attrs: { level: 6 } }, + ], toDOM(node) { const dom = toParagraphDOM(node) as any; const level = node.attrs.level || 1; @@ -129,36 +139,38 @@ export const nodes: { [index: string]: NodeSpec } = { const level = Number(dom.nodeName.substring(1)) || 1; attrs.level = level; return attrs; - } + }, }, // :: NodeSpec A code listing. Disallows marks or non-text inline // nodes by default. Represented as a `
    ` element with a
         // `` element inside of it.
         code_block: {
    -        content: "inline*",
    -        marks: "_",
    -        group: "block",
    +        content: 'inline*',
    +        marks: '_',
    +        group: 'block',
             code: true,
             defining: true,
    -        parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
    -        toDOM() { return preDOM; }
    +        parseDOM: [{ tag: 'pre', preserveWhitespace: 'full' }],
    +        toDOM() {
    +            return preDOM;
    +        },
         },
     
         // :: NodeSpec The text node.
         text: {
    -        group: "inline"
    +        group: 'inline',
         },
     
         dashComment: {
             attrs: {
    -            docid: { default: "" },
    +            docid: { default: '' },
             },
             inline: true,
    -        group: "inline",
    +        group: 'inline',
             toDOM(node) {
                 const attrs = { style: `width: 40px` };
    -            return ["span", { ...node.attrs, ...attrs }, "←"];
    +            return ['span', { ...node.attrs, ...attrs }, '←'];
             },
         },
     
    @@ -169,10 +181,10 @@ export const nodes: { [index: string]: NodeSpec } = {
                 text: { default: undefined },
                 textslice: { default: undefined },
             },
    -        group: "inline",
    +        group: 'inline',
             toDOM(node) {
                 const attrs = { style: `width: 40px` };
    -            return ["span", { ...node.attrs, ...attrs }];
    +            return ['span', { ...node.attrs, ...attrs }];
             },
         },
     
    @@ -187,27 +199,30 @@ export const nodes: { [index: string]: NodeSpec } = {
                 width: { default: 100 },
                 alt: { default: null },
                 title: { default: null },
    -            float: { default: "left" },
    -            location: { default: "add:right" },
    -            docid: { default: "" }
    +            float: { default: 'left' },
    +            location: { default: 'add:right' },
    +            docid: { default: '' },
             },
    -        group: "inline",
    +        group: 'inline',
             draggable: true,
    -        parseDOM: [{
    -            tag: "img[src]", getAttrs(dom: any) {
    -                return {
    -                    src: dom.getAttribute("src"),
    -                    title: dom.getAttribute("title"),
    -                    alt: dom.getAttribute("alt"),
    -                    width: Math.min(100, Number(dom.getAttribute("width"))),
    -                };
    -            }
    -        }],
    +        parseDOM: [
    +            {
    +                tag: 'img[src]',
    +                getAttrs(dom: any) {
    +                    return {
    +                        src: dom.getAttribute('src'),
    +                        title: dom.getAttribute('title'),
    +                        alt: dom.getAttribute('alt'),
    +                        width: Math.min(100, Number(dom.getAttribute('width'))),
    +                    };
    +                },
    +            },
    +        ],
             // TODO if we don't define toDom, dragging the image crashes. Why?
             toDOM(node) {
                 const attrs = { style: `width: ${node.attrs.width}` };
    -            return ["img", { ...node.attrs, ...attrs }];
    -        }
    +            return ['img', { ...node.attrs, ...attrs }];
    +        },
         },
     
         dashDoc: {
    @@ -216,82 +231,87 @@ export const nodes: { [index: string]: NodeSpec } = {
                 width: { default: 200 },
                 height: { default: 100 },
                 title: { default: null },
    -            float: { default: "right" },
    +            float: { default: 'right' },
                 hidden: { default: false }, // whether dashComment node has toggle the dashDoc's display off
    -            fieldKey: { default: "" },
    -            docid: { default: "" },
    -            alias: { default: "" }
    +            fieldKey: { default: '' },
    +            docid: { default: '' },
    +            alias: { default: '' },
             },
    -        group: "inline",
    +        group: 'inline',
             draggable: false,
             toDOM(node) {
                 const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
    -            return ["div", { ...node.attrs, ...attrs }];
    -        }
    +            return ['div', { ...node.attrs, ...attrs }];
    +        },
         },
     
         dashField: {
             inline: true,
             attrs: {
    -            fieldKey: { default: "" },
    -            docid: { default: "" },
    -            hideKey: { default: false }
    +            fieldKey: { default: '' },
    +            docid: { default: '' },
    +            hideKey: { default: false },
             },
    -        group: "inline",
    +        group: 'inline',
             draggable: false,
             toDOM(node) {
                 const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
    -            return ["div", { ...node.attrs, ...attrs }];
    -        }
    +            return ['div', { ...node.attrs, ...attrs }];
    +        },
         },
     
         equation: {
             inline: true,
             attrs: {
    -            fieldKey: { default: "" },
    +            fieldKey: { default: '' },
             },
             atom: true,
    -        group: "inline",
    +        group: 'inline',
             draggable: false,
             toDOM(node) {
                 const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
    -            return ["div", { ...node.attrs, ...attrs }];
    -        }
    +            return ['div', { ...node.attrs, ...attrs }];
    +        },
         },
     
         video: {
             inline: true,
             attrs: {
                 src: {},
    -            width: { default: "100px" },
    +            width: { default: '100px' },
                 alt: { default: null },
    -            title: { default: null }
    +            title: { default: null },
             },
    -        group: "inline",
    +        group: 'inline',
             draggable: true,
    -        parseDOM: [{
    -            tag: "video[src]", getAttrs(dom: any) {
    -                return {
    -                    src: dom.getAttribute("src"),
    -                    title: dom.getAttribute("title"),
    -                    alt: dom.getAttribute("alt"),
    -                    width: Math.min(100, Number(dom.getAttribute("width"))),
    -                };
    -            }
    -        }],
    +        parseDOM: [
    +            {
    +                tag: 'video[src]',
    +                getAttrs(dom: any) {
    +                    return {
    +                        src: dom.getAttribute('src'),
    +                        title: dom.getAttribute('title'),
    +                        alt: dom.getAttribute('alt'),
    +                        width: Math.min(100, Number(dom.getAttribute('width'))),
    +                    };
    +                },
    +            },
    +        ],
             toDOM(node) {
                 const attrs = { style: `width: ${node.attrs.width}` };
    -            return ["video", { ...node.attrs, ...attrs }];
    -        }
    +            return ['video', { ...node.attrs, ...attrs }];
    +        },
         },
     
         // :: NodeSpec A hard line break, represented in the DOM as `
    `. hard_break: { inline: true, - group: "inline", + group: 'inline', selectable: false, - parseDOM: [{ tag: "br" }], - toDOM() { return brDOM; } + parseDOM: [{ tag: 'br' }], + toDOM() { + return brDOM; + }, }, ordered_list: { @@ -300,85 +320,108 @@ export const nodes: { [index: string]: NodeSpec } = { group: 'block', attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" },// "decimal", "multi", "bullet" - fontColor: { default: "inherit" }, + mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet" + fontColor: { default: 'inherit' }, fontSize: { default: undefined }, fontFamily: { default: undefined }, visibility: { default: true }, - indent: { default: undefined } + indent: { default: undefined }, }, parseDOM: [ { - tag: "ul", getAttrs(dom: any) { + tag: 'ul', + getAttrs(dom: any) { return { - bulletStyle: dom.getAttribute("data-bulletStyle"), - mapStyle: dom.getAttribute("data-mapStyle"), + bulletStyle: dom.getAttribute('data-bulletStyle'), + mapStyle: dom.getAttribute('data-mapStyle'), fontColor: dom.style.color, - fontSize: dom.style["font-size"], - fontFamily: dom.style["font-family"], - indent: dom.style["margin-left"] + fontSize: dom.style['font-size'], + fontFamily: dom.style['font-family'], + indent: dom.style['margin-left'], }; - } + }, }, { - style: 'list-style-type=disc', getAttrs(dom: any) { - return { mapStyle: "bullet" }; - } + style: 'list-style-type=disc', + getAttrs(dom: any) { + return { mapStyle: 'bullet' }; + }, }, { - tag: "ol", getAttrs(dom: any) { + tag: 'ol', + getAttrs(dom: any) { return { - bulletStyle: dom.getAttribute("data-bulletStyle"), - mapStyle: dom.getAttribute("data-mapStyle"), + bulletStyle: dom.getAttribute('data-bulletStyle'), + mapStyle: dom.getAttribute('data-mapStyle'), fontColor: dom.style.color, - fontSize: dom.style["font-size"], - fontFamily: dom.style["font-family"], - indent: dom.style["margin-left"] + fontSize: dom.style['font-size'], + fontFamily: dom.style['font-family'], + indent: dom.style['margin-left'], }; - } - }], - toDOM(node: Node) { - const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : ""; - const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : ""; - const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : ""; - const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ""; - if (node.attrs.mapStyle === "bullet") { - return ['ul', { - "data-mapStyle": node.attrs.mapStyle, - "data-bulletStyle": node.attrs.bulletStyle, - style: `${fsize} ${ffam} ${fcol} ${marg}` - }, 0]; + }, + }, + ], + toDOM(node: Node) { + const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; + const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : ''; + const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : ''; + const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : ''; + const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ''; + if (node.attrs.mapStyle === 'bullet') { + return [ + 'ul', + { + 'data-mapStyle': node.attrs.mapStyle, + 'data-bulletStyle': node.attrs.bulletStyle, + style: `${fsize} ${ffam} ${fcol} ${marg}`, + }, + 0, + ]; } - return node.attrs.visibility ? - ['ol', { - class: `${map}-ol`, - "data-mapStyle": node.attrs.mapStyle, - "data-bulletStyle": node.attrs.bulletStyle, - style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}` - }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; - } + return node.attrs.visibility + ? [ + 'ol', + { + class: `${map}-ol`, + 'data-mapStyle': node.attrs.mapStyle, + 'data-bulletStyle': node.attrs.bulletStyle, + style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`, + }, + 0, + ] + : ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; + }, }, list_item: { ...listItem, attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet" - visibility: { default: true } + mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet" + visibility: { default: true }, }, content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)', - parseDOM: [{ - tag: "li", getAttrs(dom: any) { - return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") }; - } - }], + parseDOM: [ + { + tag: 'li', + getAttrs(dom: any) { + return { mapStyle: dom.getAttribute('data-mapStyle'), bulletStyle: dom.getAttribute('data-bulletStyle') }; + }, + }, + ], toDOM(node: any) { - const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - return ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, node.attrs.visibility ? 0 : - ["span", { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== "bullet" ? "inline-block" : "list-item"}; text-overflow: ellipsis; white-space: pre` }, - `${node.firstChild?.textContent}...`]]; - } + const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ''; + return [ + 'li', + { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle }, + node.attrs.visibility + ? 0 + : [ + 'span', + { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` }, + `${node.firstChild?.textContent}...`, + ], + ]; + }, }, -}; \ No newline at end of file +}; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index b30ca644d..31a50301a 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1,59 +1,54 @@ -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { saveAs } from "file-saver"; -import { action, computed, observable, ObservableMap, runInAction } from "mobx"; -import { computedFn } from "mobx-utils"; -import { alias, map, serializable } from "serializr"; -import { DocServer } from "../client/DocServer"; -import { DocumentType } from "../client/documents/DocumentTypes"; -import { CurrentUserUtils } from "../client/util/CurrentUserUtils"; -import { LinkManager } from "../client/util/LinkManager"; -import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals"; -import { SelectionManager } from "../client/util/SelectionManager"; -import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper"; -import { UndoManager } from "../client/util/UndoManager"; -import { DashColor, incrementTitleCopy, intersectRect, Utils } from "../Utils"; -import { DateField } from "./DateField"; -import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols"; -import { List } from "./List"; -import { ObjectField } from "./ObjectField"; -import { PrefetchProxy, ProxyField } from "./Proxy"; -import { FieldId, RefField } from "./RefField"; -import { RichTextField } from "./RichTextField"; -import { listSpec } from "./Schema"; -import { ComputedField, ScriptField } from "./ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; -import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField"; -import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; -import JSZip = require("jszip"); +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { saveAs } from 'file-saver'; +import { action, computed, observable, ObservableMap, runInAction } from 'mobx'; +import { computedFn } from 'mobx-utils'; +import { alias, map, serializable } from 'serializr'; +import { DocServer } from '../client/DocServer'; +import { DocumentType } from '../client/documents/DocumentTypes'; +import { CurrentUserUtils } from '../client/util/CurrentUserUtils'; +import { LinkManager } from '../client/util/LinkManager'; +import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; +import { SelectionManager } from '../client/util/SelectionManager'; +import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; +import { UndoManager } from '../client/util/UndoManager'; +import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; +import { DateField } from './DateField'; +import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from './FieldSymbols'; +import { List } from './List'; +import { ObjectField } from './ObjectField'; +import { PrefetchProxy, ProxyField } from './Proxy'; +import { FieldId, RefField } from './RefField'; +import { RichTextField } from './RichTextField'; +import { listSpec } from './Schema'; +import { ComputedField, ScriptField } from './ScriptField'; +import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; +import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from './URLField'; +import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from './util'; +import JSZip = require('jszip'); export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key); const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - return !Field.IsField(field) ? "" : (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); + return !Field.IsField(field) ? '' : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); } export function toScriptString(field: Field): string { - if (typeof field === "string") return `"${field}"`; - if (typeof field === "number" || typeof field === "boolean") return String(field); - if (field === undefined || field === null) return "null"; + if (typeof field === 'string') return `"${field}"`; + if (typeof field === 'number' || typeof field === 'boolean') return String(field); + if (field === undefined || field === null) return 'null'; return field[ToScriptString](); } export function toString(field: Field): string { - if (typeof field === "string") return field; - if (typeof field === "number" || typeof field === "boolean") return String(field); + if (typeof field === 'string') return field; + if (typeof field === 'number' || typeof field === 'boolean') return String(field); if (field instanceof ObjectField) return field[ToString](); if (field instanceof RefField) return field[ToString](); - return ""; + return ''; } export function IsField(field: any): field is Field; export function IsField(field: any, includeUndefined: true): field is Field | undefined; export function IsField(field: any, includeUndefined: boolean = false): field is Field | undefined { - return (typeof field === "string") - || (typeof field === "number") - || (typeof field === "boolean") - || (field instanceof ObjectField) - || (field instanceof RefField) - || (includeUndefined && field === undefined); + return typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean' || field instanceof ObjectField || field instanceof RefField || (includeUndefined && field === undefined); } export function Copy(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; @@ -77,40 +72,50 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); } -export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } - -export function NumListCast(field: FieldResult) { return Cast(field, listSpec("number"), []); } -export function StrListCast(field: FieldResult) { return Cast(field, listSpec("string"), []); } -export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; } -export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; } - -export const WidthSym = Symbol("Width"); -export const HeightSym = Symbol("Height"); -export const DataSym = Symbol("Data"); -export const LayoutSym = Symbol("Layout"); -export const FieldsSym = Symbol("Fields"); -export const AclSym = Symbol("Acl"); -export const DirectLinksSym = Symbol("DirectLinks"); -export const AclUnset = Symbol("AclUnset"); -export const AclPrivate = Symbol("AclOwnerOnly"); -export const AclReadonly = Symbol("AclReadOnly"); -export const AclAugment = Symbol("AclAugment"); -export const AclSelfEdit = Symbol("AclSelfEdit"); -export const AclEdit = Symbol("AclEdit"); -export const AclAdmin = Symbol("AclAdmin"); -export const UpdatingFromServer = Symbol("UpdatingFromServer"); -export const Initializing = Symbol("Initializing"); -export const ForceServerWrite = Symbol("ForceServerWrite"); -export const CachedUpdates = Symbol("Cached updates"); +export async function DocCastAsync(field: FieldResult): Promise> { + return Cast(field, Doc); +} + +export function NumListCast(field: FieldResult) { + return Cast(field, listSpec('number'), []); +} +export function StrListCast(field: FieldResult) { + return Cast(field, listSpec('string'), []); +} +export function DocListCast(field: FieldResult) { + return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; +} +export function DocListCastOrNull(field: FieldResult) { + return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; +} + +export const WidthSym = Symbol('Width'); +export const HeightSym = Symbol('Height'); +export const DataSym = Symbol('Data'); +export const LayoutSym = Symbol('Layout'); +export const FieldsSym = Symbol('Fields'); +export const AclSym = Symbol('Acl'); +export const DirectLinksSym = Symbol('DirectLinks'); +export const AclUnset = Symbol('AclUnset'); +export const AclPrivate = Symbol('AclOwnerOnly'); +export const AclReadonly = Symbol('AclReadOnly'); +export const AclAugment = Symbol('AclAugment'); +export const AclSelfEdit = Symbol('AclSelfEdit'); +export const AclEdit = Symbol('AclEdit'); +export const AclAdmin = Symbol('AclAdmin'); +export const UpdatingFromServer = Symbol('UpdatingFromServer'); +export const Initializing = Symbol('Initializing'); +export const ForceServerWrite = Symbol('ForceServerWrite'); +export const CachedUpdates = Symbol('Cached updates'); const AclMap = new Map([ - ["None", AclUnset], + ['None', AclUnset], [SharingPermissions.None, AclPrivate], [SharingPermissions.View, AclReadonly], [SharingPermissions.Augment, AclAugment], [SharingPermissions.SelfEdit, AclSelfEdit], [SharingPermissions.Edit, AclEdit], - [SharingPermissions.Admin, AclAdmin] + [SharingPermissions.Admin, AclAdmin], ]); // caches the document access permissions for the current user. @@ -120,7 +125,7 @@ export function updateCachedAcls(doc: Doc) { const permissions: { [key: string]: symbol } = {}; doc[UpdatingFromServer] = true; - Object.keys(doc).filter(key => key.startsWith("acl") && (permissions[key] = AclMap.get(StrCast(doc[key]))!)); + Object.keys(doc).filter(key => key.startsWith('acl') && (permissions[key] = AclMap.get(StrCast(doc[key]))!)); doc[UpdatingFromServer] = false; if (Object.keys(permissions).length) { @@ -134,7 +139,7 @@ export function updateCachedAcls(doc: Doc) { } @scriptingGlobal -@Deserializable("Doc", updateCachedAcls).withFields(["id"]) +@Deserializable('Doc', updateCachedAcls).withFields(['id']) export class Doc extends RefField { constructor(id?: FieldId, forceSave?: boolean) { super(id); @@ -146,24 +151,26 @@ export class Doc extends RefField { ownKeys: target => { const obj = {} as any; if (GetEffectiveAcl(target) !== AclPrivate) Object.assign(obj, target.___fieldKeys); - runInAction(() => obj.__LAYOUT__ = target.__LAYOUT__); + runInAction(() => (obj.__LAYOUT__ = target.__LAYOUT__)); return Object.keys(obj); }, getOwnPropertyDescriptor: (target, prop) => { - if (prop.toString() === "__LAYOUT__") { + if (prop.toString() === '__LAYOUT__') { return Reflect.getOwnPropertyDescriptor(target, prop); } if (prop in target.__fieldKeys) { return { - configurable: true,//TODO Should configurable be true? + configurable: true, //TODO Should configurable be true? enumerable: true, - value: 0//() => target.__fields[prop]) + value: 0, //() => target.__fields[prop]) }; } return Reflect.getOwnPropertyDescriptor(target, prop); }, deleteProperty: deleteProperty, - defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, + defineProperty: () => { + throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); + }, }); this[SelfProxy] = doc; if (!id || forceSave) { @@ -175,24 +182,30 @@ export class Doc extends RefField { proto: Opt; [key: string]: FieldResult; - @serializable(alias("fields", map(autoObject(), { afterDeserialize: afterDocDeserialize }))) - private get __fields() { return this.___fields; } + @serializable(alias('fields', map(autoObject(), { afterDeserialize: afterDocDeserialize }))) + private get __fields() { + return this.___fields; + } private set __fields(value) { this.___fields = value; for (const key in value) { const field = value[key]; - (field !== undefined) && (this.__fieldKeys[key] = true); + field !== undefined && (this.__fieldKeys[key] = true); if (!(field instanceof ObjectField)) continue; field[Parent] = this[Self]; field[OnUpdate] = updateFunction(this[Self], key, field, this[SelfProxy]); } } - private get __fieldKeys() { return this.___fieldKeys; } - private set __fieldKeys(value) { this.___fieldKeys = value; } + private get __fieldKeys() { + return this.___fieldKeys; + } + private set __fieldKeys(value) { + this.___fieldKeys = value; + } @observable private ___fields: any = {}; @observable private ___fieldKeys: any = {}; - @observable public [AclSym]: { [key: string]: symbol }; + @observable public [AclSym]: { [key: string]: symbol } = {}; @observable public [DirectLinksSym]: Set = new Set(); private [UpdatingFromServer]: boolean = false; @@ -201,7 +214,7 @@ export class Doc extends RefField { private [Update] = (diff: any) => { (!this[UpdatingFromServer] || this[ForceServerWrite]) && DocServer.UpdateField(this[Id], diff); - } + }; private [Self] = this; private [SelfProxy]: any; @@ -209,42 +222,52 @@ export class Doc extends RefField { public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`; - public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? "-inaccessible-" : this[SelfProxy].title})`; - public get [LayoutSym]() { return this[SelfProxy].__LAYOUT__; } + public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`; + public get [LayoutSym]() { + return this[SelfProxy].__LAYOUT__; + } public get [DataSym]() { const self = this[SelfProxy]; - return self.resolvedDataDoc && !self.isTemplateForField ? self : - Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); + return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } @computed get __LAYOUT__(): Doc | undefined { const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); if (templateLayoutDoc) { let renderFieldKey: any; - const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, "layout")]; - if (typeof layoutField === "string") { - renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0];//layoutField.split("'")[1]; + const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layoutKey, 'layout')]; + if (typeof layoutField === 'string') { + renderFieldKey = layoutField.split("fieldKey={'")[1].split("'")[0]; //layoutField.split("'")[1]; } else { return Cast(layoutField, Doc, null); } - return Cast(this[SelfProxy][renderFieldKey + "-layout[" + templateLayoutDoc[Id] + "]"], Doc, null) || templateLayoutDoc; + return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; } return undefined; - } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; - public static get noviceMode() { return Doc.UserDoc().noviceMode as boolean; } - public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } - public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } - public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; } - public static CurrentUserEmail: string = ""; - public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); } + public static get noviceMode() { + return Doc.UserDoc().noviceMode as boolean; + } + public static set noviceMode(val) { + Doc.UserDoc().noviceMode = val; + } + public static get defaultAclPrivate() { + return Doc.UserDoc().defaultAclPrivate; + } + public static set defaultAclPrivate(val) { + Doc.UserDoc().defaultAclPrivate = val; + } + public static CurrentUserEmail: string = ''; + public static get CurrentUserEmailNormalized() { + return normalizeEmail(Doc.CurrentUserEmail); + } public async [HandleUpdate](diff: any) { const set = diff.$set; const sameAuthor = this.author === Doc.CurrentUserEmail; if (set) { for (const key in set) { - const fprefix = "fields."; + const fprefix = 'fields.'; if (!key.startsWith(fprefix)) { continue; } @@ -255,7 +278,7 @@ export class Doc extends RefField { this[UpdatingFromServer] = true; this[fKey] = value; this[UpdatingFromServer] = false; - if (fKey.startsWith("acl")) { + if (fKey.startsWith('acl')) { updateCachedAcls(this); } if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { @@ -263,7 +286,7 @@ export class Doc extends RefField { } }; const writeMode = DocServer.getFieldWriteMode(fKey); - if (fKey.startsWith("acl") || writeMode !== DocServer.WriteMode.Playground) { + if (fKey.startsWith('acl') || writeMode !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; await fn(); } else { @@ -274,7 +297,7 @@ export class Doc extends RefField { const unset = diff.$unset; if (unset) { for (const key in unset) { - if (!key.startsWith("fields.")) { + if (!key.startsWith('fields.')) { continue; } const fKey = key.substring(7); @@ -326,7 +349,7 @@ export namespace Doc { return { end() { makeEditable(); - } + }, }; } @@ -341,16 +364,16 @@ export namespace Doc { return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult; } export function IsPrototype(doc: Doc) { - return GetT(doc, "isPrototype", "boolean", true); + return GetT(doc, 'isPrototype', 'boolean', true); } export function IsBaseProto(doc: Doc) { - return GetT(doc, "baseProto", "boolean", true); + return GetT(doc, 'baseProto', 'boolean', true); } export function IsSystem(doc: Doc) { - return GetT(doc, "system", "boolean", true); + return GetT(doc, 'system', 'boolean', true); } export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { - if (key.startsWith("_")) key = key.substring(1); + if (key.startsWith('_')) key = key.substring(1); const hasProto = doc.proto instanceof Doc; const onDeleg = Object.getOwnPropertyNames(doc).indexOf(key) !== -1; const onProto = hasProto && Object.getOwnPropertyNames(doc.proto).indexOf(key) !== -1; @@ -359,7 +382,7 @@ export namespace Doc { } else doc.proto![key] = value; } export async function SetOnPrototype(doc: Doc, key: string, value: Field) { - const proto = Object.getOwnPropertyNames(doc).indexOf("isPrototype") === -1 ? doc.proto : doc; + const proto = Object.getOwnPropertyNames(doc).indexOf('isPrototype') === -1 ? doc.proto : doc; if (proto) { proto[key] = value; @@ -391,7 +414,8 @@ export namespace Doc { for (const key in fields) { if (fields.hasOwnProperty(key)) { const value = fields[key]; - if (!skipUndefineds || value !== undefined) { // Do we want to filter out undefineds? + if (!skipUndefineds || value !== undefined) { + // Do we want to filter out undefineds? doc[key] = value; } } @@ -403,10 +427,10 @@ export namespace Doc { // compare whether documents or their protos match export function AreProtosEqual(doc?: Doc, other?: Doc) { if (!doc || !other) return false; - const r = (doc === other); - const r2 = (Doc.GetProto(doc) === other); - const r3 = (Doc.GetProto(other) === doc); - const r4 = (Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined); + const r = doc === other; + const r2 = Doc.GetProto(doc) === other; + const r3 = Doc.GetProto(other) === doc; + const r4 = Doc.GetProto(doc) === Doc.GetProto(other) && Doc.GetProto(other) !== undefined; return r || r2 || r3 || r4; } @@ -417,7 +441,7 @@ export namespace Doc { if (doc instanceof Promise) { // console.log("GetProto: warning: got Promise insead of Doc"); } - const proto = doc && (Doc.GetT(doc, "isPrototype", "boolean", true) ? doc : (doc.proto || doc)); + const proto = doc && (Doc.GetT(doc, 'isPrototype', 'boolean', true) ? doc : doc.proto || doc); return proto === doc ? proto : Doc.GetProto(proto); } export function GetDataDoc(doc: Doc): Doc { @@ -426,7 +450,7 @@ export namespace Doc { } export function allKeys(doc: Doc): string[] { - const results: Set = new Set; + const results: Set = new Set(); let proto: Doc | undefined = doc; while (proto) { @@ -441,8 +465,8 @@ export namespace Doc { * @returns the index of doc toFind in list of docs, -1 otherwise */ export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) { - let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1); - index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1); + let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind ? i : p), -1); + index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind) ? i : p), -1); return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); } @@ -478,7 +502,7 @@ export namespace Doc { const list = Cast(listDoc[key], listSpec(Doc)); if (list) { if (allowDuplicates !== true) { - const pind = list.reduce((l, d, i) => d instanceof Doc && d[Id] === doc[Id] ? i : l, -1); + const pind = list.reduce((l, d, i) => (d instanceof Doc && d[Id] === doc[Id] ? i : l), -1); if (pind !== -1) { return true; //list.splice(pind, 1); // bcz: this causes schemaView docs in the Catalog to move to the bottom of the schema view when they are dragged even though they haven't left the collection @@ -486,15 +510,13 @@ export namespace Doc { } if (first) { list.splice(0, 0, doc); - } - else { + } else { const ind = relativeTo ? list.indexOf(relativeTo) : -1; if (ind === -1) { if (reversed) list.splice(0, 0, doc); else list.push(doc); - } - else { - if (reversed) list.splice(before ? (list.length - ind) + 1 : list.length - ind, 0, doc); + } else { + if (reversed) list.splice(before ? list.length - ind + 1 : list.length - ind, 0, doc); else list.splice(before ? ind : ind + 1, 0, doc); } } @@ -507,19 +529,24 @@ export namespace Doc { * Computes the bounds of the contents of a set of documents. */ export function ComputeContentBounds(docList: Doc[]) { - const bounds = docList.reduce((bounds, doc) => { - const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; - const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }); + const bounds = docList.reduce( + (bounds, doc) => { + const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; + const [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + return { + x: Math.min(sptX, bounds.x), + y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), + b: Math.max(bptY, bounds.b), + }; + }, + { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE } + ); return bounds; } export function MakeAlias(doc: Doc, id?: string) { - const alias = !GetT(doc, "isPrototype", "boolean", true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); + const alias = !GetT(doc, 'isPrototype', 'boolean', true) && doc.proto ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); const layout = Doc.LayoutField(alias); if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) { Doc.SetLayout(alias, Doc.MakeAlias(layout)); @@ -529,71 +556,73 @@ export namespace Doc { alias.title = ComputedField.MakeFunction(`renameAlias(this)`); alias.author = Doc.CurrentUserEmail; - Doc.AddDocToList(Doc.GetProto(doc)[DataSym], "aliases", alias); + Doc.AddDocToList(Doc.GetProto(doc)[DataSym], 'aliases', alias); return alias; } - export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc, key: string, field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise { + export async function makeClone(doc: Doc, cloneMap: Map, linkMap: Map, rtfs: { copy: Doc; key: string; field: RichTextField }[], exclusions: string[], dontCreate: boolean, asBranch: boolean): Promise { if (Doc.IsBaseProto(doc)) return doc; if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; - const copy = dontCreate ? asBranch ? (Cast(doc.branchMaster, Doc, null) || doc) : doc : new Doc(undefined, true); + const copy = dontCreate ? (asBranch ? Cast(doc.branchMaster, Doc, null) || doc : doc) : new Doc(undefined, true); cloneMap.set(doc[Id], copy); - const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== "annotationOn") : exclusions; - const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec("string"), [])]; - await Promise.all(Object.keys(doc).map(async key => { - if (filter.includes(key)) return; - const assignKey = (val: any) => !dontCreate && (copy[key] = val); - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - const field = ProxyField.WithoutProxy(() => doc[key]); - const copyObjectField = async (field: ObjectField) => { - const list = await Cast(doc[key], listSpec(Doc)); - const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); - if (docs !== undefined && docs.length) { - const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch))); - !dontCreate && assignKey(new List(clones)); - } else if (doc[key] instanceof Doc) { - assignKey(key.includes("layout[") ? undefined : key.startsWith("layout") ? doc[key] as Doc : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields - } else { - !dontCreate && assignKey(ObjectField.MakeCopy(field)); - if (field instanceof RichTextField) { - if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) { - rtfs.push({ copy, key, field }); + const fieldExclusions = doc.type === DocumentType.MARKER ? exclusions.filter(ex => ex !== 'annotationOn') : exclusions; + const filter = [...fieldExclusions, ...Cast(doc.cloneFieldFilter, listSpec('string'), [])]; + await Promise.all( + Object.keys(doc).map(async key => { + if (filter.includes(key)) return; + const assignKey = (val: any) => !dontCreate && (copy[key] = val); + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + const field = ProxyField.WithoutProxy(() => doc[key]); + const copyObjectField = async (field: ObjectField) => { + const list = await Cast(doc[key], listSpec(Doc)); + const docs = list && (await DocListCastAsync(list))?.filter(d => d instanceof Doc); + if (docs !== undefined && docs.length) { + const clones = await Promise.all(docs.map(async d => Doc.makeClone(d, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch))); + !dontCreate && assignKey(new List(clones)); + } else if (doc[key] instanceof Doc) { + assignKey(key.includes('layout[') ? undefined : key.startsWith('layout') ? (doc[key] as Doc) : await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); // reference documents except copy documents that are expanded template fields + } else { + !dontCreate && assignKey(ObjectField.MakeCopy(field)); + if (field instanceof RichTextField) { + if (field.Data.includes('"audioId":') || field.Data.includes('"textId":') || field.Data.includes('"anchorId":')) { + rtfs.push({ copy, key, field }); + } } } - } - }; - if (key === "proto") { - if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); - } - } else if (key === "anchor1" || key === "anchor2") { - if (doc[key] instanceof Doc) { - assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch)); - } - } else { - if (field instanceof RefField) { - assignKey(field); - } else if (cfield instanceof ComputedField) { - !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript)); - } else if (field instanceof ObjectField) { - await copyObjectField(field); - } else if (field instanceof Promise) { - debugger; //This shouldn't happen... + }; + if (key === 'proto') { + if (doc[key] instanceof Doc) { + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch)); + } + } else if (key === 'anchor1' || key === 'anchor2') { + if (doc[key] instanceof Doc) { + assignKey(await Doc.makeClone(doc[key] as Doc, cloneMap, linkMap, rtfs, exclusions, true, asBranch)); + } } else { - assignKey(field); + if (field instanceof RefField) { + assignKey(field); + } else if (cfield instanceof ComputedField) { + !dontCreate && assignKey(ComputedField.MakeFunction(cfield.script.originalScript)); + } else if (field instanceof ObjectField) { + await copyObjectField(field); + } else if (field instanceof Promise) { + debugger; //This shouldn't happen... + } else { + assignKey(field); + } } - } - })); + }) + ); for (const link of Array.from(doc[DirectLinksSym])) { const linkClone = await Doc.makeClone(link, cloneMap, linkMap, rtfs, exclusions, dontCreate, asBranch); linkMap.set(link, linkClone); } if (!dontCreate) { - Doc.SetInPlace(copy, "title", (asBranch ? "BRANCH: " : "CLONE: ") + doc.title, true); + Doc.SetInPlace(copy, 'title', (asBranch ? 'BRANCH: ' : 'CLONE: ') + doc.title, true); asBranch ? (copy.branchOf = doc) : (copy.cloneOf = doc); if (!Doc.IsPrototype(copy)) { - Doc.AddDocToList(doc, "branches", Doc.GetProto(copy)); + Doc.AddDocToList(doc, 'branches', Doc.GetProto(copy)); } cloneMap.set(doc[Id], copy); } @@ -601,20 +630,20 @@ export namespace Doc { } export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { const linkMap = new Map(); - const rtfMap: { copy: Doc, key: string, field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ["cloneOf", "branches", "branchOf"], dontCreate, asBranch); + const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch); Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); - return attr + "\"" + (mapped ? mapped[Id] : id) + "\""; + return attr + '"' + (mapped ? mapped[Id] : id) + '"'; }; const replacer2 = (match: any, href: string, id: string, offset: any, string: any) => { const mapped = cloneMap.get(id); return href + (mapped ? mapped[Id] : id); }; const regex = `(${Doc.localServerPath()})([^"]*)`; - const re = new RegExp(regex, "g"); + const re = new RegExp(regex, 'g'); copy[key] = new RichTextField(field.Data.replace(/("textId":|"audioId":|"anchorId":)"([^"]+)"/g, replacer).replace(re, replacer2), field.Text); }); return { clone: copy, map: cloneMap }; @@ -628,37 +657,36 @@ export namespace Doc { // a.click(); const { clone, map } = await Doc.MakeClone(doc, true); function replacer(key: any, value: any) { - if (["branchOf", "cloneOf", "context", "cursors"].includes(key)) return undefined; + if (['branchOf', 'cloneOf', 'context', 'cursors'].includes(key)) return undefined; else if (value instanceof Doc) { - if (key !== "field" && Number.isNaN(Number(key))) { + if (key !== 'field' && Number.isNaN(Number(key))) { const __fields = value[FieldsSym](); - return { id: value[Id], __type: "Doc", fields: __fields }; + return { id: value[Id], __type: 'Doc', fields: __fields }; } else { - return { fieldId: value[Id], __type: "proxy" }; + return { fieldId: value[Id], __type: 'proxy' }; } - } - else if (value instanceof ScriptField) return { script: value.script, __type: "script" }; - else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: "RichTextField" }; - else if (value instanceof ImageField) return { url: value.url.href, __type: "image" }; - else if (value instanceof PdfField) return { url: value.url.href, __type: "pdf" }; - else if (value instanceof AudioField) return { url: value.url.href, __type: "audio" }; - else if (value instanceof VideoField) return { url: value.url.href, __type: "video" }; - else if (value instanceof WebField) return { url: value.url.href, __type: "web" }; - else if (value instanceof MapField) return { url: value.url.href, __type: "map" }; - else if (value instanceof DateField) return { date: value.toString(), __type: "date" }; - else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: "proxy" }; - else if (value instanceof Array && key !== "fields") return { fields: value, __type: "list" }; - else if (value instanceof ComputedField) return { script: value.script, __type: "computed" }; + } else if (value instanceof ScriptField) return { script: value.script, __type: 'script' }; + else if (value instanceof RichTextField) return { Data: value.Data, Text: value.Text, __type: 'RichTextField' }; + else if (value instanceof ImageField) return { url: value.url.href, __type: 'image' }; + else if (value instanceof PdfField) return { url: value.url.href, __type: 'pdf' }; + else if (value instanceof AudioField) return { url: value.url.href, __type: 'audio' }; + else if (value instanceof VideoField) return { url: value.url.href, __type: 'video' }; + else if (value instanceof WebField) return { url: value.url.href, __type: 'web' }; + else if (value instanceof MapField) return { url: value.url.href, __type: 'map' }; + else if (value instanceof DateField) return { date: value.toString(), __type: 'date' }; + else if (value instanceof ProxyField) return { fieldId: value.fieldId, __type: 'proxy' }; + else if (value instanceof Array && key !== 'fields') return { fields: value, __type: 'list' }; + else if (value instanceof ComputedField) return { script: value.script, __type: 'computed' }; else return value; } const docs: { [id: string]: any } = {}; - Array.from(map.entries()).forEach(f => docs[f[0]] = f[1]); + Array.from(map.entries()).forEach(f => (docs[f[0]] = f[1])); const docString = JSON.stringify({ id: doc[Id], docs }, replacer); const zip = new JSZip(); - zip.file("doc.json", docString); + zip.file('doc.json', docString); // // Generate a directory within the Zip file structure // var img = zip.folder("images"); @@ -667,11 +695,10 @@ export namespace Doc { // img.file("smile.gif", imgData, {base64: true}); // Generate the zip file asynchronously - zip.generateAsync({ type: "blob" }) - .then((content: any) => { - // Force down of the Zip file - saveAs(content, doc.title + ".zip"); // glr: Possibly change the name of the document to match the title? - }); + zip.generateAsync({ type: 'blob' }).then((content: any) => { + // Force down of the Zip file + saveAs(content, doc.title + '.zip'); // glr: Possibly change the name of the document to match the title? + }); } // // Determines whether the layout needs to be expanded (as a template). @@ -694,46 +721,47 @@ export namespace Doc { // layout_mytemplate(somparam=somearg). // then any references to @someparam would be rewritten as accesses to 'somearg' on the rootDocument export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc, templateArgs?: string) { - const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace("()", "") || StrCast(templateLayoutDoc.PARAMS); - if (!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc) || !targetDoc) return templateLayoutDoc; + const args = templateArgs?.match(/\(([a-zA-Z0-9._\-]*)\)/)?.[1].replace('()', '') || StrCast(templateLayoutDoc.PARAMS); + if ((!args && !WillExpandTemplateLayout(templateLayoutDoc, targetDoc)) || !targetDoc) return templateLayoutDoc; - const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders + const templateField = StrCast(templateLayoutDoc.isTemplateForField); // the field that the template renders // First it checks if an expanded layout already exists -- if so it will be stored on the dataDoc // using the template layout doc's id as the field key. // If it doesn't find the expanded layout, then it makes a delegate of the template layout and // saves it on the data doc indexed by the template layout's id. // - const params = args.split("=").length > 1 ? args.split("=")[0] : "PARAMS"; + const params = args.split('=').length > 1 ? args.split('=')[0] : 'PARAMS'; const layoutFielddKey = Doc.LayoutFieldKey(templateLayoutDoc); - const expandedLayoutFieldKey = (templateField || layoutFielddKey) + "-layout[" + templateLayoutDoc[Id] + (args ? `(${args})` : "") + "]"; + const expandedLayoutFieldKey = (templateField || layoutFielddKey) + '-layout[' + templateLayoutDoc[Id] + (args ? `(${args})` : '') + ']'; let expandedTemplateLayout = targetDoc?.[expandedLayoutFieldKey]; if (templateLayoutDoc.resolvedDataDoc instanceof Promise) { expandedTemplateLayout = undefined; _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey, true); - } - else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { + } else if (expandedTemplateLayout === undefined && !_pendingMap.get(targetDoc[Id] + expandedLayoutFieldKey + args)) { if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument || Doc.GetProto(targetDoc)) && templateLayoutDoc.PARAMS === StrCast(targetDoc.PARAMS)) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = Cast(templateLayoutDoc.proto, Doc, null) || templateLayoutDoc); // if the template has already been applied (ie, a nested template), then use the template's prototype if (!targetDoc[expandedLayoutFieldKey]) { _pendingMap.set(targetDoc[Id] + expandedLayoutFieldKey + args, true); - setTimeout(action(() => { - const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, "[" + templateLayoutDoc.title + "]"); - // the template's arguments are stored in params which is derefenced to find - // the actual field key where the parameterized template data is stored. - newLayoutDoc[params] = args !== "..." ? args : ""; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances - newLayoutDoc.rootDocument = targetDoc; - const dataDoc = Doc.GetProto(targetDoc); - newLayoutDoc.resolvedDataDoc = dataDoc; - if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) { - dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc }); - } - targetDoc[expandedLayoutFieldKey] = newLayoutDoc; - - _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args); - })); + setTimeout( + action(() => { + const newLayoutDoc = Doc.MakeDelegate(templateLayoutDoc, undefined, '[' + templateLayoutDoc.title + ']'); + // the template's arguments are stored in params which is derefenced to find + // the actual field key where the parameterized template data is stored. + newLayoutDoc[params] = args !== '...' ? args : ''; // ... signifies the layout has sub template(s) -- so we have to expand the layout for them so that they can get the correct 'rootDocument' field, but we don't need to reassign their params. it would be better if the 'rootDocument' field could be passed dynamically to avoid have to create instances + newLayoutDoc.rootDocument = targetDoc; + const dataDoc = Doc.GetProto(targetDoc); + newLayoutDoc.resolvedDataDoc = dataDoc; + if (dataDoc[templateField] === undefined && templateLayoutDoc[templateField] instanceof List && (templateLayoutDoc[templateField] as any).length) { + dataDoc[templateField] = ComputedField.MakeFunction(`ObjectField.MakeCopy(templateLayoutDoc["${templateField}"] as List)`, { templateLayoutDoc: Doc.name }, { templateLayoutDoc }); + } + targetDoc[expandedLayoutFieldKey] = newLayoutDoc; + + _pendingMap.delete(targetDoc[Id] + expandedLayoutFieldKey + args); + }) + ); } } } @@ -744,17 +772,17 @@ export namespace Doc { // otherwise, it just returns the childDoc export function GetLayoutDataDocPair(containerDoc: Doc, containerDataDoc: Opt, childDoc: Doc) { if (!childDoc || childDoc instanceof Promise || !Doc.GetProto(childDoc)) { - console.log("No, no, no!"); + console.log('No, no, no!'); return { layout: childDoc, data: childDoc }; } - const resolvedDataDoc = (Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc); - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, "(" + StrCast(containerDoc.PARAMS) + ")"), data: resolvedDataDoc }; + const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField && !childDoc.PARAMS) ? undefined : containerDataDoc; + return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc, '(' + StrCast(containerDoc.PARAMS) + ')'), data: resolvedDataDoc }; } export function Overwrite(doc: Doc, overwrite: Doc, copyProto: boolean = false): Doc { Object.keys(doc).forEach(key => { const field = ProxyField.WithoutProxy(() => doc[key]); - if (key === "proto" && copyProto) { + if (key === 'proto' && copyProto) { if (doc.proto instanceof Doc && overwrite.proto instanceof Doc) { overwrite[key] = Doc.Overwrite(doc[key]!, overwrite.proto); } @@ -776,12 +804,12 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc { const copy = new Doc(copyProtoId, true); - const exclude = Cast(doc.cloneFieldFilter, listSpec("string"), []); + const exclude = Cast(doc.cloneFieldFilter, listSpec('string'), []); Object.keys(doc).forEach(key => { if (exclude.includes(key)) return; const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); - if (key === "proto" && copyProto) { + if (key === 'proto' && copyProto) { if (doc[key] instanceof Doc) { copy[key] = Doc.MakeCopy(doc[key]!, false); } @@ -789,11 +817,14 @@ export namespace Doc { if (field instanceof RefField) { copy[key] = field; } else if (cfield instanceof ComputedField) { - copy[key] = cfield[Copy]();// ComputedField.MakeFunction(cfield.script.originalScript); + copy[key] = cfield[Copy](); // ComputedField.MakeFunction(cfield.script.originalScript); } else if (field instanceof ObjectField) { - copy[key] = doc[key] instanceof Doc ? - key.includes("layout[") ? undefined : doc[key] : // reference documents except remove documents that are expanded teplate fields - ObjectField.MakeCopy(field); + copy[key] = + doc[key] instanceof Doc + ? key.includes('layout[') + ? undefined + : doc[key] // reference documents except remove documents that are expanded teplate fields + : ObjectField.MakeCopy(field); } else if (field instanceof Promise) { debugger; //This shouldn't happend... } else { @@ -806,17 +837,16 @@ export namespace Doc { Doc.GetProto(copy).context = undefined; Doc.GetProto(copy).aliases = new List([copy]); } else { - Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy); + Doc.AddDocToList(Doc.GetProto(copy)[DataSym], 'aliases', copy); } copy.context = undefined; - Doc.defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared'); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } return copy; } - export function MakeDelegate(doc: Doc, id?: string, title?: string): Doc; export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt; export function MakeDelegate(doc: Opt, id?: string, title?: string): Opt { @@ -825,8 +855,10 @@ export namespace Doc { delegate[Initializing] = true; delegate.proto = doc; delegate.author = Doc.CurrentUserEmail; - Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => delegate[key] = doc[key]); - if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate); + Object.keys(doc) + .filter(key => key.startsWith('acl')) + .forEach(key => (delegate[key] = doc[key])); + if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], 'aliases', delegate); title && (delegate.title = title); delegate[Initializing] = false; return delegate; @@ -849,7 +881,7 @@ export namespace Doc { delegate[Initializing] = true; delegate.proto = delegateProto; delegate.author = Doc.CurrentUserEmail; - Doc.AddDocToList(delegateProto[DataSym], "aliases", delegate); + Doc.AddDocToList(delegateProto[DataSym], 'aliases', delegate); delegate[Initializing] = false; delegateProto[Initializing] = false; return delegate; @@ -861,11 +893,11 @@ export namespace Doc { const proto = new Doc(); proto.author = Doc.CurrentUserEmail; const target = Doc.MakeDelegate(proto); - const targetKey = StrCast(templateDoc.layoutKey, "layout"); - const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")"); + const targetKey = StrCast(templateDoc.layoutKey, 'layout'); + const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')'); target.layoutKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.defaultAclPrivate && (applied["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared'); return applied; } return undefined; @@ -888,9 +920,8 @@ export namespace Doc { // metadata field indicated by the title of the template field (not the default field that it was rendering) // export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt): boolean { - // find the metadata field key that this template field doc will display (indicated by its title) - const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, ""); + const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, ''); // update the original template to mark it as a template templateField.isTemplateForField = metadataFieldKey; @@ -904,7 +935,7 @@ export namespace Doc { // note 2: this will not overwrite any field that already exists on the template doc at the field key if (!templateDoc?.[metadataFieldKey] && templateFieldValue instanceof ObjectField) { Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc)); - (Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue)); + Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue); } // get the layout string that the template uses to specify its layout const templateFieldLayoutString = StrCast(Doc.LayoutField(Doc.Layout(templateField))); @@ -913,19 +944,18 @@ export namespace Doc { Doc.Layout(templateField).layout = templateFieldLayoutString.replace(/fieldKey={'[^']*'}/, `fieldKey={'${metadataFieldKey}'}`); // assign the template field doc a delegate of any extension document that was previously used to render the template field (since extension doc's carry rendering informatino) - Doc.Layout(templateField)[metadataFieldKey + "_ext"] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + "_ext"] as Doc); + Doc.Layout(templateField)[metadataFieldKey + '_ext'] = Doc.MakeDelegate(templateField[templateFieldLayoutString?.split("'")[1] + '_ext'] as Doc); return true; } - // converts a document id to a url path on the server - export function globalServerPath(doc: Doc | string = ""): string { - return Utils.prepend("/doc/" + (doc instanceof Doc ? doc[Id] : doc)); + export function globalServerPath(doc: Doc | string = ''): string { + return Utils.prepend('/doc/' + (doc instanceof Doc ? doc[Id] : doc)); } // converts a document id to a url path on the server export function localServerPath(doc?: Doc): string { - return "/doc/" + (doc ? doc[Id] : ""); + return '/doc/' + (doc ? doc[Id] : ''); } export function overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { @@ -958,47 +988,70 @@ export namespace Doc { export class DocData { @observable _user_doc: Doc = undefined!; @observable _sharing_doc: Doc = undefined!; - @observable _searchQuery: string = ""; + @observable _searchQuery: string = ''; } // the document containing the view layout information - will be the Document itself unless the Document has // a layout field or 'layout' is given. export function Layout(doc: Doc, layout?: Doc): Doc { - const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, "data")}-layout[` + layout[Id] + "]"], Doc, null); + const overrideLayout = layout && Cast(doc[`${StrCast(layout.isTemplateForField, 'data')}-layout[` + layout[Id] + ']'], Doc, null); return overrideLayout || doc[LayoutSym] || doc; } - export function SetLayout(doc: Doc, layout: Doc | string) { doc[StrCast(doc.layoutKey, "layout")] = layout; } - export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } - export function LayoutFieldKey(doc: Doc): string { return StrCast(Doc.Layout(doc).layout).split("'")[1]; } + export function SetLayout(doc: Doc, layout: Doc | string) { + doc[StrCast(doc.layoutKey, 'layout')] = layout; + } + export function LayoutField(doc: Doc) { + return doc[StrCast(doc.layoutKey, 'layout')]; + } + export function LayoutFieldKey(doc: Doc): string { + return StrCast(Doc.Layout(doc).layout).split("'")[1]; + } export function NativeAspect(doc: Doc, dataDoc?: Doc, useDim?: boolean) { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); } - export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeWidth"], useWidth ? doc[WidthSym]() : 0)); } + export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { + return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeWidth'], useWidth ? doc[WidthSym]() : 0)); + } export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { - const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + "-nativeHeight"], useHeight ? doc[HeightSym]() : 0) : 0; - const nheight = doc ? Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]() / doc[WidthSym]() : 0; + const dheight = doc ? NumCast((dataDoc || doc)[Doc.LayoutFieldKey(doc) + '-nativeHeight'], useHeight ? doc[HeightSym]() : 0) : 0; + const nheight = doc ? (Doc.NativeWidth(doc, dataDoc, useHeight) * doc[HeightSym]()) / doc[WidthSym]() : 0; return !doc ? 0 : NumCast(doc._nativeHeight, nheight || dheight); } - export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeWidth"] = width; } - export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + "-nativeHeight"] = height; } - + export function SetNativeWidth(doc: Doc, width: number | undefined, fieldKey?: string) { + doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeWidth'] = width; + } + export function SetNativeHeight(doc: Doc, height: number | undefined, fieldKey?: string) { + doc[(fieldKey ?? Doc.LayoutFieldKey(doc)) + '-nativeHeight'] = height; + } const manager = new DocData(); - export function SearchQuery(): string { return manager._searchQuery; } - export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } - export function UserDoc(): Doc { return manager._user_doc; } - export function SharingDoc(): Doc { return CurrentUserUtils.MySharedDocs; } - export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); } - export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); } + export function SearchQuery(): string { + return manager._searchQuery; + } + export function SetSearchQuery(query: string) { + runInAction(() => (manager._searchQuery = query)); + } + export function UserDoc(): Doc { + return manager._user_doc; + } + export function SharingDoc(): Doc { + return CurrentUserUtils.MySharedDocs; + } + export function LinkDBDoc(): Doc { + return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); + } + export function SetUserDoc(doc: Doc) { + return (manager._user_doc = doc); + } const isSearchMatchCache = computedFn(function IsSearchMatch(doc: Doc) { - return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : - brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; + return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; }); - export function IsSearchMatch(doc: Doc) { return isSearchMatchCache(doc); } + export function IsSearchMatch(doc: Doc) { + return isSearchMatchCache(doc); + } export function IsSearchMatchUnmemoized(doc: Doc) { - return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : - brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; + return brushManager.SearchMatchDoc.has(doc) ? brushManager.SearchMatchDoc.get(doc) : brushManager.SearchMatchDoc.has(Doc.GetProto(doc)) ? brushManager.SearchMatchDoc.get(Doc.GetProto(doc)) : undefined; } export function SetSearchMatch(doc: Doc, results: { searchMatch: number }) { if (doc && GetEffectiveAcl(doc) !== AclPrivate && GetEffectiveAcl(Doc.GetProto(doc)) !== AclPrivate) { @@ -1017,8 +1070,12 @@ export namespace Doc { brushManager.SearchMatchDoc.clear(); } - const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); }); - export function IsBrushed(doc: Doc) { return isBrushedCache(doc); } + const isBrushedCache = computedFn(function IsBrushed(doc: Doc) { + return brushManager.BrushedDoc.has(doc) || brushManager.BrushedDoc.has(Doc.GetProto(doc)); + }); + export function IsBrushed(doc: Doc) { + return isBrushedCache(doc); + } export enum DocBrushStatus { unbrushed = 0, @@ -1037,9 +1094,7 @@ export namespace Doc { for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) { const a1 = Cast(link.anchor1, Doc, null); const a2 = Cast(link.anchor2, Doc, null); - if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || - (Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc)) || - (Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc))) { + if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || Doc.AreProtosEqual(Cast(a1.annotationOn, Doc, null), doc) || Doc.AreProtosEqual(Cast(a2.annotationOn, Doc, null), doc)) { return DocBrushStatus.linkHighlighted; } } @@ -1070,22 +1125,21 @@ export namespace Doc { } export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { - return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || - Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? "1" : "2"; + return Doc.AreProtosEqual(anchorDoc, (linkDoc.anchor1 as Doc).annotationOn as Doc) || Doc.AreProtosEqual(anchorDoc, linkDoc.anchor1 as Doc) ? '1' : '2'; } export function linkFollowUnhighlight() { Doc.UnhighlightAll(); - document.removeEventListener("pointerdown", linkFollowUnhighlight); + document.removeEventListener('pointerdown', linkFollowUnhighlight); } let _lastDate = 0; export function linkFollowHighlight(destDoc: Doc | Doc[], dataAndDisplayDocs = true) { linkFollowUnhighlight(); (destDoc instanceof Doc ? [destDoc] : destDoc).forEach(doc => Doc.HighlightDoc(doc, dataAndDisplayDocs)); - document.removeEventListener("pointerdown", linkFollowUnhighlight); - document.addEventListener("pointerdown", linkFollowUnhighlight); - const lastDate = _lastDate = Date.now(); + document.removeEventListener('pointerdown', linkFollowUnhighlight); + document.addEventListener('pointerdown', linkFollowUnhighlight); + const lastDate = (_lastDate = Date.now()); window.setTimeout(() => _lastDate === lastDate && linkFollowUnhighlight(), 5000); } @@ -1116,53 +1170,57 @@ export namespace Doc { const targetDoc = docEntry.value; targetDoc && Doc.UnHighlightDoc(targetDoc); } - } export function UnBrushAllDocs() { brushManager.BrushedDoc.clear(); } export function getDocTemplate(doc?: Doc) { - return !doc ? undefined : - doc.isTemplateDoc ? doc : - Cast(doc.dragFactory, Doc, null)?.isTemplateDoc ? doc.dragFactory : - Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc ? - (Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc ? Doc.Layout(doc).proto : Doc.Layout(doc)) : - undefined; + return !doc + ? undefined + : doc.isTemplateDoc + ? doc + : Cast(doc.dragFactory, Doc, null)?.isTemplateDoc + ? doc.dragFactory + : Cast(Doc.Layout(doc), Doc, null)?.isTemplateDoc + ? Cast(Doc.Layout(doc), Doc, null).resolvedDataDoc + ? Doc.Layout(doc).proto + : Doc.Layout(doc) + : undefined; } export function matchFieldValue(doc: Doc, key: string, value: any): boolean { if (Utils.HasTransparencyFilter(value)) { - const isTransparent = (color: string) => color !== "" && (DashColor(color).alpha() !== 1); + const isTransparent = (color: string) => color !== '' && DashColor(color).alpha() !== 1; return isTransparent(StrCast(doc[key])); } - if (typeof value === "string") { - value = value.replace(`,${Utils.noRecursionHack}`, ""); + if (typeof value === 'string') { + value = value.replace(`,${Utils.noRecursionHack}`, ''); } - const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key]; - if (Cast(fieldVal, listSpec("string"), []).length) { - const vals = Cast(fieldVal, listSpec("string"), []); + const fieldVal = key === '#' ? (StrCast(doc.tags).includes(':#' + value + ':') ? StrCast(doc.tags) : undefined) : doc[key]; + if (Cast(fieldVal, listSpec('string'), []).length) { + const vals = Cast(fieldVal, listSpec('string'), []); const docs = vals.some(v => (v as any) instanceof Doc); if (docs) return value === Field.toString(fieldVal as Field); - return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } const fieldStr = Field.toString(fieldVal as Field); return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { - StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); + StrCast(doc.layoutKey).split('_')[1] === 'icon' && setNativeView(doc); } export function setNativeView(doc: any) { - const prevLayout = StrCast(doc.layoutKey).split("_")[1]; - const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : ""; - prevLayout === "icon" && (doc.deiconifyLayout = undefined); - doc.layoutKey = deiconify || "layout"; + const prevLayout = StrCast(doc.layoutKey).split('_')[1]; + const deiconify = prevLayout === 'icon' && StrCast(doc.deiconifyLayout) ? 'layout_' + StrCast(doc.deiconifyLayout) : ''; + prevLayout === 'icon' && (doc.deiconifyLayout = undefined); + doc.layoutKey = deiconify || 'layout'; } export function setDocRangeFilter(container: Opt, key: string, range?: number[]) { if (!container) return; - const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []); + const docRangeFilters = Cast(container._docRangeFilters, listSpec('string'), []); for (let i = 0; i < docRangeFilters.length; i += 3) { if (docRangeFilters[i] === key) { docRangeFilters.splice(i, 3); @@ -1180,16 +1238,16 @@ export namespace Doc { // filters document in a container collection: // all documents with the specified value for the specified key are included/excluded // based on the modifiers :"check", "x", undefined - export function setDocFilter(container: Opt, key: string, value: any, modifiers: "remove" | "match" | "check" | "x" | "exists" | "unset", toggle?: boolean, fieldSuffix?: string, append: boolean = true) { + export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldSuffix?: string, append: boolean = true) { if (!container) return; - const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters"; - const docFilters = Cast(container[filterField], listSpec("string"), []); + const filterField = '_' + (fieldSuffix ? fieldSuffix + '-' : '') + 'docFilters'; + const docFilters = Cast(container[filterField], listSpec('string'), []); runInAction(() => { for (let i = 0; i < docFilters.length; i++) { - const fields = docFilters[i].split(":"); // split key:value:modifier - if (fields[0] === key && (fields[1] === value || modifiers === "match")) { + const fields = docFilters[i].split(':'); // split key:value:modifier + if (fields[0] === key && (fields[1] === value || modifiers === 'match')) { if (fields[2] === modifiers && modifiers && fields[1] === value) { - if (toggle) modifiers = "remove"; + if (toggle) modifiers = 'remove'; else return; } docFilters.splice(i, 1); @@ -1197,17 +1255,17 @@ export namespace Doc { break; } } - if (!docFilters.length && modifiers === "match" && value === undefined) { + if (!docFilters.length && modifiers === 'match' && value === undefined) { container[filterField] = undefined; - } else if (modifiers !== "remove") { + } else if (modifiers !== 'remove') { !append && (docFilters.length = 0); - docFilters.push(key + ":" + value + ":" + modifiers); + docFilters.push(key + ':' + value + ':' + modifiers); container[filterField] = new List(docFilters); } }); } export function readDocRangeFilter(doc: Doc, key: string) { - const docRangeFilters = Cast(doc._docRangeFilters, listSpec("string"), []); + const docRangeFilters = Cast(doc._docRangeFilters, listSpec('string'), []); for (let i = 0; i < docRangeFilters.length; i += 3) { if (docRangeFilters[i] === key) { return [Number(docRangeFilters[i + 1]), Number(docRangeFilters[i + 2])]; @@ -1225,8 +1283,7 @@ export namespace Doc { layoutDoc._viewScale = NumCast(layoutDoc._viewScale, 1) * contentScale; layoutDoc._nativeWidth = undefined; layoutDoc._nativeHeight = undefined; - } - else { + } else { layoutDoc._autoHeight = false; if (!Doc.NativeWidth(layoutDoc)) { layoutDoc._nativeWidth = NumCast(layoutDoc._width, panelWidth); @@ -1239,44 +1296,61 @@ export namespace Doc { export function isDocPinned(doc: Doc) { //add this new doc to props.Document const curPres = CurrentUserUtils.ActivePresentation; - return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; + return !curPres ? false : DocListCast(curPres.data).findIndex(val => Doc.AreProtosEqual(val, doc)) !== -1; } export function toIcon(doc?: Doc, isOpen?: boolean) { switch (StrCast(doc?.type)) { - case DocumentType.IMG: return "image"; - case DocumentType.COMPARISON: return "columns"; - case DocumentType.RTF: return "sticky-note"; + case DocumentType.IMG: + return 'image'; + case DocumentType.COMPARISON: + return 'columns'; + case DocumentType.RTF: + return 'sticky-note'; case DocumentType.COL: - const folder: IconProp = isOpen ? "folder-open" : "folder"; - const chevron: IconProp = isOpen ? "chevron-down" : "chevron-right"; + const folder: IconProp = isOpen ? 'folder-open' : 'folder'; + const chevron: IconProp = isOpen ? 'chevron-down' : 'chevron-right'; return !doc?.isFolder ? folder : chevron; - case DocumentType.WEB: return "globe-asia"; - case DocumentType.SCREENSHOT: return "photo-video"; - case DocumentType.WEBCAM: return "video"; - case DocumentType.AUDIO: return "microphone"; - case DocumentType.BUTTON: return "bolt"; - case DocumentType.PRES: return "tv"; - case DocumentType.SCRIPTING: return "terminal"; - case DocumentType.IMPORT: return "cloud-upload-alt"; - case DocumentType.VID: return "video"; - case DocumentType.INK: return "pen-nib"; - case DocumentType.PDF: return "file-pdf"; - case DocumentType.LINK: return "link"; - case DocumentType.MAP: return "map-marker-alt"; - default: return "question"; + case DocumentType.WEB: + return 'globe-asia'; + case DocumentType.SCREENSHOT: + return 'photo-video'; + case DocumentType.WEBCAM: + return 'video'; + case DocumentType.AUDIO: + return 'microphone'; + case DocumentType.BUTTON: + return 'bolt'; + case DocumentType.PRES: + return 'tv'; + case DocumentType.SCRIPTING: + return 'terminal'; + case DocumentType.IMPORT: + return 'cloud-upload-alt'; + case DocumentType.VID: + return 'video'; + case DocumentType.INK: + return 'pen-nib'; + case DocumentType.PDF: + return 'file-pdf'; + case DocumentType.LINK: + return 'link'; + case DocumentType.MAP: + return 'map-marker-alt'; + default: + return 'question'; } } - export async function importDocument(file:File) { - const upload = Utils.prepend("/uploadDoc"); + export async function importDocument(file: File) { + const upload = Utils.prepend('/uploadDoc'); const formData = new FormData(); if (file) { formData.append('file', file); - formData.append('remap', "true"); - const response = await fetch(upload, { method: "POST", body: formData }); + formData.append('remap', 'true'); + const response = await fetch(upload, { method: 'POST', body: formData }); const json = await response.json(); - if (json !== "error") { + if (json !== 'error') { const doc = await DocServer.GetRefField(json); return doc; } @@ -1285,17 +1359,16 @@ export namespace Doc { } export namespace Get { - - const primitives = ["string", "number", "boolean"]; + const primitives = ['string', 'number', 'boolean']; export interface JsonConversionOpts { data: any; title?: string; - appendToExisting?: { targetDoc: Doc, fieldKey?: string }; + appendToExisting?: { targetDoc: Doc; fieldKey?: string }; excludeEmptyObjects?: boolean; } - const defaultKey = "json"; + const defaultKey = 'json'; /** * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily @@ -1340,17 +1413,17 @@ export namespace Doc { if (excludeEmptyObjects === undefined) { excludeEmptyObjects = true; } - if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) { + if (data === undefined || data === null || ![...primitives, 'object'].includes(typeof data)) { return undefined; } let resolved: any; try { - resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data)); + resolved = JSON.parse(typeof data === 'string' ? data : JSON.stringify(data)); } catch (e) { return undefined; } let output: Opt; - if (typeof resolved === "object" && !(resolved instanceof Array)) { + if (typeof resolved === 'object' && !(resolved instanceof Array)) { output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc); } else { // give the proper types to the data extracted from the JSON @@ -1358,7 +1431,7 @@ export namespace Doc { if (appendToExisting) { (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result; } else { - (output = new Doc).json = result; + (output = new Doc()).json = result; } } title && output && (output.title = title); @@ -1375,14 +1448,14 @@ export namespace Doc { const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt => { const hasEntries = Object.keys(object).length; if (hasEntries || !excludeEmptyObjects) { - const resolved = target ?? new Doc; + const resolved = target ?? new Doc(); if (hasEntries) { let result: Opt; Object.keys(object).map(key => { // if excludeEmptyObjects is true, any qualifying conversions from toField will // be undefined, and thus the results that would have // otherwise been empty (List or Doc)s will just not be written - if (result = toField(object[key], excludeEmptyObjects, key)) { + if ((result = toField(object[key], excludeEmptyObjects, key))) { resolved[key] = result; } }); @@ -1418,40 +1491,78 @@ export namespace Doc { if (primitives.includes(typeof data)) { return data; } - if (typeof data === "object") { + if (typeof data === 'object') { return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined); } throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`); }; } - } -ScriptingGlobals.add(function idToDoc(id: string): any { return DocServer.GetCachedRefField(id); }); -ScriptingGlobals.add(function renameAlias(doc: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${doc.aliasNumber})`; }); -ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); }); -ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); -ScriptingGlobals.add(function getAlias(doc: any) { return Doc.MakeAlias(doc); }); -ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); -ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); -ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); -ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); -ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); -ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); -ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); }); -ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; }); -ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); }); -ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); }); +ScriptingGlobals.add(function idToDoc(id: string): any { + return DocServer.GetCachedRefField(id); +}); +ScriptingGlobals.add(function renameAlias(doc: any) { + return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, '') + `(${doc.aliasNumber})`; +}); +ScriptingGlobals.add(function getProto(doc: any) { + return Doc.GetProto(doc); +}); +ScriptingGlobals.add(function getDocTemplate(doc?: any) { + return Doc.getDocTemplate(doc); +}); +ScriptingGlobals.add(function getAlias(doc: any) { + return Doc.MakeAlias(doc); +}); +ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { + return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); +}); +ScriptingGlobals.add(function copyField(field: any) { + return Field.Copy(field); +}); +ScriptingGlobals.add(function docList(field: any) { + return DocListCast(field); +}); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added: Doc) { + return Doc.AddDocToList(doc, field, added); +}); +ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { + return Doc.SetInPlace(doc, field, value, false); +}); +ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { + return Doc.AreProtosEqual(doc1, doc2); +}); +ScriptingGlobals.add(function undo() { + SelectionManager.DeselectAll(); + return UndoManager.Undo(); +}); +ScriptingGlobals.add(function redo() { + SelectionManager.DeselectAll(); + return UndoManager.Redo(); +}); +ScriptingGlobals.add(function DOC(id: string) { + console.log("Can't parse a document id in a script"); + return 'invalid'; +}); +ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { + return Doc.assignDocToField(doc, field, id); +}); +ScriptingGlobals.add(function docCast(doc: FieldResult): any { + return DocCastAsync(doc); +}); ScriptingGlobals.add(function activePresentationItem() { const curPres = CurrentUserUtils.ActivePresentation; return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; }); ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { - const docs = SelectionManager.Views().map(dv => dv.props.Document). - filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && - (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); + const docs = SelectionManager.Views() + .map(dv => dv.props.Document) + .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); return docs.length ? new List(docs) : prevValue; }); -ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: "match" | "check" | "x" | "remove") { Doc.setDocFilter(container, key, value, modifiers); }); -ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { Doc.setDocRangeFilter(container, key, range); }); +ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') { + Doc.setDocFilter(container, key, value, modifiers); +}); +ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { + Doc.setDocRangeFilter(container, key, range); +}); diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index a19be5df9..bf055cd8b 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -1,48 +1,46 @@ -import { AssertionError } from "assert"; -import { docs_v1 } from "googleapis"; -import { Fragment, Mark, Node } from "prosemirror-model"; -import { sinkListItem } from "prosemirror-schema-list"; -import { Utils, DashColor } from "../Utils"; -import { Docs, DocUtils } from "../client/documents/Documents"; -import { schema } from "../client/views/nodes/formattedText/schema_rts"; -import { GooglePhotos } from "../client/apis/google_docs/GooglePhotosClientUtils"; -import { DocServer } from "../client/DocServer"; -import { Networking } from "../client/Network"; -import { FormattedTextBox } from "../client/views/nodes/formattedText/FormattedTextBox"; -import { Doc, Opt } from "./Doc"; -import { Id } from "./FieldSymbols"; -import { RichTextField } from "./RichTextField"; -import { Cast, StrCast } from "./Types"; +import { AssertionError } from 'assert'; +import { docs_v1 } from 'googleapis'; +import { Fragment, Mark, Node } from 'prosemirror-model'; +import { sinkListItem } from 'prosemirror-schema-list'; +import { Utils, DashColor } from '../Utils'; +import { Docs, DocUtils } from '../client/documents/Documents'; +import { schema } from '../client/views/nodes/formattedText/schema_rts'; +import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils'; +import { DocServer } from '../client/DocServer'; +import { Networking } from '../client/Network'; +import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox'; +import { Doc, Opt } from './Doc'; +import { Id } from './FieldSymbols'; +import { RichTextField } from './RichTextField'; +import { Cast, StrCast } from './Types'; import Color = require('color'); -import { EditorState, TextSelection, Transaction } from "prosemirror-state"; -import { GoogleApiClientUtils } from "../client/apis/google_docs/GoogleApiClientUtils"; +import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; +import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils'; export namespace RichTextUtils { - - const delimiter = "\n"; - const joiner = ""; - + const delimiter = '\n'; + const joiner = ''; export const Initialize = (initial?: string) => { const content: any[] = []; const state = { doc: { - type: "doc", + type: 'doc', content, }, selection: { - type: "text", + type: 'text', anchor: 0, - head: 0 - } + head: 0, + }, }; if (initial && initial.length) { content.push({ - type: "paragraph", + type: 'paragraph', content: { - type: "text", - text: initial - } + type: 'text', + text: initial, + }, }); state.selection.anchor = state.selection.head = initial.length + 1; } @@ -56,8 +54,8 @@ export namespace RichTextUtils { export const ToPlainText = (state: EditorState) => { // Because we're working with plain text, just concatenate all paragraphs const content = state.doc.content; - const paragraphs: Node[] = []; - content.forEach(node => node.type.name === "paragraph" && paragraphs.push(node)); + const paragraphs: Node[] = []; + content.forEach(node => node.type.name === 'paragraph' && paragraphs.push(node)); // Functions to flatten ProseMirror paragraph objects (and their components) to plain text // Concatentate paragraphs and string the result together @@ -80,22 +78,21 @@ export namespace RichTextUtils { // Preserve the current state, but re-write the content to be the blocks const parsed = JSON.parse(oldState ? oldState.Data : Initialize()); parsed.doc.content = elements.map(text => { - const paragraph: any = { type: "paragraph" }; - text.length && (paragraph.content = [{ type: "text", marks: [], text }]); // An empty paragraph gets treated as a line break + const paragraph: any = { type: 'paragraph' }; + text.length && (paragraph.content = [{ type: 'text', marks: [], text }]); // An empty paragraph gets treated as a line break return paragraph; }); // If the new content is shorter than the previous content and selection is unchanged, may throw an out of bounds exception, so we reset it - parsed.selection = { type: "text", anchor: 1, head: 1 }; + parsed.selection = { type: 'text', anchor: 1, head: 1 }; // Export the ProseMirror-compatible state object we've just built return JSON.stringify(parsed); }; export namespace GoogleDocs { - export const Export = async (state: EditorState): Promise => { - const nodes: (Node | null)[] = []; + const nodes: (Node | null)[] = []; const text = ToPlainText(state); state.doc.content.forEach(node => { if (!node.childCount) { @@ -126,10 +123,10 @@ export namespace RichTextUtils { return { baseUrl: embeddedObject.imageProperties!.contentUri! }; }); - const uploads = await Networking.PostToServer("/googlePhotosMediaGet", { mediaItems }); + const uploads = await Networking.PostToServer('/googlePhotosMediaGet', { mediaItems }); if (uploads.length !== mediaItems.length) { - throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: "Error with internally uploading inlineObjects!" }); + throw new AssertionError({ expected: mediaItems.length, actual: uploads.length, message: 'Error with internally uploading inlineObjects!' }); } for (let i = 0; i < objects.length; i++) { @@ -144,14 +141,14 @@ export namespace RichTextUtils { title: embeddedObject.title || `Imported Image from ${document.title}`, width, url: Utils.prepend(_m.client), - agnostic: Utils.prepend(agnostic.client) + agnostic: Utils.prepend(agnostic.client), }); } } return inlineObjectMap; }; - type BulletPosition = { value: number, sinks: number }; + type BulletPosition = { value: number; sinks: number }; interface MediaItem { baseUrl: string; @@ -172,7 +169,7 @@ export namespace RichTextUtils { const lists: ListGroup[] = []; const indentMap = new Map(); let globalOffset = 0; - const nodes: Node[] = []; + const nodes: Node[] = []; for (const element of structured) { if (Array.isArray(element)) { lists.push(element); @@ -182,7 +179,7 @@ export namespace RichTextUtils { const sinks = paragraph.bullet!; positions.push({ value: position + globalOffset, - sinks + sinks, }); position += item.nodeSize; globalOffset += 2 * sinks; @@ -191,13 +188,13 @@ export namespace RichTextUtils { indentMap.set(element, positions); nodes.push(list(state.schema, items)); } else { - if (element.contents.some(child => "inlineObjectId" in child)) { + if (element.contents.some(child => 'inlineObjectId' in child)) { const group = element.contents; group.forEach((child, i) => { - let node: Opt>; - if ("inlineObjectId" in child) { + let node: Opt; + if ('inlineObjectId' in child) { node = imageNode(state.schema, inlineObjectMap.get(child.inlineObjectId!)!, textNote); - } else if ("content" in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { + } else if ('content' in child && (i !== group.length - 1 || child.content!.removeTrailingNewlines().length)) { node = paragraphNode(state.schema, [child]); } if (node) { @@ -215,7 +212,7 @@ export namespace RichTextUtils { state = state.apply(state.tr.replaceWith(0, 2, nodes)); const sink = sinkListItem(state.schema.nodes.list_item); - const dispatcher = (tr: Transaction) => state = state.apply(tr); + const dispatcher = (tr: Transaction) => (state = state.apply(tr)); for (const list of lists) { for (const pos of indentMap.get(list)!) { const resolved = state.doc.resolve(pos.value); @@ -252,17 +249,17 @@ export namespace RichTextUtils { }; const listItem = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { - return schema.node("list_item", null, paragraphNode(schema, runs)); + return schema.node('list_item', null, paragraphNode(schema, runs)); }; const list = (schema: any, items: Node[]): Node => { - return schema.node("ordered_list", { mapStyle: "bullet" }, items); + return schema.node('ordered_list', { mapStyle: 'bullet' }, items); }; const paragraphNode = (schema: any, runs: docs_v1.Schema$TextRun[]): Node => { const children = runs.map(run => textNode(schema, run)).filter(child => child !== undefined); const fragment = children.length ? Fragment.from(children) : undefined; - return schema.node("paragraph", null, fragment); + return schema.node('paragraph', null, fragment); }; const imageNode = (schema: any, image: ImageTemplate, textNote: Doc) => { @@ -278,7 +275,7 @@ export namespace RichTextUtils { } else { docid = backingDocId; } - return schema.node("image", { src, agnostic, width, docid, float: null, location: "add:right" }); + return schema.node('image', { src, agnostic, width, docid, float: null, location: 'add:right' }); }; const textNode = (schema: any, run: docs_v1.Schema$TextRun) => { @@ -287,10 +284,10 @@ export namespace RichTextUtils { }; const StyleToMark = new Map([ - ["bold", "strong"], - ["italic", "em"], - ["foregroundColor", "pFontColor"], - ["fontSize", "pFontSize"] + ['bold', 'strong'], + ['italic', 'em'], + ['foregroundColor', 'pFontColor'], + ['fontSize', 'pFontSize'], ]); const styleToMarks = (schema: any, textStyle?: docs_v1.Schema$TextStyle) => { @@ -301,21 +298,21 @@ export namespace RichTextUtils { Object.keys(textStyle).forEach(key => { let value: any; const targeted = key as keyof docs_v1.Schema$TextStyle; - if (value = textStyle[targeted]) { + if ((value = textStyle[targeted])) { const attributes: any = {}; let converted = StyleToMark.get(targeted) || targeted; value.url && (attributes.href = value.url); if (value.color) { const object = value.color.rgbColor; - attributes.color = Color.rgb(["red", "green", "blue"].map(color => object[color] * 255 || 0)).hex(); + attributes.color = Color.rgb(['red', 'green', 'blue'].map(color => object[color] * 255 || 0)).hex(); } if (value.magnitude) { attributes.fontSize = value.magnitude; } - if (converted === "weightedFontFamily") { - converted = ImportFontFamilyMapping.get(value.fontFamily) || "timesNewRoman"; + if (converted === 'weightedFontFamily') { + converted = ImportFontFamilyMapping.get(value.fontFamily) || 'timesNewRoman'; } const mapped = schema.marks[converted]; @@ -332,38 +329,38 @@ export namespace RichTextUtils { }; const MarkToStyle = new Map([ - ["strong", "bold"], - ["em", "italic"], - ["pFontColor", "foregroundColor"], - ["pFontSize", "fontSize"], - ["timesNewRoman", "weightedFontFamily"], - ["georgia", "weightedFontFamily"], - ["comicSans", "weightedFontFamily"], - ["tahoma", "weightedFontFamily"], - ["impact", "weightedFontFamily"] + ['strong', 'bold'], + ['em', 'italic'], + ['pFontColor', 'foregroundColor'], + ['pFontSize', 'fontSize'], + ['timesNewRoman', 'weightedFontFamily'], + ['georgia', 'weightedFontFamily'], + ['comicSans', 'weightedFontFamily'], + ['tahoma', 'weightedFontFamily'], + ['impact', 'weightedFontFamily'], ]); const ExportFontFamilyMapping = new Map([ - ["timesNewRoman", "Times New Roman"], - ["arial", "Arial"], - ["georgia", "Georgia"], - ["comicSans", "Comic Sans MS"], - ["tahoma", "Tahoma"], - ["impact", "Impact"] + ['timesNewRoman', 'Times New Roman'], + ['arial', 'Arial'], + ['georgia', 'Georgia'], + ['comicSans', 'Comic Sans MS'], + ['tahoma', 'Tahoma'], + ['impact', 'Impact'], ]); const ImportFontFamilyMapping = new Map([ - ["Times New Roman", "timesNewRoman"], - ["Arial", "arial"], - ["Georgia", "georgia"], - ["Comic Sans MS", "comicSans"], - ["Tahoma", "tahoma"], - ["Impact", "impact"] + ['Times New Roman', 'timesNewRoman'], + ['Arial', 'arial'], + ['Georgia', 'georgia'], + ['Comic Sans MS', 'comicSans'], + ['Tahoma', 'tahoma'], + ['Impact', 'impact'], ]); - const ignored = ["user_mark"]; + const ignored = ['user_mark']; - const marksToStyle = async (nodes: (Node | null)[]): Promise => { + const marksToStyle = async (nodes: (Node | null)[]): Promise => { const requests: docs_v1.Schema$Request[] = []; let position = 1; for (const node of nodes) { @@ -376,25 +373,25 @@ export namespace RichTextUtils { const information: LinkInformation = { startIndex: position, endIndex: position + nodeSize, - textStyle + textStyle, }; - let mark: Mark; + let mark: Mark; const markMap = BuildMarkMap(marks); for (const markName of Object.keys(schema.marks)) { if (ignored.includes(markName) || !(mark = markMap[markName])) { continue; } - let converted = MarkToStyle.get(markName) || markName as keyof docs_v1.Schema$TextStyle; + let converted = MarkToStyle.get(markName) || (markName as keyof docs_v1.Schema$TextStyle); let value: any = true; if (!converted) { continue; } const { attrs } = mark; switch (converted) { - case "link": - let url = attrs.allLinks.length ? attrs.allLinks[0].href : ""; - const delimiter = "/doc/"; - const alreadyShared = "?sharing=true"; + case 'link': + let url = attrs.allLinks.length ? attrs.allLinks[0].href : ''; + const delimiter = '/doc/'; + const alreadyShared = '?sharing=true'; if (new RegExp(window.location.origin + delimiter).test(url) && !url.endsWith(alreadyShared)) { const linkDoc = await DocServer.GetRefField(url.split(delimiter)[1]); if (linkDoc instanceof Doc) { @@ -411,41 +408,43 @@ export namespace RichTextUtils { textStyle.foregroundColor = fromRgb.blue; textStyle.bold = true; break; - case "fontSize": - value = { magnitude: attrs.fontSize, unit: "PT" }; + case 'fontSize': + value = { magnitude: attrs.fontSize, unit: 'PT' }; break; - case "foregroundColor": + case 'foregroundColor': value = fromHex(attrs.color); break; - case "weightedFontFamily": + case 'weightedFontFamily': value = { fontFamily: ExportFontFamilyMapping.get(markName) }; } let matches: RegExpExecArray | null; if ((matches = /p(\d+)/g.exec(markName)) !== null) { - converted = "fontSize"; - value = { magnitude: parseInt(matches[1].replace("px", "")), unit: "PT" }; + converted = 'fontSize'; + value = { magnitude: parseInt(matches[1].replace('px', '')), unit: 'PT' }; } textStyle[converted] = value; } if (Object.keys(textStyle).length) { requests.push(EncodeStyleUpdate(information)); } - if (node.type.name === "image") { + if (node.type.name === 'image') { const width = attrs.width; - requests.push(await EncodeImage({ - startIndex: position + nodeSize - 1, - uri: attrs.agnostic, - width: Number(typeof width === "string" ? width.replace("px", "") : width) - })); + requests.push( + await EncodeImage({ + startIndex: position + nodeSize - 1, + uri: attrs.agnostic, + width: Number(typeof width === 'string' ? width.replace('px', '') : width), + }) + ); } position += nodeSize; } return requests; }; - const BuildMarkMap = (marks: Mark[]) => { - const markMap: { [type: string]: Mark } = {}; - marks.forEach(mark => markMap[mark.type.name] = mark); + const BuildMarkMap = (marks: readonly Mark[]) => { + const markMap: { [type: string]: Mark } = {}; + marks.forEach(mark => (markMap[mark.type.name] = mark)); return markMap; }; @@ -462,23 +461,21 @@ export namespace RichTextUtils { } namespace fromRgb { - export const convert = (red: number, green: number, blue: number): docs_v1.Schema$OptionalColor => { return { color: { rgbColor: { red: red / 255, green: green / 255, - blue: blue / 255 - } - } + blue: blue / 255, + }, + }, }; }; export const red = convert(255, 0, 0); export const green = convert(0, 255, 0); export const blue = convert(0, 0, 255); - } const fromHex = (color: string): docs_v1.Schema$OptionalColor => { @@ -490,10 +487,10 @@ export namespace RichTextUtils { const { startIndex, endIndex, textStyle } = information; return { updateTextStyle: { - fields: "*", + fields: '*', range: { startIndex, endIndex }, - textStyle - } as docs_v1.Schema$UpdateTextStyleRequest + textStyle, + } as docs_v1.Schema$UpdateTextStyleRequest, }; }; @@ -507,13 +504,12 @@ export namespace RichTextUtils { return { insertInlineImage: { uri: baseUrls[0], - objectSize: { width: { magnitude: width, unit: "PT" } }, - location: { index: startIndex } - } + objectSize: { width: { magnitude: width, unit: 'PT' } }, + location: { index: startIndex }, + }, }; } return {}; }; } - -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From 85e290ee7a666412570f2bae43a9b62d35b425f2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 6 Jul 2022 17:26:26 -0400 Subject: fixed pushpin behavior to work with notes in annotation sidebar. fixed right-clicking on text anchors to bring up anchor menu --- src/client/util/DocumentManager.ts | 2 +- src/client/views/LightboxView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/PDFBox.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 90 +++++++++++++--------- 6 files changed, 66 insertions(+), 42 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0435cd535..8473ce703 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -255,7 +255,7 @@ export class DocumentManager { retryDocView.focus(targetDoc, { willZoom, afterFocus: (didFocus: boolean) => new Promise(res => { - !noSelect && focusAndFinish(didFocus); + !noSelect && focusAndFinish(true); res(ViewAdjustment.doNothing); }) }); // focus on the target in the context diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index fcc4aea13..a1e71b5f4 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -233,7 +233,7 @@ export class LightboxView extends React.Component { const doc = LightboxView._doc; const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); - //else target && targetView?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button + //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button })); })} Document={LightboxView.LightboxDoc} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 44abbdb1c..37589974b 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -191,7 +191,7 @@ export class CollectionStackingView extends CollectionSubView options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing; + const endFocus = async (moved: boolean) => options?.afterFocus?.(moved) ?? ViewAdjustment.doNothing; this.props.focus(this.rootDoc, { willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 360a9b242..7569b209d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -470,10 +470,13 @@ export class DocumentViewInternal extends DocComponent this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here - const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : ViewAdjustment.doNothing; + const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => (options?.afterFocus?.(true) ?? ViewAdjustment.doNothing); this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, afterFocus: (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : ViewAdjustment.doNothing), focusSpeed ?? 0)) + new Promise(res => setTimeout(async () => res(endFocus ? + await endFocus(didFocus||focusSpeed !== undefined) : + ViewAdjustment.doNothing), focusSpeed ?? 0) + ) }); } @@ -696,6 +699,7 @@ export class DocumentViewInternal extends DocComponent { + if (e?.nativeEvent.cancelBubble) return; if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 4eb07fd8d..1fb3cef2a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -179,12 +179,14 @@ export class PDFBox extends ViewBoxAnnotatableComponent { + let didToggle = false; if (DocListCast(this.props.Document[this.fieldKey + "-sidebar"]).includes(doc) && !this.SidebarShown) { this.toggleSidebar(!smooth); + didToggle = true; } if (this._sidebarRef?.current?.makeDocUnfiltered(doc)) return 1; this._initialScrollTarget = doc; - return this._pdfViewer?.scrollFocus(doc, smooth); + return this._pdfViewer?.scrollFocus(doc, smooth) ?? (didToggle ? 1 : undefined); } getAnchor = () => { const anchor = diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f83fdffc9..27227c152 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -18,7 +18,7 @@ import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { ComputedField } from '../../../../fields/ScriptField'; -import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; @@ -617,9 +617,48 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return false; } + + @undoBatch + deleteAnnotation = (anchor:Doc) => { + LinkManager.Instance.deleteLink(DocListCast(anchor.links)[0]); + // const docAnnotations = DocListCast(this.props.dataDoc[this.props.fieldKey]); + // this.props.dataDoc[this.props.fieldKey] = new List(docAnnotations.filter(a => a !== this.annoTextRegion)); + // AnchorMenu.Instance.fadeOut(true); + this.props.select(false); + } + + @undoBatch + pinToPres = (anchor:Doc) => this.props.pinToPres(anchor) + + @undoBatch + makePushpin = (anchor:Doc) => anchor.isPushpin = !anchor.isPushpin + + isPushpin = (anchor:Doc) => BoolCast(anchor.isPushpin); + specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; + const editor = this._editorView!; + const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); + let target = (e.target as any).parentElement; // hrefs are stored on the database of the
    node that wraps the hyerlink + while (target && !target.dataset?.targethrefs) target = target.parentElement; + if (target) { + const hrefs = (target.dataset?.targethrefs as string)?.trim().split(" ").filter(h => h); + const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), "").split("?")[0]; + e.persist(); + anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => { + AnchorMenu.Instance.Status = "annotation"; + AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc); + AnchorMenu.Instance.Pinned = false; + AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc); + AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc); + AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc); + AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); + })); + e.stopPropagation(); + return; + } + const changeItems: ContextMenuProps[] = []; changeItems.push({ description: "plain", event: undoBatch(() => { @@ -797,8 +836,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } scrollFocus = (textAnchor: Doc, smooth: boolean) => { + let didToggle = false; if (DocListCast(this.Document[this.fieldKey + "-sidebar"]).includes(textAnchor) && !this.SidebarShown) { this.toggleSidebar(!smooth); + didToggle = true; } const textAnchorId = textAnchor[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { @@ -851,7 +892,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed + return this._didScroll ? this._focusSpeed : didToggle ? 1: undefined; // if we actually scrolled, then return some focusSpeed } getScrollHeight = () => this.scrollHeight; @@ -1251,6 +1292,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onPointerDown = (e: React.PointerEvent): void => { + if (((e.nativeEvent as any).handledByInnerReactInstance)) { + return e.stopPropagation(); + } else (e.nativeEvent as any).handledByInnerReactInstance = true; + if (this.Document.forceActive) e.stopPropagation(); this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view. if ((e.target as any).tagName === "AUDIOTAG") { @@ -1304,10 +1349,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; - if ((e.nativeEvent as any).formattedHandled) { - console.log("handled"); - } - if (!(e.nativeEvent as any).formattedHandled && this.props.isContentActive(true)) { + if (this.props.isContentActive(true)) { const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); @@ -1315,9 +1357,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); } - (e.nativeEvent as any).formattedHandled = true; - if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { + if (e.button === 0 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } } @@ -1334,8 +1375,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } FormattedTextBoxComment.Hide(); - (e.nativeEvent as any).formattedHandled = true; - if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } @@ -1354,6 +1393,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @observable public static Focused: FormattedTextBox | undefined; onClick = (e: React.MouseEvent): void => { + if ((e.nativeEvent as any).handledByInnerReactInstance) { + e.stopPropagation(); + return; + } if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { this._forceDownNode = undefined; return; @@ -1378,12 +1421,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos))); } } - if ((e.nativeEvent as any).formattedHandled) { - e.stopPropagation(); - return; - } if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events - (e.nativeEvent as any).formattedHandled = true; + (e.nativeEvent as any).handledByInnerReactInstance = true; if (this.ProseRef?.children[0] !== e.nativeEvent.target) e.stopPropagation(); // if you double click on text, then it will be selected instead of sending a double click to DocumentView & opening a lightbox. Also,if a text box has isLinkButton, this will prevent link following if you've selected the document to edit it. // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks (see above comment) this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey); @@ -1424,26 +1463,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - onMouseUp = (e: React.MouseEvent): void => { - e.stopPropagation(); - - const view = this._editorView as any; - // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there - // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. - if (view.mouseDown) { - const originalUpHandler = view.mouseDown.up; - view.root.removeEventListener("mouseup", originalUpHandler); - view.mouseDown.up = (e: MouseEvent) => { - if (!(e as any).formattedHandled) { - originalUpHandler(e); - (e as any).formattedHandled = true; - } else { - console.log("prehandled"); - } - }; - view.root.addEventListener("mouseup", view.mouseDown.up); - } - } startUndoTypingBatch() { !this._undoTyping && (this._undoTyping = UndoManager.StartBatch("undoTyping")); } @@ -1688,7 +1707,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} - onMouseUp={this.onMouseUp} onDoubleClick={this.onDoubleClick} >
    Date: Thu, 7 Jul 2022 13:02:33 -0400 Subject: trying to fix errors in compiles --- package-lock.json | 2282 +++++++++++++++++-- package.json | 40 +- src/client/util/CaptureManager.tsx | 157 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/GroupManager.tsx | 253 ++- src/client/util/ScrollBox.tsx | 23 +- src/client/util/SharingManager.tsx | 566 +++-- src/client/views/Main.tsx | 44 +- src/client/views/MainView.tsx | 1086 ++++++---- src/client/views/Touchable.tsx | 68 +- src/client/views/animationtimeline/Timeline.tsx | 286 ++- .../views/collections/CollectionStackingView.tsx | 657 +++--- .../views/collections/CollectionTreeView.tsx | 321 +-- src/client/views/collections/CollectionView.tsx | 266 ++- .../CollectionFreeFormLinksView.tsx | 34 +- .../views/collections/collectionGrid/Grid.tsx | 17 +- .../collectionLinear/CollectionLinearView.tsx | 291 +-- src/client/views/linking/LinkEditor.tsx | 346 +-- src/client/views/linking/LinkMenuItem.tsx | 226 +- src/client/views/nodes/DocumentContentsView.tsx | 259 ++- src/client/views/nodes/LinkDocPreview.tsx | 192 +- src/client/views/nodes/MapBox/MapBox.tsx | 505 ++--- src/client/views/nodes/PDFBox.tsx | 451 ++-- src/client/views/nodes/formattedText/schema_rts.ts | 13 +- src/client/views/nodes/trails/PresBox.tsx | 2285 +++++++++++++------- src/client/views/search/SearchBox.tsx | 213 +- src/mobile/MobileInterface.tsx | 602 ++++-- src/server/authentication/AuthenticationManager.ts | 306 +-- 28 files changed, 7658 insertions(+), 4133 deletions(-) (limited to 'src/client/util') diff --git a/package-lock.json b/package-lock.json index e895161b5..7e413d5c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -462,6 +462,76 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, + "@eslint/eslintrc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, "@ffmpeg/core": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@ffmpeg/core/-/core-0.10.0.tgz", @@ -547,9 +617,9 @@ } }, "@fortawesome/react-fontawesome": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz", - "integrity": "sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==", + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz", + "integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==", "requires": { "prop-types": "^15.8.1" } @@ -580,6 +650,19 @@ "emotion": "^10.0.0", "prop-types": "^15.7.1", "react-transition-group": "^2.3.1" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } } }, "@hig/theme-context": { @@ -608,6 +691,40 @@ "lodash.memoize": "^4.1.2" } }, + "@humanwhocodes/config-array": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@hypnosphi/create-react-context": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", @@ -1184,9 +1301,9 @@ } }, "@types/express-session": { - "version": "1.17.4", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz", - "integrity": "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg==", + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.5.tgz", + "integrity": "sha512-l0DhkvNVfyUPEEis8fcwbd46VptfA/jmMwHfob2TfDMf3HyPLiB9mKD71LXhz5TMUobODXPD27zXSwtFQLHm+w==", "dev": true, "requires": { "@types/express": "*" @@ -1332,6 +1449,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/keygrip": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz", @@ -1461,9 +1584,9 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" }, "@types/passport": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.8.tgz", - "integrity": "sha512-Gdcvis7+7G/Mobm+25AeFi+oe5teBhHzpbCOFWeN10Bj8tnoEE1L5lkraQjzmDEKkJQuM7xSJUGIFGl/giyRfQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.9.tgz", + "integrity": "sha512-9+ilzUhmZQR4JP49GdC2O4UdDE3POPLwpmaTC/iLkW7l0TZCXOo1zsTnnlXPq6rP1UsUZPfbAV4IUdiwiXyC7g==", "dev": true, "requires": { "@types/express": "*" @@ -1663,10 +1786,9 @@ } }, "@types/react": { - "version": "16.14.26", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.26.tgz", - "integrity": "sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ==", - "dev": true, + "version": "18.0.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.15.tgz", + "integrity": "sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1676,8 +1798,7 @@ "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", - "dev": true + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" } } }, @@ -1720,18 +1841,18 @@ } }, "@types/react-dom": { - "version": "16.9.16", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.16.tgz", - "integrity": "sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg==", + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", + "integrity": "sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==", "dev": true, "requires": { - "@types/react": "^16" + "@types/react": "*" } }, "@types/react-grid-layout": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-0.17.2.tgz", - "integrity": "sha512-iLKQ5rI9KyPGs/BlWmeBfoikRnoOmTuddK8V5mOCgJ46e2JaecZuPhuIX1kV0mkQSk6X3XoEZALVW0XQbz9g5Q==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz", + "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==", "dev": true, "requires": { "@types/react": "*" @@ -1741,6 +1862,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz", "integrity": "sha512-Vefs6LkLqF61vfV7AiAqls+vpR94q67gunhMueDznG+msAkrYgRxl7gYjNem/kZ+as2l2mNChmF1jRZzzQQtMg==", + "dev": true, "requires": { "react-icons": "*" } @@ -1758,6 +1880,7 @@ "version": "0.26.7", "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "dev": true, "requires": { "@types/react": "*" }, @@ -1766,6 +1889,7 @@ "version": "18.0.12", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", + "dev": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1775,7 +1899,8 @@ "csstype": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true } } }, @@ -1844,28 +1969,11 @@ } }, "@types/react-transition-group": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", - "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", "requires": { "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "18.0.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.12.tgz", - "integrity": "sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "csstype": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", - "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" - } } }, "@types/reactcss": { @@ -2359,22 +2467,22 @@ } }, "@webpack-cli/configtest": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz", - "integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==" }, "@webpack-cli/info": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.4.1.tgz", - "integrity": "sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz", - "integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==" }, "@webscopeio/react-textarea-autocomplete": { "version": "4.9.1", @@ -2487,7 +2595,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" }, "agent-base": { "version": "6.0.2", @@ -2617,6 +2725,23 @@ } } }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, "ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -2735,6 +2860,16 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -2852,6 +2987,19 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -2872,6 +3020,30 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, + "array.prototype.flatmap": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz", + "integrity": "sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + } + }, "array.prototype.reduce": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", @@ -2922,6 +3094,18 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", @@ -3006,6 +3190,12 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "axe-core": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.2.tgz", + "integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==", + "dev": true + }, "axios": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", @@ -3014,6 +3204,12 @@ "follow-redirects": "1.5.10" } }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, "babel": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel/-/babel-6.23.0.tgz", @@ -3078,6 +3274,28 @@ } } }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "babel-plugin-emotion": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", @@ -3169,7 +3387,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" }, "balanced-match": { "version": "1.0.2", @@ -3234,7 +3452,7 @@ "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" }, "base64-js": { "version": "1.5.1", @@ -3752,9 +3970,9 @@ "integrity": "sha512-u+Ll+RDaiQEproTQjZLjZwyfNgNezA1fERMT7/54npcz+PkbVJUAHXMUz4bkXQYRPWrcFNO0Fbi1mwjfXg6N5g==" }, "canvas": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.1.tgz", - "integrity": "sha512-vSQti1uG/2gjv3x6QLOZw7TctfufaerTWbVe+NSduHxxLGB+qf3kFgQ6n66DSnuoINtVUjrLLIK2R+lxrBG07A==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.9.3.tgz", + "integrity": "sha512-WOUM7ghii5TV2rbhaZkh1youv/vW1/Canev6Yx6BG2W+1S07w8jKZqKkPnbiPpQEDsnJdN8ouDd7OvQEGXDcUw==", "requires": { "@mapbox/node-pre-gyp": "^1.0.0", "nan": "^2.15.0", @@ -3822,6 +4040,12 @@ "is-regex": "^1.0.3" } }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -3932,6 +4156,21 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, "clipboard": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", @@ -4140,7 +4379,7 @@ "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" }, "component-emitter": { "version": "1.3.0", @@ -4150,7 +4389,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" }, "compress-brotli": { "version": "1.3.8", @@ -4295,6 +4534,12 @@ } } }, + "confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", @@ -4860,16 +5105,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -4995,6 +5230,12 @@ "d3-transition": "2" } }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -5584,6 +5825,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", @@ -5809,9 +6059,9 @@ } }, "engine.io": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", - "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz", + "integrity": "sha512-Kc8fo5bbg8F4a2f3HPHTEpGyq/IRIQpyeHu3H1ThR14XDD7VrLcsGBo16HUpahgp8YkHJDaU5gNxJZbuGcuueg==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", @@ -5865,7 +6115,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "ws": { "version": "7.4.6", @@ -5985,6 +6235,15 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", "dev": true }, + "es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -5995,28 +6254,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.61", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", - "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6028,7 +6265,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -6069,36 +6305,1192 @@ } } }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "eslint": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", + "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, "requires": { - "estraverse": "^5.2.0" + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true - } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-config-esnext": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-esnext/-/eslint-config-esnext-4.1.0.tgz", + "integrity": "sha512-GhfVEXdqYKEIIj7j+Fw2SQdL9qyZMekgXfq6PyXM66cQw0B435ddjz3P3kxOBVihMRJ0xGYjosaveQz5Y6z0uA==", + "dev": true, + "requires": { + "babel-eslint": "^10.0.1", + "eslint": "^6.8.0", + "eslint-plugin-babel": "^5.2.1", + "eslint-plugin-import": "^2.14.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "eslint-config-node": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-node/-/eslint-config-node-4.1.0.tgz", + "integrity": "sha512-Wz17xV5O2WFG8fGdMYEBdbiL6TL7YNJSJvSX9V4sXQownewfYmoqlly7wxqLkOUv/57pq6LnnotMiQQrrPjCqQ==", + "dev": true, + "requires": { + "eslint": "^6.8.0", + "eslint-config-esnext": "^4.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + } + } + }, + "eslint-plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", + "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.0.tgz", + "integrity": "sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.18.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.2", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.1", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "dependencies": { + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-react": { + "version": "7.30.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz", + "integrity": "sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "array.prototype.flatmap": "^1.3.0", + "doctrine": "^2.1.0", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.5", + "object.fromentries": "^2.0.5", + "object.hasown": "^1.1.1", + "object.values": "^1.1.5", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.3", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.7" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "resolve": { + "version": "2.0.0-next.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", + "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true + }, + "eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "dev": true, + "requires": { + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } } }, "estraverse": { @@ -6451,6 +7843,28 @@ } } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -6554,6 +7968,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6614,6 +8034,24 @@ "when": ">= 0.0.1" } }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-3.0.1.tgz", @@ -6818,6 +8256,22 @@ "is-buffer": "~2.0.3" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", + "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", + "dev": true + }, "flexlayout-react": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/flexlayout-react/-/flexlayout-react-0.3.11.tgz", @@ -7144,6 +8598,12 @@ "functions-have-names": "^1.2.2" } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -7645,14 +9105,14 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" }, "has-flag": { "version": "3.0.0", @@ -7963,11 +9423,6 @@ } } }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, "hyntax": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/hyntax/-/hyntax-1.1.9.tgz", @@ -8153,7 +9608,7 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" }, "inflight": { "version": "1.0.6", @@ -8198,6 +9653,116 @@ "css-in-js-utils": "^2.0.0" } }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "inspect-function": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz", @@ -9055,6 +10620,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -9195,6 +10766,30 @@ "promise": "^7.0.1" } }, + "jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, "jszip": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.0.tgz", @@ -9292,6 +10887,21 @@ "graceful-fs": "^4.1.9" } }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -9460,7 +11070,7 @@ "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, "lodash.isplainobject": { "version": "4.0.6", @@ -9711,7 +11321,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", + "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", "requires": { "jquery": "^1.12.3" }, @@ -9719,7 +11329,7 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" + "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" } } }, @@ -9817,7 +11427,8 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "methods": { "version": "1.1.2", @@ -9871,7 +11482,8 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, "mimic-response": { "version": "2.1.0", @@ -10301,6 +11913,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "nan": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", @@ -10339,6 +11957,12 @@ "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz", "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck=" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14468,6 +16092,28 @@ "object-keys": "^1.0.11" } }, + "object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "object.getownpropertydescriptors": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", @@ -14479,6 +16125,16 @@ "es-abstract": "^1.20.1" } }, + "object.hasown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", + "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", + "dev": true, + "requires": { + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -14487,6 +16143,17 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, "obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -14518,6 +16185,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -14563,9 +16231,9 @@ } }, "orderedmap": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-1.1.8.tgz", - "integrity": "sha512-eWEYOAggZZpZbJ9CTsqAKOTxlbBHdHZ8pzcfEvNTxGrjQ/m+Q25nSWUiMlT9MTbgpB6FOiBDKqsgJ2FlLDVNaw==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.0.0.tgz", + "integrity": "sha512-buf4PoAMlh45b8a8gsGy/X6w279TSqkyAS0C0wdTSJwFSU+ljQFJON5I8NfjLHoCXwpSROIo2wr0g33T+kQshQ==" }, "os-homedir": { "version": "1.0.2", @@ -15270,6 +16938,21 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -15402,11 +17085,11 @@ } }, "prosemirror-model": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.18.0.tgz", - "integrity": "sha512-EZLl0mvVIixEL1V538GsPP4Iw+D83gBRdLcjL7VouzoPPWlhhv/0Y0V9ttT5lUhF7jieIrNHOa3XNFs1FwgrOw==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.18.1.tgz", + "integrity": "sha512-IxSVBKAEMjD7s3n8cgtwMlxAXZrC7Mlag7zYsAKDndAqnDScvSmp/UdnRTV/B33lTCVU3CCm7dyAn/rVVD0mcw==", "requires": { - "orderedmap": "^1.1.0" + "orderedmap": "^2.0.0" } }, "prosemirror-schema-list": { @@ -15420,9 +17103,9 @@ } }, "prosemirror-state": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.0.tgz", - "integrity": "sha512-mVDZdjNX/YT5FvypiwbphJe9psA5h+j9apsSszVRFc6oKFoIInvzdujh8QW9f9lwHtSYajLxNiM1hPhd0Sl1XA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.1.tgz", + "integrity": "sha512-U/LBDW2gNmVa07sz/D229XigSdDQ5CLFwVB1Vb32MJbAHHhWe/6pOc721faI17tqw4pZ49i1xfY/jEZ9tbIhPg==", "requires": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0" @@ -15437,9 +17120,9 @@ } }, "prosemirror-view": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.26.0.tgz", - "integrity": "sha512-tT8ICUAw6YfOTRIwVQJoCdF65BYxoWFjAqmv44Kn4phYjdLJ8Vc92dhL3LnmwAQuZ9yfPHzRR6Lfb3/5EVJHWg==", + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.26.5.tgz", + "integrity": "sha512-SO+AX6WwdbJZHVvuloXI0qfO+YJAnZAat8qrYwfiqTQwL/FewLUnr0m3EXZ6a60hQs8/Q/lzeJXiFR/dOPaaKQ==", "requires": { "prosemirror-model": "^1.16.0", "prosemirror-state": "^1.0.0", @@ -15981,15 +17664,26 @@ } }, "react-grid-layout": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-0.18.3.tgz", - "integrity": "sha512-lHkrk941Tk5nTwZPa9uj6ttHBT0VehSHwEhWbINBJKvM1GRaFNOefvjcuxSyuCI5JWjVUP+Qm3ARt2470AlxMA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", + "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", "requires": { - "classnames": "2.x", + "clsx": "^1.1.1", "lodash.isequal": "^4.0.0", - "prop-types": "^15.0.0", + "prop-types": "^15.8.1", "react-draggable": "^4.0.0", - "react-resizable": "^1.9.0" + "react-resizable": "^3.0.4" + }, + "dependencies": { + "react-resizable": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.4.tgz", + "integrity": "sha512-StnwmiESiamNzdRHbSSvA65b0ZQJ7eVQpPusrSmcpyGKzC0gojhtO62xxH6YOBmepk9dQTBi9yxidL3W4s3EBA==", + "requires": { + "prop-types": "15.x", + "react-draggable": "^4.0.3" + } + } } }, "react-icons": { @@ -16189,9 +17883,9 @@ } }, "react-refresh-typescript": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.5.tgz", - "integrity": "sha512-TlcxF9VMBBS8dHvMD+3dklgArXeIhjbtT7R09ycsXvhPPYmv70J67kiGT8mxe6/tYzkql9aCHaZAY6QYdutLdw==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/react-refresh-typescript/-/react-refresh-typescript-2.0.7.tgz", + "integrity": "sha512-KbuW57FauO11e6a9gU836sCm3M3z0b2+J2qPhUudg0QplOfz0eAF3gMeshcUC/ChfNLJCK1SZxvYmUtRxiZE5A==" }, "react-resizable": { "version": "1.11.1", @@ -16298,14 +17992,30 @@ "integrity": "sha512-EblIqTAsIpkYeM8bZtC4lcpTE0A2zCEGipFB52RgcQq/q+0oryrk7Sxt+sqhIjUu6xMNEVywV8dr74lz5yWO6A==" }, "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", "requires": { - "dom-helpers": "^3.4.0", + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "prop-types": "^15.6.2" + }, + "dependencies": { + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + } } }, "react-use-measure": { @@ -16492,6 +18202,12 @@ "functions-have-names": "^1.2.2" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "registry-auth-token": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", @@ -16811,6 +18527,16 @@ } } }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -16856,6 +18582,12 @@ "sdp": "^2.6.0" } }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -16873,6 +18605,23 @@ } } }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -17446,6 +19195,17 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", @@ -17567,15 +19327,15 @@ } }, "socket.io": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz", - "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.5.0.tgz", + "integrity": "sha512-gGunfS0od3VpwDBpGwVkzSZx6Aqo9uOcf1afJj2cKnKFAoyl16fvhpsUhmUFd4Ldbvl5JvRQed6eQw6oQp6n8w==", "requires": { "debug": "~4.1.0", - "engine.io": "~3.5.0", + "engine.io": "~3.6.0", "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.4.0", + "socket.io-client": "2.5.0", "socket.io-parser": "~3.4.0" }, "dependencies": { @@ -17595,9 +19355,9 @@ "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" }, "socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz", + "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==", "requires": { "backo2": "1.0.2", "component-bind": "1.0.0", @@ -17623,12 +19383,12 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "socket.io-parser": { "version": "3.3.2", @@ -18052,6 +19812,22 @@ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" }, + "string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -18128,11 +19904,6 @@ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, "strip-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", @@ -18240,6 +20011,46 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "table-layout": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.5.tgz", @@ -18373,6 +20184,12 @@ } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "textarea-caret": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", @@ -18451,10 +20268,19 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" }, "to-fast-properties": { "version": "2.0.0", @@ -18835,6 +20661,26 @@ } } }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, "tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -18946,12 +20792,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -18966,6 +20806,12 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -18986,9 +20832,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz", - "integrity": "sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, "typescript-collections": { @@ -19415,6 +21261,12 @@ "resolved": "https://registry.npmjs.org/uuid-js/-/uuid-js-0.7.5.tgz", "integrity": "sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA=" }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", @@ -19637,17 +21489,17 @@ } }, "webpack-cli": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.9.2.tgz", - "integrity": "sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.1.1", - "@webpack-cli/info": "^1.4.1", - "@webpack-cli/serve": "^1.6.1", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", "colorette": "^2.0.14", "commander": "^7.0.0", - "execa": "^5.0.0", + "cross-spawn": "^7.0.3", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", @@ -19670,40 +21522,11 @@ "which": "^2.0.1" } }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, "interpret": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==" }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -20281,6 +22104,15 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, "write-file-atomic": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", @@ -20445,7 +22277,7 @@ "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" }, "yn": { "version": "2.0.0", diff --git a/package.json b/package.json index e4ec2798d..93968dce4 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@types/exif": "^0.6.3", "@types/express": "^4.17.13", "@types/express-flash": "0.0.0", - "@types/express-session": "^1.17.4", + "@types/express-session": "^1.17.5", "@types/express-validator": "^3.0.0", "@types/file-saver": "^2.0.5", "@types/google-maps-react": "^2.0.5", @@ -49,7 +49,7 @@ "@types/mongoose": "^5.11.97", "@types/node": "^10.17.60", "@types/nodemailer": "^4.6.6", - "@types/passport": "^1.0.7", + "@types/passport": "^1.0.9", "@types/passport-google-oauth20": "^2.0.11", "@types/passport-local": "^1.0.34", "@types/pdfjs-dist": "^2.10.378", @@ -65,12 +65,15 @@ "@types/prosemirror-transform": "^1.1.5", "@types/prosemirror-view": "^1.23.1", "@types/rc-switch": "^1.9.2", - "@types/react": "^16.14.23", + "@types/react": "^18.0.15", + "@types/react-icons": "^3.0.0", + "@types/react-reconciler": "^0.26.4", + "@types/react-transition-group": "^4.4.5", "@types/react-autosuggest": "^9.3.14", "@types/react-color": "^2.17.6", "@types/react-datepicker": "^3.1.8", - "@types/react-dom": "^16.9.14", - "@types/react-grid-layout": "^0.17.2", + "@types/react-dom": "^18.0.6", + "@types/react-grid-layout": "^1.3.2", "@types/react-measure": "^2.0.8", "@types/react-select": "^3.1.2", "@types/react-table": "^6.8.9", @@ -117,7 +120,7 @@ "ts-node-dev": "^1.1.8", "tslint": "^5.20.1", "tslint-loader": "^3.6.0", - "typescript": "^4.6.2", + "typescript": "^4.7.4", "webpack": "^5.69.1", "webpack-dev-server": "^3.11.3", "webpack-hot-middleware": "^2.25.1" @@ -129,7 +132,7 @@ "@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", - "@fortawesome/react-fontawesome": "^0.1.17", + "@fortawesome/react-fontawesome": "^0.1.19", "@hig/flyout": "^1.3.1", "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.23.1", @@ -145,14 +148,13 @@ "@types/dom-speech-recognition": "0.0.1", "@types/formidable": "1.0.31", "@types/google-maps": "^3.2.3", - "@types/react-icons": "^3.0.0", - "@types/react-reconciler": "^0.26.4", "@types/reveal": "^3.3.33", "@types/supercluster": "^7.1.0", "@types/three": "^0.126.2", "@types/web": "0.0.53", "@types/webscopeio__react-textarea-autocomplete": "^4.7.2", "@webscopeio/react-textarea-autocomplete": "^4.9.1", + "D": "^1.0.0", "adm-zip": "^0.4.16", "archiver": "^3.1.1", "array-batcher": "^1.2.3", @@ -167,7 +169,7 @@ "bootstrap": "^4.6.1", "browser-assert": "^1.2.1", "bson": "^4.6.1", - "canvas": "^2.9.0", + "canvas": "^2.9.3", "child_process": "^1.0.2", "chrome": "^0.1.0", "class-transformer": "^0.2.0", @@ -178,7 +180,6 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.0.0", "cors": "^2.8.5", - "D": "^1.0.0", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", "exif": "^0.6.0", @@ -250,11 +251,11 @@ "prosemirror-history": "^1.2.0", "prosemirror-inputrules": "^1.1.3", "prosemirror-keymap": "^1.1.5", - "prosemirror-model": "^1.16.1", + "prosemirror-model": "^1.18.1", "prosemirror-schema-list": "^1.1.6", - "prosemirror-state": "^1.3.4", + "prosemirror-state": "^1.4.1", "prosemirror-transform": "^1.3.4", - "prosemirror-view": "^1.23.7", + "prosemirror-view": "^1.26.5", "pug": "^2.0.4", "puppeteer": "^3.3.0", "query-string": "^6.14.1", @@ -268,18 +269,19 @@ "react-compound-slider": "^2.5.0", "react-datepicker": "^3.8.0", "react-dom": "^16.14.0", - "react-grid-layout": "^0.18.3", + "react-grid-layout": "^1.3.4", "react-icons": "^4.3.1", "react-image-lightbox-with-rotate": "^5.1.1", "react-jsx-parser": "^1.29.0", "react-loading": "^2.0.3", "react-measure": "^2.5.2", - "react-refresh-typescript": "^2.0.3", + "react-refresh-typescript": "^2.0.7", "react-resizable": "^1.11.1", "react-resizable-rotatable-draggable": "^0.2.0", "react-reveal": "^1.2.2", "react-select": "^3.2.0", "react-table": "^6.11.5", + "react-transition-group": "^4.4.2", "readline": "^1.3.0", "request": "^2.88.2", "request-promise": "^4.2.6", @@ -288,8 +290,8 @@ "serializr": "^1.5.4", "sharp": "^0.23.4", "shelljs": "^0.8.5", - "socket.io": "^2.4.1", - "socket.io-client": "^2.4.0", + "socket.io": "^2.5.0", + "socket.io-client": "^2.5.0", "solr-node": "^1.2.1", "standard-http-error": "^2.0.1", "stream-browserify": "^3.0.0", @@ -306,7 +308,7 @@ "uuid": "^3.4.0", "valid-url": "^1.0.9", "web-request": "^1.0.7", - "webpack-cli": "^4.9.2", + "webpack-cli": "^4.10.0", "webpack-dev-middleware": "^5.3.1", "webrtc-adapter": "^7.7.1", "wikijs": "^6.3.3", diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index c247afa26..0b5957fac 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -1,17 +1,17 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { convertToObject } from "typescript"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { BoolCast, StrCast, Cast } from "../../fields/Types"; -import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils"; -import { LightboxView } from "../views/LightboxView"; -import { MainViewModal } from "../views/MainViewModal"; -import "./CaptureManager.scss"; -import { SelectionManager } from "./SelectionManager"; -import { undoBatch } from "./UndoManager"; -const higflyout = require("@hig/flyout"); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { convertToObject } from 'typescript'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { BoolCast, StrCast, Cast } from '../../fields/Types'; +import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils'; +import { LightboxView } from '../views/LightboxView'; +import { MainViewModal } from '../views/MainViewModal'; +import './CaptureManager.scss'; +import { SelectionManager } from './SelectionManager'; +import { undoBatch } from './UndoManager'; +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -22,32 +22,31 @@ export class CaptureManager extends React.Component<{}> { @observable _document: any; @observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not. - constructor(props: {}) { super(props); CaptureManager.Instance = this; } - public close = action(() => this.isOpen = false); + public close = action(() => (this.isOpen = false)); public open = action((doc: Doc) => { this.isOpen = true; this._document = doc; }); - @computed get visibilityContent() { - - return
    -
    Visibility
    -
    -
    - Private -
    -
    - Public + return ( +
    +
    Visibility
    +
    +
    + Private +
    +
    + Public +
    -
    ; + ); } @computed get linksContent() { @@ -66,75 +65,79 @@ export class CaptureManager extends React.Component<{}> { order.push(
    {i}
    - {(l.anchor1 as Doc).title} + {StrCast((l.anchor1 as Doc).title)}
    ); } }); } - return
    -
    Links
    -
    - {order} + return ( +
    +
    Links
    +
    {order}
    -
    ; + ); } @computed get closeButtons() { - return
    -
    -
    { - LightboxView.SetLightboxDoc(this._document); - this.close(); - }}> - Save -
    -
    { - const selected = SelectionManager.Views().slice(); - SelectionManager.DeselectAll(); - selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); - this.close(); - }}> - Cancel + return ( +
    +
    +
    { + LightboxView.SetLightboxDoc(this._document); + this.close(); + }}> + Save +
    +
    { + const selected = SelectionManager.Views().slice(); + SelectionManager.DeselectAll(); + selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + this.close(); + }}> + Cancel +
    -
    ; + ); } - - private get captureInterface() { - return
    -
    -
    -
    + return ( +
    +
    +
    +
    + Conversation Capture
    - Conversation Capture -
    -
    - -
    - {this.visibilityContent} - {this.linksContent} -
    - +
    + {this.visibilityContent} + {this.linksContent} +
    + +
    + {this.closeButtons}
    - {this.closeButtons} -
    ; - + ); } render() { - return ; + return ( + + ); } -} \ No newline at end of file +} diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b2a5fddcd..84efcb966 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -957,7 +957,7 @@ export class CurrentUserUtils { } new LinkManager(); - DocServer.UPDATE_SERVER_CACHE(); + setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500); return doc; } static setupFieldInfos(doc:Doc, field="fieldInfos") { diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 62d656c5d..59334f6a2 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,19 +1,19 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; import Select from 'react-select'; -import * as RequestPromise from "request-promise"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; -import { StrCast, Cast } from "../../fields/Types"; -import { Utils } from "../../Utils"; -import { MainViewModal } from "../views/MainViewModal"; -import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import "./GroupManager.scss"; -import { GroupMemberView } from "./GroupMemberView"; -import { SharingManager, User } from "./SharingManager"; -import { listSpec } from "../../fields/Schema"; -import { DateField } from "../../fields/DateField"; +import * as RequestPromise from 'request-promise'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; +import { StrCast, Cast } from '../../fields/Types'; +import { Utils } from '../../Utils'; +import { MainViewModal } from '../views/MainViewModal'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import './GroupManager.scss'; +import { GroupMemberView } from './GroupMemberView'; +import { SharingManager, User } from './SharingManager'; +import { listSpec } from '../../fields/Schema'; +import { DateField } from '../../fields/DateField'; /** * Interface for options for the react-select component @@ -25,7 +25,6 @@ export interface UserOptions { @observer export class GroupManager extends React.Component<{}> { - static Instance: GroupManager; @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not. @observable private users: string[] = []; // list of users populated from the database. @@ -34,24 +33,26 @@ export class GroupManager extends React.Component<{}> { @observable private createGroupModalOpen: boolean = false; private inputRef: React.RefObject = React.createRef(); // the ref for the input box. private createGroupButtonRef: React.RefObject = React.createRef(); // the ref for the group creation button - @observable private buttonColour: "#979797" | "black" = "#979797"; - @observable private groupSort: "ascending" | "descending" | "none" = "none"; + @observable private buttonColour: '#979797' | 'black' = '#979797'; + @observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; constructor(props: Readonly<{}>) { super(props); GroupManager.Instance = this; } - componentDidMount() { this.populateUsers(); } + componentDidMount() { + this.populateUsers(); + } /** * Fetches the list of users stored on the database. */ populateUsers = async () => { - const userList = await RequestPromise.get(Utils.prepend("/getUsers")); + const userList = await RequestPromise.get(Utils.prepend('/getUsers')); const raw = JSON.parse(userList) as User[]; raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email))); - } + }; /** * @returns the options to be rendered in the dropdown menu to add users and create a group. @@ -68,11 +69,11 @@ export class GroupManager extends React.Component<{}> { // SelectionManager.DeselectAll(); this.isOpen = true; this.populateUsers(); - } + }; /** * Hides the GroupManager. - */ + */ @action close = () => { this.isOpen = false; @@ -81,26 +82,32 @@ export class GroupManager extends React.Component<{}> { // this.users = []; this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; - } + }; /** * @returns the database of groups. */ - @computed get GroupManagerDoc(): Doc | undefined { return Doc.UserDoc().globalGroupDatabase as Doc; } + @computed get GroupManagerDoc(): Doc | undefined { + return Doc.UserDoc().globalGroupDatabase as Doc; + } /** * @returns a list of all group documents. */ - @computed get allGroups(): Doc[] { return DocListCast(this.GroupManagerDoc?.data); } + @computed get allGroups(): Doc[] { + return DocListCast(this.GroupManagerDoc?.data); + } /** * @returns the members of the admin group. */ - @computed get adminGroupMembers(): string[] { return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; } + @computed get adminGroupMembers(): string[] { + return this.getGroup('Admin') ? JSON.parse(StrCast(this.getGroup('Admin')!.members)) : ''; + } /** * @returns a group document based on the group name. - * @param groupName + * @param groupName */ getGroup(groupName: string): Doc | undefined { return this.allGroups.find(group => group.title === groupName); @@ -114,10 +121,9 @@ export class GroupManager extends React.Component<{}> { return JSON.parse(StrCast(this.getGroup(group)!.members)) as string[]; } - /** * @returns a boolean indicating whether the current user has access to edit group documents. - * @param groupDoc + * @param groupDoc */ hasEditAccess(groupDoc: Doc): boolean { if (!groupDoc) return false; @@ -127,12 +133,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that sets up the group document. - * @param groupName - * @param memberEmails + * @param groupName + * @param memberEmails */ createGroupDoc(groupName: string, memberEmails: string[] = []) { - const name = groupName.toLowerCase() === "admin" ? "Admin" : groupName; - const groupDoc = new Doc("GROUP:" + name, true); + const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName; + const groupDoc = new Doc('GROUP:' + name, true); groupDoc.title = name; groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]); groupDoc.members = JSON.stringify(memberEmails); @@ -141,12 +147,12 @@ export class GroupManager extends React.Component<{}> { /** * Helper method that adds a group document to the database of group documents and @returns whether it was successfully added or not. - * @param groupDoc + * @param groupDoc */ addGroup(groupDoc: Doc): boolean { if (this.GroupManagerDoc) { - Doc.AddDocToList(this.GroupManagerDoc, "data", groupDoc); - this.GroupManagerDoc["data-lastModified"] = new DateField; + Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc); + this.GroupManagerDoc['data-lastModified'] = new DateField(); return true; } return false; @@ -154,20 +160,20 @@ export class GroupManager extends React.Component<{}> { /** * Deletes a group from the database of group documents and @returns whether the group was deleted or not. - * @param group + * @param group */ @action deleteGroup(group: Doc): boolean { if (group) { if (this.GroupManagerDoc && this.hasEditAccess(group)) { - Doc.RemoveDocFromList(this.GroupManagerDoc, "data", group); + Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group); SharingManager.Instance.removeGroup(group); const members = JSON.parse(StrCast(group.members)); if (members.includes(Doc.CurrentUserEmail)) { const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group); index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1); } - this.GroupManagerDoc["data-lastModified"] = new DateField; + this.GroupManagerDoc['data-lastModified'] = new DateField(); if (group === this.currentGroup) { this.currentGroup = undefined; } @@ -179,8 +185,8 @@ export class GroupManager extends React.Component<{}> { /** * Adds a member to a group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ addMemberToGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -188,14 +194,14 @@ export class GroupManager extends React.Component<{}> { !memberList.includes(email) && memberList.push(email); groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.shareWithAddedMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } /** * Removes a member from the group. - * @param groupDoc - * @param email + * @param groupDoc + * @param email */ removeMemberFromGroup(groupDoc: Doc, email: string) { if (this.hasEditAccess(groupDoc)) { @@ -205,27 +211,27 @@ export class GroupManager extends React.Component<{}> { const user = memberList.splice(index, 1)[0]; groupDoc.members = JSON.stringify(memberList); SharingManager.Instance.removeMember(groupDoc, email); - this.GroupManagerDoc && (this.GroupManagerDoc["data-lastModified"] = new DateField); + this.GroupManagerDoc && (this.GroupManagerDoc['data-lastModified'] = new DateField()); } } } /** * Handles changes in the users selected in the "Select users" dropdown. - * @param selectedOptions + * @param selectedOptions */ @action handleChange = (selectedOptions: any) => { this.selectedUsers = selectedOptions as UserOptions[]; - } + }; /** * Creates the group when the enter key has been pressed (when in the input). - * @param e + * @param e */ handleKeyDown = (e: React.KeyboardEvent) => { - e.key === "Enter" && this.createGroup(); - } + e.key === 'Enter' && this.createGroup(); + }; /** * Handles the input of required fields in the setup of a group and resets the relevant variables. @@ -234,32 +240,37 @@ export class GroupManager extends React.Component<{}> { createGroup = () => { const { value } = this.inputRef.current!; if (!value) { - alert("Please enter a group name"); + alert('Please enter a group name'); return; } - if (["admin", "public", "override"].includes(value.toLowerCase())) { - if (value.toLowerCase() !== "admin" || (value.toLowerCase() === "admin" && this.getGroup("Admin"))) { + if (['admin', 'public', 'override'].includes(value.toLowerCase())) { + if (value.toLowerCase() !== 'admin' || (value.toLowerCase() === 'admin' && this.getGroup('Admin'))) { alert(`You cannot override the ${value.charAt(0).toUpperCase() + value.slice(1)} group`); return; } } if (this.getGroup(value)) { - alert("Please select a unique group name"); + alert('Please select a unique group name'); return; } - this.createGroupDoc(value, this.selectedUsers?.map(user => user.value)); + this.createGroupDoc( + value, + this.selectedUsers?.map(user => user.value) + ); this.selectedUsers = null; - this.inputRef.current!.value = ""; - this.buttonColour = "#979797"; + this.inputRef.current!.value = ''; + this.buttonColour = '#979797'; const { left, width, top } = this.createGroupButtonRef.current!.getBoundingClientRect(); TaskCompletionBox.popupX = left - 2 * width; TaskCompletionBox.popupY = top; - TaskCompletionBox.textDisplayed = "Group created!"; + TaskCompletionBox.textDisplayed = 'Group created!'; TaskCompletionBox.taskCompleted = true; - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2000); - - } + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); + }; /** * @returns the MainViewModal which allows the user to create groups. @@ -268,50 +279,43 @@ export class GroupManager extends React.Component<{}> { const contents = (
    -

    New Group

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

    + New Group +

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

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

    -
    - +

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

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

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

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

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

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

    {doc.title}

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

    {StrCast(doc.title)}

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

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

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

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

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

    Source: {source}

    : null} + {source ? ( +

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

    + ) : null}
    -
    +

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

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

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

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

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

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

    awaiting layout

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

    Loading layout

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

    awaiting layout

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

    Loading layout

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

    {StrCast(this._linkDoc.description)}

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

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