aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/CurrentUserUtils.ts81
-rw-r--r--src/client/util/DocumentManager.ts60
-rw-r--r--src/client/util/DragManager.ts38
-rw-r--r--src/client/util/DropConverter.ts8
-rw-r--r--src/client/util/LinkManager.ts6
-rw-r--r--src/client/util/PingManager.ts37
-rw-r--r--src/client/util/UndoManager.ts22
7 files changed, 148 insertions, 104 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 592f6d836..11a8dcaf6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -97,7 +97,7 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
- _stayInCollection: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
+ _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _forceActive: true, isSystem: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
};
const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
@@ -123,7 +123,7 @@ export class CurrentUserUtils {
}
/// Initializes templates for editing click funcs of a document
- static setupClickEditorTemplates(doc: Doc, field = "template-clickFuncs") {
+ static setupClickEditorTemplates(doc: Doc, field = "template_clickFuncs") {
const tempClicks = DocCast(doc[field]);
const reqdClickOpts:DocumentOptions = { _width: 300, _height:200, isSystem: true};
const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
@@ -144,7 +144,7 @@ export class CurrentUserUtils {
}
/// Initializes templates that can be applied to notes
- static setupNoteTemplates(doc: Doc, field="template-notes") {
+ static setupNoteTemplates(doc: Doc, field="template_notes") {
const tempNotes = DocCast(doc[field]);
const reqdTempOpts:DocumentOptions[] = [
{ noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"},
@@ -174,7 +174,7 @@ export class CurrentUserUtils {
}
// setup templates for different document types when they are iconified from Document Decorations
- static setupDefaultIconTemplates(doc: Doc, field="template-icons") {
+ static setupDefaultIconTemplates(doc: Doc, field="template_icons") {
const reqdOpts = { title: "icon templates", _height: 75, isSystem: true };
const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
@@ -279,11 +279,11 @@ export class CurrentUserUtils {
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }},
{key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, treeViewHideUnrendered: true}},
- {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, targetDropAction: "embed" as dropActionType, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
+ {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeViewHideTitle: true, _layout_fitWidth:true, _chromeHidden: true, layout_boxShadow: "0 0" }},
{key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }},
{key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree,
treeViewHasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true,
- allowOverlayDrop: true, treeViewType: TreeViewType.outline,
+ dropAction:'move', treeViewType: TreeViewType.outline,
backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true
}, funcs: {title: 'self.text?.Text'}},
];
@@ -322,17 +322,16 @@ export class CurrentUserUtils {
const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
- _width: 35, _height: 35, _layout_hideContextMenu: true, _stayInCollection: true,
+ _width: 35, _height: 35, _layout_hideContextMenu: true, _dragOnlyWithinContainer: true,
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
- _removeDropProperties: new List<string>(["_stayInCollection"]),
};
return DocUtils.AssignScripts(DocUtils.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs);
});
const reqdOpts:DocumentOptions = {
- title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
+ title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true,
- childDropAction: 'embed'
+ childDragAction: 'embed'
};
const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
@@ -357,7 +356,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") {
- DocUtils.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.isSystem = true; return doc;})(new Doc()), {isSystem:true});
+ DocUtils.AssignDocField(doc, field, (opts) => Doc.assign(new Doc(), opts as any), {isSystem:true, undoIgnoreFields: new List<string>(['proto'])});
}
/// Initializes the left sidebar menu buttons and the panels they open up
@@ -367,15 +366,14 @@ export class CurrentUserUtils {
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, isSystem: true, dontUndo: true, dontRegisterView: true,
- _width: 60, _height: 60, _stayInCollection: true, _layout_hideContextMenu: true,
- _removeDropProperties: new List<string>(["_stayInCollection"]),
+ title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
+ _width: 60, _height: 60, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
};
return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs);
});
const reqdStackOpts:DocumentOptions ={
- title: "menuItemPanel", childDropAction: "same", backgroundColor: Colors.DARK_GRAY, layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
+ title: "menuItemPanel", childDragAction: "same", backgroundColor: Colors.DARK_GRAY, layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
_chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
@@ -458,7 +456,7 @@ export class CurrentUserUtils {
/// Search option on the left side button panel
static setupSearcher(doc: Doc, field:string) {
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
- dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDropAction: "embed",
+ dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: "embed",
_lockedPosition: true, _type_collection: CollectionViewType.Schema });
}
@@ -469,7 +467,7 @@ export class CurrentUserUtils {
const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
- _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _chromeHidden: true,
+ _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true,
};
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
}
@@ -481,7 +479,7 @@ export class CurrentUserUtils {
const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`;
const newDashboard = `createNewDashboard()`;
- const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true,
+ const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", isSystem: true };
const reqdBtnScript = {onClick: newDashboard,}
const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
@@ -495,8 +493,8 @@ export class CurrentUserUtils {
const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
const reqdOpts:DocumentOptions = {
title: "My Dashboards", childHideLinkButton: true, treeViewFreezeChildren: "remove|add", treeViewHideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeViewTruncateTitleWidth: 350, ignoreClick: true,
- layout_headerButton: newDashboardButton, childDropAction: "embed",
+ dropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeViewTruncateTitleWidth: 350, ignoreClick: true,
+ layout_headerButton: newDashboardButton, childDragAction: "embed",
_layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
contextMenuLabels:new List<string>(contextMenuLabels),
contextMenuIcons:new List<string>(contextMenuIcons),
@@ -520,20 +518,20 @@ export class CurrentUserUtils {
/// initializes the left sidebar File system pane
static setupFilesystem(doc: Doc, field:string) {
var myFilesystem = DocCast(doc[field]);
- const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, isSystem: true, isFolder: true });
+ const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", undoIgnoreFields:new List<string>(['treeViewSortCriterion']), _dragOnlyWithinContainer: true, isSystem: true, isFolder: true });
const newFolder = `TreeView_addNewFolder()`;
const newFolderOpts: DocumentOptions = {
- _forceActive: true, _stayInCollection: true, _layout_hideContextMenu: true, _width: 30, _height: 30,
+ _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
};
const newFolderScript = { onClick: newFolder};
const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true,
- title: "My Documents", layout_headerButton: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", isSystem: true,
+ title: "My Documents", layout_headerButton: newFolderButton, treeViewHideTitle: true, dropAction: "proto", isSystem: true,
isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- treeViewTruncateTitleWidth: 350, ignoreClick: true, childDropAction: "embed",
+ treeViewTruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
childContextMenuLabels: new List<string>(["Create new folder"]),
childContextMenuIcons: new List<string>(["plus"]),
layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
@@ -549,8 +547,8 @@ export class CurrentUserUtils {
/// initializes the panel displaying docs that have been recently closed
static setupRecentlyClosed(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true,
- title: "My Recently Closed", childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "embed", isSystem: true,
- treeViewTruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same",
+ title: "My Recently Closed", childHideLinkButton: true, treeViewHideTitle: true, childDragAction: "embed", isSystem: true,
+ treeViewTruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same",
contextMenuLabels: new List<string>(["Empty recently closed"]),
contextMenuIcons:new List<string>(["trash"]),
layout_explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list."
@@ -558,7 +556,7 @@ export class CurrentUserUtils {
const recentlyClosed = DocUtils.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, _layout_hideContextMenu: true,
+ const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true,
title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", isSystem: true,
toolTip: "Empty recently closed",};
DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")});
@@ -575,7 +573,7 @@ export class CurrentUserUtils {
static setupUserDocView(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
_lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
- layout_boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, isSystem: true,
+ layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same", ignoreClick: true, isSystem: true,
treeViewHideTitle: true, treeViewTruncateTitleWidth: 350
};
if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" });
@@ -590,7 +588,7 @@ export class CurrentUserUtils {
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true,
- _removeDropProperties: new List<string>([ "_layout_hideContextMenu", "stayInCollection"]),
+ _dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]),
_nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts,
})
@@ -608,9 +606,9 @@ export class CurrentUserUtils {
{ scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}},
{ scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}},
];
- const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts));
+ const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts));
const dockBtnsReqdOpts:DocumentOptions = {
- title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDropAction: 'embed',
+ title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
childDontRegisterViews: true, linearView_IsExpanded: true, linearView_Expandable: true, ignoreClick: true
};
reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
@@ -716,12 +714,11 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit,
backgroundColor: params.backgroundColor ??"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, isSystem: true, //dontUndo: true,
+ color: Colors.WHITE, isSystem: true,
_nativeWidth: params.width ?? 30, _width: params.width ?? 30,
_height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth,
toolType: params.toolType, expertMode: params.expertMode,
- _stayInCollection: true, _layout_hideContextMenu: true, _lockedPosition: true,
- _removeDropProperties: new List<string>([ "_stayInCollection"]),
+ _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _lockedPosition: true,
};
const reqdFuncs:{[key:string]:any} = {
...params.funcs,
@@ -732,14 +729,14 @@ export class CurrentUserUtils {
/// Initializes all the default buttons for the top bar context menu
static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") {
- const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", dontUndo:true, flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true, linearView_IsExpanded: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+ const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsExpanded"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsExpanded: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
const ctxtMenuBtnsDoc = DocUtils.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, dontUndo: true,
+ const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsExpanded"]),
childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true,
linearView_SubMenu: true, linearView_Expandable: true, };
const items = params.subMenu?.map(sub =>
@@ -786,13 +783,13 @@ export class CurrentUserUtils {
const sharedDocOpts:DocumentOptions = {
title: "My Shared Docs",
userColor: "rgb(202, 202, 202)",
- isFolder:true,
+ isFolder:true, undoIgnoreFields:new List<string>(['treeViewSortCriterion']),
childContextMenuFilters: new List<ScriptField>([dashboardFilter!,]),
childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
childContextMenuLabels: new List<string>(["Add to Dashboards",]),
childContextMenuIcons: new List<string>(["user-plus",]),
"acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment,
- childDropAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
+ childDragAction: "embed", isSystem: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
// NOTE: treeViewHideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
_layout_showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
layout_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'"
@@ -805,14 +802,14 @@ export class CurrentUserUtils {
static setupImportSidebar(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
title: "My Imports", _forceActive: true, ignoreClick: true, _layout_showTitle: "title",
- _stayInCollection: true, _layout_hideContextMenu: true, childLimitHeight: 0,
- childDropAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
+ _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, childLimitHeight: 0,
+ childDragAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
dontRegisterView: true, layout_explainer: "This is where documents that are Imported into Dash will go."
};
const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts);
const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer",
- _width: 30, _height: 30, _stayInCollection: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
+ _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton,
buttonText: "Import", icon: "upload", isSystem: true };
DocUtils.AssignDocField(myImports, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" });
return myImports;
@@ -864,7 +861,7 @@ export class CurrentUserUtils {
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
- DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", isSystem: true }); // drop down panel at top of dashboard for stashing documents
+ DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", isSystem: true, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 612f16ce9..3f0848d00 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -25,7 +25,13 @@ export class DocumentManager {
@observable public RecordingEvent = 0;
@observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
@computed public get DocumentViews() {
- return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox));
+ return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath)));
+ }
+ public AddDocumentView(dv: DocumentView) {
+ this._documentViews.add(dv);
+ }
+ public DeleteDocumentView(dv: DocumentView) {
+ this._documentViews.delete(dv);
}
private static _instance: DocumentManager;
@@ -83,7 +89,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.add(view);
+ this.AddDocumentView(view);
}
this.callAddViewFuncs(view);
};
@@ -101,7 +107,7 @@ export class DocumentManager {
const index = this.LinkAnchorBoxViews.indexOf(view);
this.LinkAnchorBoxViews.splice(index, 1);
} else {
- this._documentViews.delete(view);
+ this.DeleteDocumentView(view);
}
SelectionManager.DeselectView(view);
});
@@ -228,7 +234,7 @@ export class DocumentManager {
public showDocumentView = async (targetDocView: DocumentView, options: DocFocusOptions) => {
const docViewPath = targetDocView.docViewPath.slice();
let rootContextView = docViewPath.shift();
- await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
+ await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
};
@@ -242,20 +248,22 @@ export class DocumentManager {
public showDocument = async (
targetDoc: Doc, // document to display
options: DocFocusOptions, // options for how to navigate to target
- finished?: () => void
+ finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
- let rootContextView = await new Promise<DocumentView>(res => {
- const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
- if (viewIndex !== -1) {
- viewIndex && docContextPath.splice(0, viewIndex);
- return res(this.getDocumentView(docContextPath[0])!);
- }
- options.didMove = true;
- docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
- this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
- });
+ let rootContextView =
+ docContextPath.length &&
+ (await new Promise<DocumentView>(res => {
+ const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
+ if (viewIndex !== -1) {
+ viewIndex && docContextPath.splice(0, viewIndex);
+ return res(this.getDocumentView(docContextPath[0])!);
+ }
+ options.didMove = true;
+ docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
+ }));
if (options.openLocation === OpenWhere.lightbox) {
// even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
const target = DocCast(targetDoc.annotationOn, targetDoc);
@@ -267,21 +275,29 @@ export class DocumentManager {
docContextPath.shift();
const childViewIterator = async (docView: DocumentView) => {
const innerDoc = docContextPath.shift();
- return { viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
+ return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined };
};
- const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
- this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
- finished?.();
+ if (rootContextView) {
+ const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+ finished?.(target.focused);
+ } else finished?.(false);
};
- focusViewsInPath = async (docView: DocumentView, options: DocFocusOptions, iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView> }>) => {
+ focusViewsInPath = async (
+ docView: DocumentView, //
+ options: DocFocusOptions,
+ iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
+ ) => {
let contextView: DocumentView | undefined; // view containing context that contains target
+ let focused = false;
while (true) {
docView.rootDoc.layout_fieldKey === 'layout_icon' ? await new Promise<void>(res => docView.iconify(res)) : undefined;
- docView.props.focus(docView.rootDoc, options); // focus the view within its container
+ const nextFocus = docView.props.focus(docView.rootDoc, options); // focus the view within its container
+ focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView };
+ if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.rootDoc, docView, contextView, focused };
contextView = docView;
docView = childDocView;
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 87833d322..85101fcab 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -15,42 +15,36 @@ import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-export type dropActionType = 'embed' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call removeDropProperties
+export type dropActionType = 'embed' | 'copy' | 'move' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
/**
* Initialize drag
* @param _reference: The HTMLElement that is being dragged
* @param docFunc: The Dash document being moved
- * @param moveFunc: The function called when the document is moved
- * @param dropAction: What to do with the document when it is dropped
- * @param dragStarted: Method to call when the drag is started
*/
-export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc | undefined> | undefined, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, dragStarted?: () => void) {
- const onRowMove = async (e: PointerEvent) => {
+export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | undefined) {
+ const onRowMove = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
- const doc = await docFunc();
+ const doc = docFunc();
if (doc) {
const dragData = new DragManager.DocumentDragData([doc]);
- dragData.dropAction = dropAction;
- dragData.moveDocument = moveFunc;
DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
- dragStarted?.();
}
};
const onRowUp = (): void => {
document.removeEventListener('pointermove', onRowMove);
document.removeEventListener('pointerup', onRowUp);
};
- const onItemDown = async (e: React.PointerEvent) => {
+ const onItemDown = (e: React.PointerEvent) => {
if (e.button === 0) {
e.stopPropagation();
if (e.shiftKey) {
e.persist();
- const dragDoc = await docFunc();
+ const dragDoc = docFunc();
dragDoc && DragManager.StartWindowDrag?.(e, [dragDoc]);
} else {
document.addEventListener('pointermove', onRowMove);
@@ -132,7 +126,7 @@ export namespace DragManager {
userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys
defaultDropAction?: dropActionType; // an optionally specified default drop action when there is no user drop actionl - this will be honored if there is no user drop action
dropAction: dropActionType; // a drop action request by the initiating code. the actual drop action may be different -- eg, if the request is 'embed', but the document is dropped within the same collection, the drop action will be switched to 'move'
- removeDropProperties?: string[];
+ dropPropertiesToRemove?: string[];
moveDocument?: MoveFunction;
removeDocument?: RemoveFunction;
isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
@@ -189,7 +183,7 @@ export namespace DragManager {
const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
const preDropHandler = (e: Event) => {
const de = (e as CustomEvent<DropEvent>).detail;
- (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.targetDropAction) as dropActionType);
+ (preDropFunc ?? defaultPreDropFunc)(e, de, StrCast(doc?.dropAction) as dropActionType);
};
element.addEventListener('dashOnDrop', handler);
doc && element.addEventListener('dashPreDrop', preDropHandler);
@@ -230,11 +224,13 @@ export namespace DragManager {
)
).filter(d => d);
!['same', 'proto'].includes(docDragData.dropAction as any) &&
- docDragData.droppedDocuments.forEach((drop: Doc, i: number) => {
- const dragProps = StrListCast(dragData.draggedDocuments[i].removeDropProperties);
- const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps));
- remProps.map(prop => (drop[prop] = undefined));
- });
+ docDragData.droppedDocuments
+ // .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any))
+ .forEach((drop: Doc, i: number) => {
+ const dragProps = StrListCast(dragData.draggedDocuments[i].dropPropertiesToRemove);
+ const remProps = (dragData?.dropPropertiesToRemove || []).concat(Array.from(dragProps));
+ [...remProps, 'dropPropertiesToRemove'].map(prop => (drop[prop] = undefined));
+ });
}
return e;
};
@@ -586,6 +582,7 @@ export namespace DragManager {
async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
const dropArgs = {
+ cancelable: true, // allows preventDefault() to be called to cancel the drop
bubbles: true,
detail: {
...pos,
@@ -598,8 +595,9 @@ export namespace DragManager {
},
};
target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
+ UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
await finishDrag?.(complete);
- target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs));
+ UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
options?.dragComplete?.(complete);
endDrag?.();
}
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index f46ea393a..47997cc5c 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -57,11 +57,11 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
let dbox = doc;
// bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes('FontIconBox')) {
- if (data.removeDropProperties || dbox.removeDropProperties) {
+ if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
//dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
dbox = Doc.MakeEmbedding(dbox);
- const dragProps = Cast(dbox.removeDropProperties, listSpec('string'), []);
- const remProps = (data.removeDropProperties || []).concat(Array.from(dragProps));
+ const dragProps = Cast(dbox.dropPropertiesToRemove, listSpec('string'), []);
+ const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
remProps.map(prop => (dbox[prop] = undefined));
}
} else if (!doc.onDragStart && !doc.isButtonBar) {
@@ -81,7 +81,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
icon: layoutDoc.isTemplateDoc ? 'font' : 'bolt',
});
dbox.dragFactory = layoutDoc;
- dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
+ dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
} else if (doc.isButtonBar) {
dbox.ignoreClick = true;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 3aa72f501..ce422f849 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -194,10 +194,8 @@ export class LinkManager {
public static getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined {
const a1 = Cast(linkDoc.link_anchor_1, Doc, null);
const a2 = Cast(linkDoc.link_anchor_2, Doc, null);
- if (Doc.AreProtosEqual(anchor, a1)) return a2;
- if (Doc.AreProtosEqual(anchor, a2)) return a1;
- if (Doc.AreProtosEqual(anchor, a1.annotationOn as Doc)) return a2;
- if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1;
+ if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a1.annotationOn, a1))) return a2;
+ if (Doc.AreProtosEqual(DocCast(anchor.annotationOn, anchor), DocCast(a2.annotationOn, a2))) return a1;
if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc;
}
}
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
new file mode 100644
index 000000000..0c41a1ea7
--- /dev/null
+++ b/src/client/util/PingManager.ts
@@ -0,0 +1,37 @@
+import { action, observable } from 'mobx';
+import { Networking } from '../Network';
+export class PingManager {
+ // create static instance and getter for global use
+ @observable static _instance: PingManager;
+ static get Instance(): PingManager {
+ return PingManager._instance;
+ }
+
+ @observable IsBeating = true;
+ private setIsBeating = action((status: boolean) => {
+ this.IsBeating = status;
+ setTimeout(this.showAlert, 100);
+ });
+
+ showAlert = () => {
+ alert(PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.');
+ };
+ sendPing = async (): Promise<void> => {
+ try {
+ await Networking.PostToServer('/ping', { date: new Date() });
+ !this.IsBeating && this.setIsBeating(true);
+ } catch {
+ if (this.IsBeating) {
+ this.setIsBeating(false);
+ }
+ }
+ };
+
+ // not used now, but may need to clear interval
+ private _interval: NodeJS.Timeout | null = null;
+ INTERVAL_SECONDS = 1;
+ constructor() {
+ PingManager._instance = this;
+ this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000);
+ }
+}
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index d4532c8ef..b59af6656 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,4 +1,5 @@
import { observable, action, runInAction } from 'mobx';
+import { Field } from '../../fields/Doc';
import { Without } from '../../Utils';
function getBatchName(target: any, key: string | symbol): string {
@@ -91,11 +92,11 @@ export namespace UndoManager {
let currentBatch: UndoBatch | undefined;
export let batchCounter = observable.box(0);
let undoing = false;
- let tempEvents: UndoEvent[] | undefined = undefined;
+ export let tempEvents: UndoEvent[] | undefined = undefined;
export function AddEvent(event: UndoEvent, value?: any): void {
if (currentBatch && batchCounter.get() && !undoing) {
- // console.log(' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + value);
+ console.log(' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + (value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value)));
currentBatch.push(event);
tempEvents?.push(event);
}
@@ -187,15 +188,11 @@ export namespace UndoManager {
return false;
});
- export function RunInTempBatch<T>(fn: () => T) {
+ export function StartTempBatch() {
tempEvents = [];
- try {
- const success = runInAction(fn);
- if (!success) UndoManager.UndoTempBatch();
- return success;
- } finally {
- tempEvents = undefined;
- }
+ }
+ export function EndTempBatch<T>(success: boolean) {
+ UndoManager.UndoTempBatch(success);
}
//TODO Make this return the return value
export function RunInBatch<T>(fn: () => T, batchName: string) {
@@ -206,10 +203,11 @@ export namespace UndoManager {
batch.end();
}
}
- export const UndoTempBatch = action(() => {
- if (tempEvents) {
+ export const UndoTempBatch = action((success: any) => {
+ if (tempEvents && !success) {
undoing = true;
for (let i = tempEvents.length - 1; i >= 0; i--) {
+ currentBatch?.includes(tempEvents[i]) && currentBatch.splice(currentBatch.indexOf(tempEvents[i]));
tempEvents[i].undo();
}
undoing = false;