aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts7
-rw-r--r--src/client/documents/Documents.ts59
-rw-r--r--src/client/goldenLayout.js2
-rw-r--r--src/client/util/CurrentUserUtils.ts147
-rw-r--r--src/client/util/DragManager.ts56
-rw-r--r--src/client/views/ContextMenu.tsx5
-rw-r--r--src/client/views/DocComponent.tsx8
-rw-r--r--src/client/views/DocumentButtonBar.tsx48
-rw-r--r--src/client/views/DocumentDecorations.tsx14
-rw-r--r--src/client/views/FieldsDropdown.tsx120
-rw-r--r--src/client/views/FilterPanel.tsx243
-rw-r--r--src/client/views/InkingStroke.tsx2
-rw-r--r--src/client/views/MainView.tsx6
-rw-r--r--src/client/views/OverlayView.tsx4
-rw-r--r--src/client/views/PropertiesButtons.tsx3
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx4
-rw-r--r--src/client/views/PropertiesView.tsx3
-rw-r--r--src/client/views/TemplateMenu.tsx2
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx12
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx15
-rw-r--r--src/client/views/collections/CollectionMenu.tsx4
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.scss1
-rw-r--r--src/client/views/collections/CollectionNoteTakingView.tsx107
-rw-r--r--src/client/views/collections/CollectionNoteTakingViewColumn.tsx22
-rw-r--r--src/client/views/collections/CollectionPileView.tsx3
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx73
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx23
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx55
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx24
-rw-r--r--src/client/views/collections/TabDocView.tsx10
-rw-r--r--src/client/views/collections/TreeView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx15
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx9
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx62
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx1
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx4
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx7
-rw-r--r--src/client/views/global/globalScripts.ts14
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx14
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.scss1
-rw-r--r--src/client/views/nodes/DocumentView.tsx74
-rw-r--r--src/client/views/nodes/EquationBox.tsx5
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx10
-rw-r--r--src/client/views/nodes/ImageBox.tsx1
-rw-r--r--src/client/views/nodes/VideoBox.tsx3
-rw-r--r--src/client/views/nodes/WebBox.tsx6
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx15
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx9
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx5
-rw-r--r--src/client/views/topbar/TopBar.tsx27
-rw-r--r--src/fields/Doc.ts11
-rw-r--r--src/fields/documentSchemas.ts6
53 files changed, 760 insertions, 629 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index e8bd35ac4..38325a463 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -902,6 +902,13 @@ export function setupMoveUpEvents(
document.addEventListener('click', _clickEvent, true);
}
+export function DivHeight(ele: HTMLElement): number {
+ return Number(getComputedStyle(ele).height.replace('px', ''));
+}
+export function DivWidth(ele: HTMLElement): number {
+ return Number(getComputedStyle(ele).width.replace('px', ''));
+}
+
export function dateRangeStrToDates(dateStr: string) {
// dateStr in yyyy-mm-dd format
const dateRangeParts = dateStr.split('|'); // splits into from and to date
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2d2f5fe4a..031560886 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -68,13 +68,24 @@ class EmptyBox {
return '';
}
}
+
+export enum FInfoFieldType {
+ string,
+ boolean,
+ number,
+ Doc,
+ enumeration,
+ date,
+ list,
+ rtf,
+}
export class FInfo {
description: string = '';
readOnly: boolean = false;
- fieldType?: string = '';
+ fieldType?: FInfoFieldType;
values?: Field[];
- filterable?: boolean = true;
+ filterable?: boolean = true; // can be used as a Filter in FilterPanel
// format?: string; // format to display values (e.g, decimal places, $, etc)
// parse?: ScriptField; // parse a value from a string
constructor(d: string, readOnly?: boolean) {
@@ -84,7 +95,7 @@ export class FInfo {
searchable = () => true;
}
class BoolInfo extends FInfo {
- fieldType? = 'boolean';
+ fieldType? = FInfoFieldType.boolean;
values?: boolean[] = [true, false];
constructor(d: string, filterable?: boolean) {
super(d);
@@ -93,7 +104,7 @@ class BoolInfo extends FInfo {
override searchable = () => false;
}
class NumInfo extends FInfo {
- fieldType? = 'number';
+ fieldType? = FInfoFieldType.number;
values?: number[] = [];
constructor(d: string, filterable?: boolean, readOnly?: boolean, values?: number[]) {
super(d, readOnly);
@@ -103,7 +114,7 @@ class NumInfo extends FInfo {
override searchable = () => false;
}
class StrInfo extends FInfo {
- fieldType? = 'string';
+ fieldType? = FInfoFieldType.string;
values?: string[] = [];
constructor(d: string, filterable?: boolean, readOnly?: boolean, values?: string[]) {
super(d, readOnly);
@@ -112,7 +123,7 @@ class StrInfo extends FInfo {
}
}
class DocInfo extends FInfo {
- fieldType? = 'Doc';
+ fieldType? = FInfoFieldType.Doc;
values?: Doc[] = [];
constructor(d: string, filterable?: boolean, values?: Doc[]) {
super(d, true);
@@ -122,45 +133,55 @@ class DocInfo extends FInfo {
override searchable = () => false;
}
class DimInfo extends FInfo {
- fieldType? = 'enumeration';
+ fieldType? = FInfoFieldType.enumeration;
values? = [DimUnit.Pixel, DimUnit.Ratio];
readOnly = false;
filterable = false;
override searchable = () => false;
}
class PEInfo extends FInfo {
- fieldType? = 'enumeration';
+ fieldType? = FInfoFieldType.enumeration;
values? = ['all', 'none'];
readOnly = false;
filterable = false;
override searchable = () => false;
}
class DAInfo extends FInfo {
- fieldType? = 'enumeration';
- values? = ['embed', 'copy', 'move', 'same', 'proto', 'none'];
+ fieldType? = FInfoFieldType.enumeration;
+ values? = ['embed', 'copy', 'move', 'same', 'add', 'inSame', 'proto'];
readOnly = false;
filterable = false;
override searchable = () => false;
}
class CTypeInfo extends FInfo {
- fieldType? = 'enumeration';
+ fieldType? = FInfoFieldType.enumeration;
values? = Array.from(Object.keys(CollectionViewType));
readOnly = false;
filterable = false;
override searchable = () => false;
}
class DTypeInfo extends FInfo {
- fieldType? = 'enumeration';
+ fieldType? = FInfoFieldType.enumeration;
values? = Array.from(Object.keys(DocumentType));
override searchable = () => false;
}
class DateInfo extends FInfo {
- fieldType? = 'date';
+ constructor(d: string, filterable?: boolean) {
+ super(d, true);
+ this.filterable = filterable;
+ }
+ fieldType? = FInfoFieldType.date;
values?: DateField[] = [];
- filterable = true;
+}
+class RtfInfo extends FInfo {
+ constructor(d: string, filterable?: boolean) {
+ super(d, true);
+ this.filterable = filterable;
+ }
+ fieldType? = FInfoFieldType.rtf;
}
class ListInfo extends FInfo {
- fieldType? = 'list';
+ fieldType? = FInfoFieldType.list;
values?: List<any>[] = [];
}
type BOOLt = BoolInfo | boolean;
@@ -168,6 +189,7 @@ type NUMt = NumInfo | number;
type STRt = StrInfo | string;
type LISTt = ListInfo | List<any>;
type DOCt = DocInfo | Doc;
+type RTFt = RtfInfo | RichTextField;
type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio;
type PEVt = PEInfo | 'none' | 'all';
type COLLt = CTypeInfo | CollectionViewType;
@@ -181,6 +203,7 @@ export class DocumentOptions {
z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, false, [1, 0]);
overlayX?: NUMt = new NumInfo('x coordinate of document in a overlay view', false);
overlayY?: NUMt = new NumInfo('y coordinate of document in a overlay view', false);
+ text?: RTFt = new RtfInfo('rich text of a text doc', true);
_dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false);
_dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units");
latitude?: NUMt = new NumInfo('latitude coordinate for map views', false);
@@ -257,6 +280,7 @@ export class DocumentOptions {
_layout_reflowVertical?: BOOLt = new BoolInfo('native height can be changed independent of width by dragging decoration resizers');
_layout_reflowHorizontal?: BOOLt = new BoolInfo('whether a doc with a native size can be horizonally resized, causing some form of reflow');
layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow
+ layout_maxShown?: NUMt = new NumInfo('maximum number of children to display at one time (see multicolumnview)');
_layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents');
_layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false);
_layout_currentTimecode?: NUMt = new NumInfo('the current timecode of a time-based document (e.g., current time of a video) value is in seconds', false);
@@ -459,7 +483,6 @@ export class DocumentOptions {
sidebar_type_collection?: string; // collection type of text sidebar
data_dashboards?: List<any>; // list of dashboards used in shareddocs;
- text?: string;
textTransform?: string;
letterSpacing?: string;
iconTemplate?: string; // name of icon template style
@@ -728,7 +751,7 @@ export namespace Docs {
{
data: '',
layout: { view: ComparisonBox, dataField: defaultDataKey },
- options: { backgroundColor: 'gray', dropAction: 'move', waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
+ options: { backgroundColor: 'gray', dropAction: dropActionType.move, waitForDoubleClickToClick: 'always', layout_reflowHorizontal: true, layout_reflowVertical: true, layout_nativeDimEditable: true, systemIcon: 'BsLayoutSplit' },
},
],
[
@@ -1182,7 +1205,7 @@ export namespace Docs {
return InstanceFromProto(
Prototypes.get(DocumentType.COL),
new List(documents),
- { backgroundColor: 'transparent', dropAction: 'move', _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
+ { backgroundColor: 'transparent', dropAction: dropActionType.move, _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Pile },
id
);
}
diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js
index 2b94d35ee..cb1dfd76a 100644
--- a/src/client/goldenLayout.js
+++ b/src/client/goldenLayout.js
@@ -4727,7 +4727,7 @@
*/
} else {
type = isVertical ? 'column' : 'row';
- if (this.parent.contentItems.length === 1) {
+ if (this.parent.contentItems.length === 1 && this.contentItems.length === 1) {
let grandparent = this.parent.parent;
let correctRowOrCol = this.layoutManager.createContentItem({ type: type }, this);
grandparent.replaceChild(this.parent, correctRowOrCol);
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 714e33d25..d75c717c9 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -2,6 +2,7 @@ import { observable, reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
import { OmitKeys, Utils } from "../../Utils";
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
+import { DocData } from "../../fields/DocSymbols";
import { InkTool } from "../../fields/InkField";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
@@ -14,7 +15,7 @@ import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { GestureUtils } from "../../pen-gestures/GestureUtils";
import { DocServer } from "../DocServer";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
-import { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents";
+import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
import { DashboardView } from "../views/DashboardView";
import { OverlayView } from "../views/OverlayView";
import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
@@ -234,6 +235,95 @@ export class CurrentUserUtils {
MakeTemplate(Doc.GetProto(slide), true, "Untitled Slide View");
return slide;
}
+ const plotlyView = (opts:DocumentOptions) => {
+ var plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
+ if (!plotly) {
+ const plotly = Docs.Create.TextDocument(
+ `await import("https://cdn.plot.ly/plotly-2.27.0.min.js");
+ Plotly.newPlot(dashDiv.id, [ --DOCDATA-- ])`
+ , {title: "@plotly", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
+ Doc.AddToMyPublished(plotly);
+ }
+ const rtfield = new RichTextField(JSON.stringify(
+ {doc: {type:"doc",content:[
+ {type:"code_block",content:[
+ {type:"text",text:"^@plotly"},
+ {type:"text",text:"\n"},
+ {type:"text",text:"\n{"},
+ {type:"text",text:"\n x: [1,2,3,5,19],"},
+ {type:"text",text:"\n y: [1, 9, 15, 12,3],"},
+ {type:"text",text:"\n mode: 'lines+markers', "},
+ {type:"text",text:"\n type: 'scatter'"},
+ {type:"text",text:"\n}"}
+ ]}
+ ]},
+ selection:{type:"text",anchor:2,head:2}}),
+ `^@plotly
+ {
+ x: [1,2,3,5,19],
+ y: [1, 9, 15, 12,3],
+ mode: 'lines+markers',
+ type: 'scatter'
+ }`);
+ const slide = Docs.Create.TextDocument("", opts);
+ slide[DocData].text = rtfield;
+ slide[DocData].layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
+ slide[DocData]._type_collection = CollectionViewType.Freeform;
+ slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`);
+ return slide;
+ }
+ const mermaidsView = (opts:DocumentOptions) => {
+ var mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
+ if (!mermaids) {
+ const mermaids = Docs.Create.TextDocument(
+ `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid\@10.8.0/dist/mermaid.esm.min.mjs")).default;
+ window["callb"] = (x) => {
+ alert(x);
+ }
+ mdef.initialize({
+ securityLevel : "loose",
+ startOnLoad: true,
+ flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
+ });
+ const mermaid = async (str) => (await mdef.render("graph"+Date.now(),str));
+ const {svg, bindFunctions} = await mermaid(\`--DOCDATA--\`);
+ dashDiv.innerHTML = svg;
+ if (bindFunctions) {
+ bindFunctions(dashDiv);
+ }`
+ , {title: "@mermaids", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
+ Doc.AddToMyPublished(mermaids);
+ }
+ const rtfield = new RichTextField(JSON.stringify(
+ {doc: {type:"doc",content:[
+ {type:"code_block",content:[
+ {type:"text",text:"^@mermaids"},
+ {type:"text",text:"\n\n"},
+ {type:"text",text:"pie "},
+ {type:"text",text:"title"},
+ {type:"text",text:" "},
+ {type:"text",text:"Minerals in my tap water"},
+ {type:"text",text:"\n \"Calcium\" : "},
+ {type:"dashField",attrs:{fieldKey:"calcium",docId:"","hideKey":false,editable:true}},
+ {type:"text",text:"\n \"Potassium\" : "},
+ {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,editable:true}},
+ {type:"text",text:"\n \"Magnesium\" : 10.01"}
+ ]}
+ ]},
+ selection:{type:"text",anchor:109,head:109}
+ }),
+ `^@mermaids
+pie title Minerals in my tap water
+ "Calcium" : 42.96
+ "Potassium" : 50
+ "Magnesium" : 10.01`);
+ const slide = Docs.Create.TextDocument("", opts);
+ slide[DocData].text = rtfield;
+ slide[DocData].layout_textPainted = `<CollectionView {...props} fieldKey={'text'}/>`;
+ slide[DocData]._type_collection = CollectionViewType.Freeform;
+ slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`);
+ return slide;
+ }
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
@@ -257,14 +347,16 @@ export class CurrentUserUtils {
{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, treeView_HideUnrendered: true}},
{key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
- {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: "embed" as dropActionType, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }},
+ {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth: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,
treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true,
- dropAction:'move', treeView_Type: TreeViewType.outline,
+ dropAction:dropActionType.move, treeView_Type: TreeViewType.outline,
backgroundColor: "white", _xMargin: 0, _yMargin: 0, _createDocOnCR: true
}, funcs: {title: 'this.text?.Text'}},
- ];
+ {key: "Mermaids", creator: mermaidsView, opts: { _width: 300, _height: 300, }},
+ {key: "Plotly", creator: plotlyView, opts: { _width: 300, _height: 300, }},
+ ];
emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, thing.scripts, thing.funcs));
@@ -272,6 +364,8 @@ export class CurrentUserUtils {
{ toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, clickFactory: DocCast(doc.emptyNote)},
{ toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)},
{ toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)},
+ { toolTip: "Tap or drag to create a mermaid node", title: "Mermaids", icon: "rocket", dragFactory: doc.emptyMermaids as Doc, clickFactory: DocCast(doc.emptyMermaids)},
+ { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)},
{ toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)},
{ toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)},
@@ -311,7 +405,7 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
title: "Document 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,
- childDragAction: 'embed'
+ childDragAction: dropActionType.embed
};
const reqdScripts = { dropConverter: "convertToButtons(dragData)" };
return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts);
@@ -353,7 +447,7 @@ export class CurrentUserUtils {
});
const reqdStackOpts:DocumentOptions ={
- title: "menuItemPanel", childDragAction: "same", layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
+ title: "menuItemPanel", childDragAction: dropActionType.same, layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true,
_chromeHidden: true, _gridGap: 0, _yMargin: 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)" });
@@ -436,14 +530,13 @@ 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, childDragAction: "embed",
+ dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: dropActionType.embed,
_lockedPosition: true, _type_collection: CollectionViewType.Schema });
}
/// 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 allTools = DocListCast(myTools?.data);
+ const allTools = DocListCast(DocCast(doc[field])?.data);
const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
@@ -476,8 +569,8 @@ export class CurrentUserUtils {
const childContextMenuIcons = ["tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters
const reqdOpts:DocumentOptions = {
title: "My Dashboards", childHideLinkButton: true, treeView_FreezeChildren: "remove|add", treeView_HideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- dropAction: "inSame", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true,
- layout_headerButton: newDashboardButton, childDragAction: "inSame",
+ dropAction: dropActionType.inPlace, treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true,
+ layout_headerButton: newDashboardButton, childDragAction: dropActionType.inPlace,
_layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true,
contextMenuLabels:new List<string>(contextMenuLabels),
contextMenuIcons:new List<string>(contextMenuIcons),
@@ -510,9 +603,9 @@ export class CurrentUserUtils {
const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript);
const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true,
- title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true,
+ title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: dropActionType.add, isSystem: true,
isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
- treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
+ treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: dropActionType.embed,
layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
};
const fileFolders = new Set(DocListCast(DocCast(doc[field])?.data));
@@ -522,8 +615,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, isFolder: true,
- title: "My Recently Closed", childHideLinkButton: true, treeView_HideTitle: true, childDragAction: "move", isSystem: true,
- treeView_TruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: "same",
+ title: "My Recently Closed", childHideLinkButton: true, treeView_HideTitle: true, childDragAction: dropActionType.move, isSystem: true,
+ treeView_TruncateTitleWidth: 350, ignoreClick: true, layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.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."
@@ -546,7 +639,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, dropAction: "same", ignoreClick: true, isSystem: true,
+ layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same, ignoreClick: true, isSystem: true,
treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
};
if (!doc[field]) DocUtils.AssignOpts(doc, {treeView_Open: true, treeView_ExpandedView: "fields" });
@@ -587,7 +680,7 @@ export class CurrentUserUtils {
];
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", childDragAction: 'move',
+ title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.move,
childDontRegisterViews: true, linearView_IsOpen: 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 });
@@ -611,9 +704,7 @@ export class CurrentUserUtils {
return [
{ title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
{ title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- { title: "View All", icon: "object-group", toolTip: "Keep all Docs in View",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
- // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true,
- { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit Docs to View (once)",btnType: ButtonType.ClickButton,ignoreClick:false,expertMode: false, toolType:"fitOnce", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+ { title: "Fit All", icon: "object-group", toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform
{ title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
{ title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
{ title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
@@ -743,7 +834,7 @@ 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", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 };
+ const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List<string>(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: dropActionType.embed, childDontRegisterViews: true, linearView_IsOpen: 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 => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) );
return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns);
@@ -769,7 +860,7 @@ export class CurrentUserUtils {
];
const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs));
const dockBtnsReqdOpts:DocumentOptions = {
- title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move',
+ title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.move,
childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: false, ignoreClick: true
};
return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
@@ -809,7 +900,7 @@ export class CurrentUserUtils {
// childContextMenuLabels: new List<string>(["Add to Dashboards",]),
// childContextMenuIcons: new List<string>(["user-plus",]),
"acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment,
- childDragAction: "embed", isSystem: true, childContentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
+ childDragAction: dropActionType.embed, isSystem: true, childContentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
// NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
_layout_showTitle: "title", treeView_HideTitle: 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'"
@@ -824,7 +915,7 @@ export class CurrentUserUtils {
const reqdOpts:DocumentOptions = {
title: "My Imports", _forceActive: true, _layout_showTitle: "title", childLayoutString: ImportElementBox.LayoutString('data'),
_dragOnlyWithinContainer: true, _layout_hideContextMenu: true, childLimitHeight: 0, onClickScriptDisable:"never",
- childDragAction: "copy", _layout_autoHeight: true, _yMargin: 50, _gridGap: 15, layout_boxShadow: "0 0", _lockedPosition: true, isSystem: true, _chromeHidden: true,
+ childDragAction: dropActionType.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.MasonryDocument([], opts), reqdOpts, undefined, {onClick: "deselectAll()"});
@@ -886,7 +977,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: "My Header Bar", isSystem: true, _chromeHidden:true, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: 'move'}); // drop down panel at top of dashboard for stashing documents
+ DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, layout_maxShown: 10, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: dropActionType.move}); // drop down panel at top of dashboard for stashing documents
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
@@ -909,9 +1000,9 @@ export class CurrentUserUtils {
const options = pair[1] as FInfo;
const opts:DocumentOptions = { isSystem: true, title: pair[0], ...OmitKeys(options, ["values"]).omit, fieldIsLayout: pair[0].startsWith("_")};
switch (options.fieldType) {
- case "boolean": opts.fieldValues = new List<boolean>(options.values as any); break;
- case "number": opts.fieldValues = new List<number>(options.values as any); break;
- case Doc.name: opts.fieldValues = new List<Doc>(options.values as any); break;
+ case FInfoFieldType.boolean: opts.fieldValues = new List<boolean>(options.values as any); break;
+ case FInfoFieldType.number: opts.fieldValues = new List<number>(options.values as any); break;
+ case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break;
default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
}
DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1f093a33c..aa0f77c72 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,10 +1,24 @@
+/**
+ * The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc)
+ *
+ * Events are generated for
+ * a pause in the drag movement (dashDragMovePause) as a Doc(s) is dragged,
+ * just before (dashPreDrop) a Doc(s) is dropped,
+ * and just after (dashDropEvent) a Doc(s) is dropped
+ * If the document is dragged and paused over the golden layout header tabs, the
+ * drag interaction will switch to a golden layout tab drag.
+ *
+ * All drag operations can be aborted by hitting the Esc key
+ *
+ */
+
import { action, observable, runInAction } from 'mobx';
import { DateField } from '../../fields/DateField';
import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
-import { ScriptCast, StrCast } from '../../fields/Types';
+import { ScriptCast } from '../../fields/Types';
import { emptyFunction, Utils } from '../../Utils';
import { Docs, DocUtils } from '../documents/Documents';
import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
@@ -16,7 +30,15 @@ import { UndoManager } from './UndoManager';
import { DocData } from '../../fields/DocSymbols';
const { default : { contextMenuZindex } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
-export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove
+export enum dropActionType {
+ embed = 'embed', // create a new embedding of the dragged document for the new location
+ copy = 'copy', // copy the dragged document
+ move = 'move', // move the dragged document to the drop location after removing it from where it was
+ add = 'add', // add the dragged document to the drop location without removing it from where it was
+ same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
+ inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
+ proto = 'proto',
+} // undefined = move, same = move but doesn't call dropPropertiesToRemove
/**
* Initialize drag
@@ -79,8 +101,6 @@ export namespace DragManager {
export interface DragOptions {
dragComplete?: (e: DragCompleteEvent) => void; // function to invoke when drag has completed
hideSource?: boolean; // hide source document during drag
- offsetX?: number; // offset of top left of source drag visual from cursor
- offsetY?: number;
noAutoscroll?: boolean;
}
@@ -129,9 +149,9 @@ export namespace DragManager {
treeViewDoc?: Doc;
offset: number[];
canEmbed?: boolean;
- userDropAction: dropActionType; // the user requested drop action -- this will be honored as specified by modifier keys
+ 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'
+ 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'
dropPropertiesToRemove?: string[];
moveDocument?: MoveFunction;
removeDocument?: RemoveFunction;
@@ -170,8 +190,8 @@ export namespace DragManager {
dropDocCreator: (annotationOn: Doc | undefined) => Doc;
dropDocument?: Doc;
offset: number[];
- dropAction: dropActionType;
- userDropAction: dropActionType;
+ dropAction?: dropActionType;
+ userDropAction?: dropActionType;
}
let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
@@ -189,7 +209,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.dropAction) as dropActionType);
+ (preDropFunc ?? defaultPreDropFunc)(e, de, doc.dropAction as any as dropActionType);
};
element.addEventListener('dashOnDrop', handler);
element.addEventListener('dashPreDrop', preDropHandler);
@@ -218,19 +238,19 @@ export namespace DragManager {
dragData.draggedDocuments.map(async d =>
!dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart)
? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result)
- : docDragData.dropAction === 'embed'
+ : docDragData.dropAction === dropActionType.embed
? Doc.BestEmbedding(d)
- : docDragData.dropAction === 'add'
+ : docDragData.dropAction === dropActionType.add
? d
- : docDragData.dropAction === 'proto'
+ : docDragData.dropAction === dropActionType.proto
? d[DocData]
- : docDragData.dropAction === 'copy'
+ : docDragData.dropAction === dropActionType.copy
? (await Doc.MakeClone(d)).clone
: d
)
)
).filter(d => d);
- !['same', 'proto'].includes(docDragData.dropAction as any) &&
+ ![dropActionType.same, dropActionType.proto].includes(docDragData.dropAction as any) &&
docDragData.droppedDocuments
// .filter(drop => !drop.dragOnlyWithinContainer || ['embed', 'copy'].includes(docDragData.dropAction as any))
.forEach((drop: Doc, i: number) => {
@@ -504,7 +524,7 @@ export namespace DragManager {
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.shiftKey ? 'move' : e.ctrlKey ? 'embed' : dragData.defaultDropAction;
+ dragData.userDropAction = e.ctrlKey && e.altKey ? dropActionType.copy : e.shiftKey ? dropActionType.move : e.ctrlKey ? dropActionType.embed : dragData.defaultDropAction;
}
if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(typeof (e.target as any).className === 'string' ? (e.target as any)?.className : '') && dragData.draggedDocuments.length === 1) {
if (!startWindowDragTimer) {
@@ -530,7 +550,7 @@ export namespace DragManager {
if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._freeform_noAutoPan)) {
const autoScrollHandler = () => {
target.dispatchEvent(
- new CustomEvent<React.DragEvent>('dashDragAutoScroll', {
+ new CustomEvent<React.DragEvent>('dashDragMovePause', {
bubbles: true,
detail: {
shiftKey: e.shiftKey,
@@ -552,7 +572,7 @@ export namespace DragManager {
screenY: e.screenY,
detail: e.detail,
view: e.view ? e.view : (new Window() as any),
- nativeEvent: new DragEvent('dashDragAutoScroll'),
+ nativeEvent: new DragEvent('dashDragMovePause'),
currentTarget: target,
target: target,
bubbles: true,
@@ -566,7 +586,7 @@ export namespace DragManager {
isPropagationStopped: () => ('not implemented for this event' ? false : false),
persist: emptyFunction,
timeStamp: e.timeStamp,
- type: 'dashDragAutoScroll',
+ type: 'dashDragMovePause',
},
})
);
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8c3c9df2e..8f4e43978 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -7,6 +7,7 @@ import { SettingsManager } from '../util/SettingsManager';
import './ContextMenu.scss';
import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
import { ObservableReactComponent } from './ObservableReactComponent';
+import { DivHeight, DivWidth } from '../../Utils';
@observer
export class ContextMenu extends ObservableReactComponent<{}> {
@@ -214,8 +215,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
className="contextMenu-cont"
ref={action((r: any) => {
if (r) {
- this._width = Number(getComputedStyle(r).width.replace('px', ''));
- this._height = Number(getComputedStyle(r).height.replace('px', ''));
+ this._width = DivWidth(r);
+ this._height = DivHeight(r);
}
})}
style={{
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 3d5a5b945..2a527eca1 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -187,14 +187,14 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
const toRemove = value.filter(v => docs.includes(v));
if (toRemove.length !== 0) {
- const recent = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
+ const recentlyClosed = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined;
toRemove.forEach(doc => {
leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
- Doc.RemoveEmbedding(doc, doc);
doc.embedContainer = undefined;
- if (recent && !dontAddToRemoved) {
- doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
+ if (recentlyClosed && !dontAddToRemoved && doc.type !== DocumentType.LOADING) {
+ Doc.AddDocToList(recentlyClosed, 'data', doc, undefined, true, true);
+ Doc.RemoveEmbedding(doc, doc);
}
});
if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) {
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 8a4b42ae0..d65e0b406 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -4,12 +4,12 @@ import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
import { Doc } from '../../fields/Doc';
import { Cast, DocCast } from '../../fields/Types';
import { DocUtils } from '../documents/Documents';
import { CalendarManager } from '../util/CalendarManager';
-import { DragManager } from '../util/DragManager';
+import { DragManager, dropActionType } from '../util/DragManager';
import { IsFollowLinkScript } from '../util/LinkFollower';
import { SelectionManager } from '../util/SelectionManager';
import { SharingManager } from '../util/SharingManager';
@@ -24,6 +24,9 @@ import { DocumentView, DocumentViewInternal, OpenWhere } from './nodes/DocumentV
import { DashFieldView } from './nodes/formattedText/DashFieldView';
import { PinProps } from './nodes/trails';
import { faCalendarDays } from '@fortawesome/free-solid-svg-icons';
+import { Popup } from 'browndash-components';
+import { TemplateMenu } from './TemplateMenu';
+import { FaEdit } from 'react-icons/fa';
@observer
export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: any }> {
@@ -306,7 +309,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
const dragDocView = this.view0!;
const dragData = new DragManager.DocumentDragData([dragDocView.Document]);
const [left, top] = dragDocView.screenToContentsTransform().inverse().transformPoint(0, 0);
- dragData.defaultDropAction = 'embed';
+ dragData.defaultDropAction = dropActionType.embed;
dragData.canEmbed = true;
DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false });
return true;
@@ -316,35 +319,24 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
_ref = React.createRef<HTMLDivElement>();
@observable _tooltipOpen: boolean = false;
+ @computed get templateMenu() {
+ return (
+ <div ref={this._ref}>
+ <TemplateMenu
+ docViews={this._props
+ .views()
+ .filter(v => v)
+ .map(v => v as DocumentView)}
+ />
+ </div>
+ );
+ }
@computed
get templateButton() {
- const view0 = this.view0;
- const views = this._props.views();
- return !view0 ? null : (
+ return !this.view0 ? null : (
<Tooltip title={<div className="dash-tooltip">Tap to Customize Layout. Drag an embedding</div>} open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom">
<div className="documentButtonBar-linkFlyout" ref={this._dragRef} onPointerEnter={action(() => !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}>
- {
- /* <Flyout
- anchorPoint={anchorPoints.LEFT_TOP}
- onOpen={action(() => (this._embedDown = true))}
- onClose={action(() => (this._embedDown = false))}
- content={
- !this._embedDown ? null : (
- <div ref={this._ref}>
- {' '}
- <TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} />
- </div>
- )
- }>
- <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />
- </div>
- </Flyout> */
-
- <div className={'documentButtonBar-linkButton-empty'} ref={this._dragRef} onPointerDown={this.onTemplateButton}>
- <FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />
- </div>
- }
+ <Popup icon={<FaEdit />} popup={this.templateMenu} popupContainsPt={returnTrue} />
</div>
</Tooltip>
);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index e9c4d9cc5..87ee962a0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,7 +77,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
const {x,y} = Utils.rotPt(e.clientX - center.x,
e.clientY - center.y,
NumCast(SelectionManager.Views.lastElement()?.screenToViewTransform().Rotate));
- (this._showNothing = !(this.Bounds.x !== Number.MAX_VALUE && //
+ (this._showNothing = !DocumentButtonBar.Instance?._tooltipOpen && !(this.Bounds.x !== Number.MAX_VALUE && //
(this.Bounds.x > center.x+x || this.Bounds.r < center.x+x ||
this.Bounds.y > center.y+y || this.Bounds.b < center.y+y )));
})); // prettier-ignore
@@ -514,7 +514,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
//
// determines if anything being dragged directly or via a group has a fixed aspect ratio (in which case we resize uniformly)
//
- hasFixedAspect = (doc: Doc): boolean => (doc.isGroup ? DocListCast(doc.data).some(this.hasFixedAspect) : !BoolCast(doc.layout_nativeDimEditable));
+ hasFixedAspect = (doc: Doc): boolean => (doc.isGroup ? DocListCast(doc.data).some(this.hasFixedAspect) : !BoolCast(doc._layout_nativeDimEditable));
//
// resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc
@@ -562,7 +562,13 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
doc.y = NumCast(doc.y) + deltaY;
doc._layout_modificationDate = new DateField();
- scale.y !== 1 && (doc._layout_autoHeight = undefined);
+ if (scale.y !== 1) {
+ docView.layoutDoc._layout_autoHeight = undefined;
+ if (docView.layoutDoc._layout_autoHeight) {
+ // if autoHeight is still on because of a prototype
+ docView.layoutDoc._layout_autoHeight = false; // then don't inherit, but explicitly set it to false
+ }
+ }
}
};
@@ -597,7 +603,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
this._resizeUndo?.end();
// detect layout_autoHeight gesture and apply
- SelectionManager.Docs.forEach(doc => NumCast(doc._height) < 20 && (doc._layout_autoHeight = true));
+ SelectionManager.Views.forEach(view => NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true));
//need to change points for resize, or else rotation/control points will fail.
this._inkDragDocs
.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] }))
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
new file mode 100644
index 000000000..5638d34c6
--- /dev/null
+++ b/src/client/views/FieldsDropdown.tsx
@@ -0,0 +1,120 @@
+/**
+ * This creates a dropdown menu that's populated with possible field key names (e.g., author, tags)
+ *
+ * The set of field names actually displayed is based on searching the prop 'Document' and its descendants :
+ * The field list will contain all of the fields within the prop Document and all of its children;
+ * this list is then pruned down to only include fields that are not marked in Documents.ts to be non-filterable
+ */
+
+import { computed, makeObservable, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import Select from 'react-select';
+import { Doc } from '../../fields/Doc';
+import { DocOptions, FInfo } from '../documents/Documents';
+import { SearchUtil } from '../util/SearchUtil';
+import { SettingsManager } from '../util/SettingsManager';
+import './FilterPanel.scss';
+import { ObservableReactComponent } from './ObservableReactComponent';
+
+interface fieldsDropdownProps {
+ Document: Doc; // show fields for this Doc if set, otherwise for all docs in dashboard
+ selectFunc: (value: string) => void;
+ menuClose?: () => void;
+ placeholder?: string | (() => string);
+ showPlaceholder?: true; // if true, then input field always shows the placeholder value; otherwise, it shows the current selection
+ addedFields?: string[];
+}
+
+@observer
+export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps> {
+ @observable _newField = '';
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ }
+
+ @computed get allDescendantDocs() {
+ const allDocs = new Set<Doc>();
+ SearchUtil.foreachRecursiveDoc([this._props.Document], (depth, doc) => allDocs.add(doc));
+ return Array.from(allDocs);
+ }
+
+ @computed get fieldsOfDocuments() {
+ const keys = new Set<string>();
+ this.allDescendantDocs.forEach(doc => SearchUtil.documentKeys(doc).filter(key => keys.add(key)));
+ const sortedKeys = Array.from(keys.keys())
+ .filter(key => key[0])
+ .filter(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || !Doc.noviceMode)
+ .sort();
+
+ Array.from(keys).forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1));
+
+ return [...Array.from(keys), ...sortedKeys];
+ }
+
+ render() {
+ const filteredOptions = ['author', ...(this._newField ? [this._newField] : []), ...(this._props.addedFields ?? []), ...this.fieldsOfDocuments.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];
+
+ Object.entries(DocOptions)
+ .filter(opts => opts[1].filterable)
+ .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0]));
+ const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));
+
+ console.log(options);
+ return (
+ <Select
+ styles={{
+ control: (baseStyles, state) => ({
+ ...baseStyles,
+ minHeight: '5px',
+ maxHeight: '30px',
+ color: SettingsManager.userColor,
+ backgroundColor: SettingsManager.userBackgroundColor,
+ padding: 0,
+ margin: 0,
+ }),
+ singleValue: (baseStyles, state) => ({
+ ...baseStyles,
+ color: SettingsManager.userColor,
+ background: SettingsManager.userBackgroundColor,
+ }),
+ placeholder: (baseStyles, state) => ({
+ ...baseStyles,
+ color: SettingsManager.userColor,
+ background: SettingsManager.userBackgroundColor,
+ }),
+ input: (baseStyles, state) => ({
+ ...baseStyles,
+ padding: 0,
+ margin: 0,
+ color: SettingsManager.userColor,
+ background: 'transparent',
+ }),
+ option: (baseStyles, state) => ({
+ ...baseStyles,
+ color: SettingsManager.userColor,
+ background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
+ }),
+ menuList: (baseStyles, state) => ({
+ ...baseStyles,
+ backgroundColor: SettingsManager.userBackgroundColor,
+ }),
+ }}
+ placeholder={typeof this._props.placeholder === 'string' ? this._props.placeholder : this._props.placeholder?.()}
+ options={options as any}
+ isMulti={false}
+ onChange={val => this._props.selectFunc((val as any as { value: string; label: string }).value)}
+ onKeyDown={e => {
+ if (e.key === 'Enter') {
+ runInAction(() => this._props.selectFunc((this._newField = (e.nativeEvent.target as any)?.value)));
+ }
+ e.stopPropagation();
+ }}
+ onMenuClose={this._props.menuClose}
+ closeMenuOnSelect={true}
+ value={this._props.showPlaceholder ? null : undefined}
+ />
+ );
+ }
+}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index 818c81c9a..0521c4a4b 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -4,19 +4,16 @@ import * as React from 'react';
import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
import { CiCircleRemove } from 'react-icons/ci';
-import Select from 'react-select';
import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
-import { DocOptions, FInfo } from '../documents/Documents';
import { DocumentManager } from '../util/DocumentManager';
-import { UserOptions } from '../util/GroupManager';
import { SearchUtil } from '../util/SearchUtil';
import { SettingsManager } from '../util/SettingsManager';
import { undoable } from '../util/UndoManager';
+import { FieldsDropdown } from './FieldsDropdown';
import './FilterPanel.scss';
-import { FieldView } from './nodes/FieldView';
import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
import { ObservableReactComponent } from './ObservableReactComponent';
@@ -26,9 +23,7 @@ interface filterProps {
@observer
export class FilterPanel extends ObservableReactComponent<filterProps> {
- public static LayoutString(fieldKey: string) {
- return FieldView.LayoutString(FilterPanel, fieldKey);
- }
+ @observable _selectedFacetHeaders = new Set<string>();
constructor(props: any) {
super(props);
@@ -38,45 +33,19 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
/**
* @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
*/
- get targetDoc() {
+ get Document() {
return this._props.Document;
}
@computed get targetDocChildKey() {
- const targetView = DocumentManager.Instance.getFirstDocumentView(this.targetDoc);
+ const targetView = DocumentManager.Instance.getFirstDocumentView(this.Document);
return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data';
}
@computed get targetDocChildren() {
- return [...DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.targetDoc[Doc.LayoutFieldKey(this.targetDoc) + '_sidebar'])];
- }
-
- @computed get allDocs() {
- const allDocs = new Set<Doc>();
- const targetDoc = this.targetDoc;
- if (targetDoc) {
- SearchUtil.foreachRecursiveDoc([this.targetDoc], (depth, doc) => allDocs.add(doc));
- }
- return Array.from(allDocs);
- }
-
- @computed get _allFacets() {
- const noviceReqFields = ['author', 'tags', 'text', 'type', LinkedTo];
- const noviceLayoutFields: string[] = []; //["_layout_curPage"];
- const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
-
- const keys = new Set<string>(noviceFields);
- this.allDocs.forEach(doc => SearchUtil.documentKeys(doc).filter(key => keys.add(key)));
- const sortedKeys = Array.from(keys.keys())
- .filter(key => key[0])
- .filter(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
- .sort();
-
- noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1));
-
- return [...noviceFields, ...sortedKeys];
+ return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_sidebar'])];
}
@computed get rangeFilters() {
- return StrListCast(this.targetDoc?._childFiltersByRanges).filter((filter, i) => !(i % 3));
+ return StrListCast(this.Document?._childFiltersByRanges).filter((filter, i) => !(i % 3));
}
/**
@@ -84,7 +53,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
* ["#tags::bob::check", "tags::joe::check", "width", "height"]
*/
@computed get activeFilters() {
- return StrListCast(this.targetDoc?._childFilters).concat(this.rangeFilters);
+ return StrListCast(this.Document?._childFilters).concat(this.rangeFilters);
}
@computed get mapActiveFiltersToFacets() {
@@ -100,32 +69,27 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// this wants to return all the filter facets that have an existing filter set on them in order to show them in the rendered panel
// this set may overlap the selectedFilters
// if the components reloads, these will still exist and be shown
-
+ //
// ["#tags", "width", "height"]
//
-
@computed get activeFacetHeaders() {
const activeHeaders = new Array();
this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0]));
return activeHeaders;
}
- /**
- * @returns a string array of the current attributes
- */
- // @computed get currentFacets() {
- // return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]);
- // }
static gatherFieldValues(childDocs: Doc[], facetKey: string, childFilters: string[]) {
const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1]));
let rtFields = 0;
let subDocs = childDocs;
+ let gatheredDocs = [] as Doc[];
if (subDocs.length > 0) {
let newarray: Doc[] = [];
while (subDocs.length > 0) {
newarray = [];
subDocs.forEach(t => {
+ gatheredDocs.push(t);
const facetVal = t[facetKey];
if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
@@ -135,7 +99,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
});
- subDocs = newarray;
+ subDocs = newarray.filter(d => !gatheredDocs.includes(d));
}
}
// }
@@ -145,8 +109,8 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
}
public removeFilter = (filterName: string) => {
- Doc.setDocFilter(this.targetDoc, filterName, undefined, 'remove');
- Doc.setDocRangeFilter(this.targetDoc, filterName, undefined);
+ Doc.setDocFilter(this.Document, filterName, undefined, 'remove');
+ Doc.setDocRangeFilter(this.Document, filterName, undefined);
};
// @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
@@ -154,18 +118,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
@observable _collapseReturnKeys = new Array();
// this computed function gets the active filters and maps them to their headers
-
//
// activeRenderedFacetInfos()
// returns renderInfo for all user selected filters and for all existing filters set on the document
// Map("tags" => {"checkbox"},
- // "width" => {"rangs", domain:[1978,1992]})
+ // "width" => {"range", domain:[1978,1992]})
//
-
@computed get activeRenderedFacetInfos() {
return new Set(
Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => {
- const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.targetDoc.childFilters));
+ const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters));
let nonNumbers = 0;
let minVal = Number.MAX_VALUE,
@@ -185,7 +147,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
} else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
- const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter
+ const ranged = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] };
} else {
return { facetHeader, renderType: 'checkbox' };
@@ -194,8 +156,6 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
);
}
- @observable _selectedFacetHeaders = new Set<string>();
-
/**
* user clicks on a filter facet because they want to see it.
* this adds this chosen filter to a set of user selected filters called: selectedFilters
@@ -229,7 +189,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
facetValues = (facetHeader: string) => {
const allCollectionDocs = new Set<Doc>();
SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>([...StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
+ const set = new Set<string>([...StrListCast(this.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);
if (facetHeader === 'tags')
allCollectionDocs.forEach(child =>
StrListCast(child[facetHeader])
@@ -253,59 +213,13 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
};
- @computed get fieldsDropdown() {
- const filteredOptions = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];
-
- Object.entries(DocOptions)
- .filter(opts => opts[1].filterable)
- .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0]));
- const options = filteredOptions.map(facet => ({ value: facet, label: facet }));
-
- return (
- <Select
- styles={{
- control: (baseStyles, state) => ({
- ...baseStyles,
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
- }),
- placeholder: (baseStyles, state) => ({
- ...baseStyles,
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
- }),
- input: (baseStyles, state) => ({
- ...baseStyles,
- color: SettingsManager.userColor,
- background: SettingsManager.userBackgroundColor,
- }),
- option: (baseStyles, state) => ({
- ...baseStyles,
- color: SettingsManager.userColor,
- background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor,
- }),
- menuList: (baseStyles, state) => ({
- ...baseStyles,
- backgroundColor: SettingsManager.userBackgroundColor,
- }),
- }}
- placeholder={'add a filter'}
- options={options}
- isMulti={false}
- onChange={val => this.facetClick((val as UserOptions).value)}
- onKeyDown={e => e.stopPropagation()}
- //onMenuClose={onClose}
- value={null}
- closeMenuOnSelect={true}
- />
- );
- }
-
render() {
return (
<div className="filterBox-treeView">
<div className="filterBox-select">
- <div style={{ width: '100%' }}>{this.fieldsDropdown}</div>
+ <div style={{ width: '100%' }}>
+ <FieldsDropdown Document={this.Document} selectFunc={this.facetClick} showPlaceholder={true} placeholder="add a filter" addedFields={['acl-Guest', LinkedTo]} />
+ </div>
{/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
{/* <div className="filterBox-select-bool">
<select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}>
@@ -341,11 +255,11 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
className="filterBox-facetHeader-remove"
onClick={action(e => {
if (renderInfo.facetHeader === 'text') {
- Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, 'match', 'remove');
+ Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove');
} else {
for (var key of this.facetValues(renderInfo.facetHeader)) {
if (this.mapActiveFiltersToFacets.get(key)) {
- Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove');
+ Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove');
}
}
}
@@ -353,7 +267,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
this._chosenFacetsCollapse.delete(renderInfo.facetHeader);
if (renderInfo.domain) {
- Doc.setDocRangeFilter(this.targetDoc, renderInfo.facetHeader, renderInfo.domain, 'remove');
+ Doc.setDocRangeFilter(this.Document, renderInfo.facetHeader, renderInfo.domain, 'remove');
}
})}>
<CiCircleRemove />{' '}
@@ -377,16 +291,16 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
case 'text':
return (
<input
- key={this.targetDoc[Id]}
+ key={this.Document[Id]}
placeholder={'enter text to match'}
defaultValue={
- StrListCast(this.targetDoc._childFilters)
+ StrListCast(this.Document._childFilters)
.find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
?.split(Doc.FilterSep)[1]
}
style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
- onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
- onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)}
+ onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
+ onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)}
/>
);
case 'checkbox':
@@ -397,12 +311,12 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
<input
style={{ width: 20, marginLeft: 20 }}
checked={['check', 'exists'].includes(
- StrListCast(this.targetDoc._childFilters)
+ StrListCast(this.Document._childFilters)
.find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue)
?.split(Doc.FilterSep)[2] ?? ''
)}
type={type}
- onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')}
+ onChange={undoable(e => Doc.setDocFilter(this.Document, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')}
/>
{facetValue}
</div>
@@ -412,64 +326,51 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
case 'range':
const domain = renderInfoDomain;
const range = renderInfoRange;
-
- if (range) {
- console.log('this is info range ' + range[0] + ' , ' + range[1]);
- }
-
if (domain) {
- console.log('this is info domain ' + domain[0] + ', ' + domain[1]);
-
return (
- <>
- {/* <div className="sliderBox-outerDiv-checkBox" style={{ float: 'left' }}>
- <Checkbox color="primary" onChange={action(() => console.log('on change'))} />
- </div> */}
-
- <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
- <Slider
- mode={2}
- step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
- domain={[domain[0], domain[1]]} // -1000, 1000
- rootStyle={{ position: 'relative', width: '100%' }}
- onChange={values => Doc.setDocRangeFilter(this.targetDoc, facetHeader, values)}
- values={renderInfoRange!}>
- <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
- <Handles>
- {({ handles, activeHandleID, getHandleProps }) => (
- <div className="slider-handles">
- {handles.map((handle, i) => {
- // const value = i === 0 ? defaultValues[0] : defaultValues[1];
- return (
- <div>
- <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} />
- </div>
- );
- })}
- </div>
- )}
- </Handles>
- <Tracks left={false} right={false}>
- {({ tracks, getTrackProps }) => (
- <div className="slider-tracks">
- {tracks.map(({ id, source, target }) => (
- <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
- ))}
- </div>
- )}
- </Tracks>
- <Ticks count={5}>
- {({ ticks }) => (
- <div className="slider-ticks">
- {ticks.map(tick => (
- <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
- ))}
- </div>
- )}
- </Ticks>
- </Slider>
- </div>
- </>
+ <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
+ <Slider
+ mode={2}
+ step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
+ domain={[domain[0], domain[1]]} // -1000, 1000
+ rootStyle={{ position: 'relative', width: '100%' }}
+ onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
+ values={renderInfoRange!}>
+ <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
+ <Handles>
+ {({ handles, activeHandleID, getHandleProps }) => (
+ <div className="slider-handles">
+ {handles.map((handle, i) => {
+ // const value = i === 0 ? defaultValues[0] : defaultValues[1];
+ return (
+ <div>
+ <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} />
+ </div>
+ );
+ })}
+ </div>
+ )}
+ </Handles>
+ <Tracks left={false} right={false}>
+ {({ tracks, getTrackProps }) => (
+ <div className="slider-tracks">
+ {tracks.map(({ id, source, target }) => (
+ <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
+ ))}
+ </div>
+ )}
+ </Tracks>
+ <Ticks count={5}>
+ {({ ticks }) => (
+ <div className="slider-ticks">
+ {ticks.map(tick => (
+ <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
+ ))}
+ </div>
+ )}
+ </Ticks>
+ </Slider>
+ </div>
);
}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 92644d3c5..122e5c4c3 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -438,7 +438,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() impleme
<svg
className="inkStroke"
style={{
- transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.w_Rotation?.() ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
+ transform: isInkMask ? `rotate(-${NumCast(this._props.CollectionFreeFormDocumentView?.()._props.rotation ?? 0)}deg) translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
// mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? 'multiply' : 'unset',
cursor: this._props.isSelected() ? 'default' : undefined,
}}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index b6cb845a6..3be52597a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -19,7 +19,7 @@ import { Docs } from '../documents/Documents';
import { CalendarManager } from '../util/CalendarManager';
import { CaptureManager } from '../util/CaptureManager';
import { DocumentManager } from '../util/DocumentManager';
-import { DragManager } from '../util/DragManager';
+import { DragManager, dropActionType } from '../util/DragManager';
import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
@@ -623,7 +623,7 @@ export class MainView extends ObservableReactComponent<{}> {
isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive
ScreenToLocalTransform={this.headerBarScreenXf}
childHideResizeHandles={true}
- childDragAction="move"
+ childDragAction={dropActionType.move}
dontRegisterView={true}
hideResizeHandles={true}
PanelWidth={this.headerBarDocWidth}
@@ -905,7 +905,7 @@ export class MainView extends ObservableReactComponent<{}> {
Document={Doc.MyDockedBtns}
docViewPath={returnEmptyDocViewList}
fieldKey="data"
- dropAction="embed"
+ dropAction={dropActionType.embed}
styleProvider={DefaultStyleProvider}
select={emptyFunction}
isAnyChildContentActive={returnFalse}
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 20dc6c9fa..15b1f0275 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -9,7 +9,7 @@ import { Height, Width } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { NumCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
-import { DragManager } from '../util/DragManager';
+import { DragManager, dropActionType } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { LightboxView } from './LightboxView';
import { ObservableReactComponent } from './ObservableReactComponent';
@@ -196,7 +196,7 @@ export class OverlayView extends ObservableReactComponent<{}> {
if (e.metaKey) {
const dragData = new DragManager.DocumentDragData([d]);
dragData.offset = [-offsetx, -offsety];
- dragData.dropAction = 'move';
+ dragData.dropAction = dropActionType.move;
dragData.removeDocument = this.removeOverlayDoc;
dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
return dragData.removeDocument!(doc) ? addDocument(doc) : false;
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index cb38ab602..3cb835e39 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -486,8 +486,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
const isImage = layoutField instanceof ImageField;
const isMap = this.selectedDoc?.type === DocumentType.MAP;
const isCollection = this.selectedDoc?.type === DocumentType.COL;
- //TODO: will likely need to create separate note-taking view type here
- const isStacking = this.selectedDoc?._type_collection === CollectionViewType.Stacking || this.selectedDoc?._type_collection === CollectionViewType.NoteTaking;
+ const isStacking = [CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.NoteTaking].includes(this.selectedDoc?._type_collection as any);
const isFreeForm = this.selectedDoc?._type_collection === CollectionViewType.Freeform;
const isTree = this.selectedDoc?._type_collection === CollectionViewType.Tree;
const isTabView = this.selectedTabView;
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 361451c4d..b8bbde9de 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -30,12 +30,12 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
const embeddings = Doc.GetEmbeddings(target);
const containerProtos = embeddings.filter(embedding => embedding.embedContainer && embedding.embedContainer instanceof Doc).reduce((set, embedding) => set.add(Cast(embedding.embedContainer, Doc, null)), new Set<Doc>());
- const containerSets = Array.from(containerProtos.keys()).map(container => Doc.GetEmbeddings(container));
+ const containerSets = Array.from(containerProtos.keys()).map(container => (Doc.GetEmbeddings(container).length ? Doc.GetEmbeddings(container) : [container]));
const containers = containerSets.reduce((p, set) => {
set.map(s => p.add(s));
return p;
}, new Set<Doc>());
- const doclayoutSets = Array.from(containers.keys()).map(dp => Doc.GetEmbeddings(dp));
+ const doclayoutSets = Array.from(containers.keys()).map(dp => (Doc.GetEmbeddings(dp).length ? Doc.GetEmbeddings(dp) : [dp]));
const doclayouts = Array.from(
doclayoutSets
.reduce((p, set) => {
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e4e7bec32..cbd3ff358 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -135,7 +135,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return this.selectedDoc?.isGroup;
}
@computed get isStack() {
- return [CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
+ return [CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Stacking, CollectionViewType.NoteTaking].includes(this.selectedDoc?.type_collection as any);
}
rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20));
@@ -1076,6 +1076,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
<div className="transform-editor">
{!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))}
{!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))}
+ {!this.isStack ? null : this.getNumber('yMargin', ' px', 0, 500, NumCast(this.selectedDoc!.yMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.yMargin = val))}
{!this.isGroup ? null : this.getNumber('Padding', ' px', 0, 500, NumCast(this.selectedDoc!.xPadding), (val: number) => !isNaN(val) && (this.selectedDoc!.xPadding = this.selectedDoc!.yPadding = val))}
{this.isInk ? this.controlPointsButton : null}
{this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 5fc33207e..f7729dbc7 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -81,7 +81,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
componentDidMount() {
!this._addedKeys && (this._addedKeys = new ObservableSet());
[...Array.from(Object.keys(this.props.docViews[0].Document[DocData])), ...Array.from(Object.keys(this.props.docViews[0].Document))]
- .filter(key => key.startsWith('layout_'))
+ .filter(key => key.startsWith('layout_') && key !== 'layout_fieldKey')
.map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', ''))));
}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 68de62d93..8f1633122 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,7 +11,7 @@ import { List } from '../../../fields/List';
import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, incrementTitleCopy } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils';
import { DocServer } from '../../DocServer';
import { Docs } from '../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
@@ -432,8 +432,8 @@ export class CollectionDockingView extends CollectionSubView() {
public CaptureThumbnail() {
const content = this.DocumentView?.()?.ContentDiv;
if (content) {
- const _width = Number(getComputedStyle(content).width.replace('px', ''));
- const _height = Number(getComputedStyle(content).height.replace('px', ''));
+ const _width = DivWidth(content);
+ const _height = DivHeight(content);
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 proto = this.dataDoc; // Cast(img.proto, Doc, null)!;
proto['thumb_nativeWidth'] = _width;
@@ -488,8 +488,10 @@ export class CollectionDockingView extends CollectionSubView() {
Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc, undefined, undefined, true);
// if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed
if (tab.DashDoc.embedContainer === this.Document) tab.DashDoc.embedContainer = undefined;
- if (!tab.DashDoc.embedContainer) Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
- Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
+ if (!tab.DashDoc.embedContainer) {
+ Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true);
+ Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
+ }
}
if (CollectionDockingView.Instance) {
const dview = CollectionDockingView.Instance.Document;
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 41c5d5b42..763d2e3a6 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -33,8 +33,7 @@ interface CMVFieldRowProps {
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
setDocHeight: (key: string, thisHeight: number) => void;
- observeHeight: (myref: any) => void;
- unobserveHeight: (myref: any) => void;
+ refList: any[];
showHandle: boolean;
}
@@ -68,20 +67,20 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF
createRowDropRef = (ele: HTMLDivElement | null) => {
this._dropDisposer?.();
- if (ele) {
- this._ele = ele;
- this._props.observeHeight(ele);
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
- }
+ if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Document);
+ else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = ele;
};
@action
componentDidMount() {
this.heading = this._props.headingObject?.heading || '';
this.color = this._props.headingObject?.color || '#f1efeb';
this.collapsed = this._props.headingObject?.collapsed || false;
+ this._ele && this.props.refList.push(this._ele);
}
componentWillUnmount() {
- this._props.unobserveHeight(this._ele);
+ this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = null;
}
getTrueHeight = () => {
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 0f90818ef..8729ef549 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -11,7 +11,7 @@ import { RichTextField } from '../../../fields/RichTextField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
@@ -91,7 +91,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> {
Document={selDoc}
docViewPath={returnEmptyDocViewList}
fieldKey="data"
- dropAction="embed"
+ dropAction={dropActionType.embed}
styleProvider={DefaultStyleProvider}
select={emptyFunction}
isContentActive={returnTrue}
diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss
index 91a82d40f..4c2dcf9ab 100644
--- a/src/client/views/collections/CollectionNoteTakingView.scss
+++ b/src/client/views/collections/CollectionNoteTakingView.scss
@@ -98,6 +98,7 @@
overflow: auto;
display: flex;
flex-direction: column;
+ height: max-content;
}
.collectionSchemaView-previewDoc {
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index b8133806f..6318620e0 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Field, Opt } from '../../../fields/Doc';
@@ -9,7 +9,7 @@ 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, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SnappingManager } from '../../util/SnappingManager';
@@ -45,6 +45,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
public DividerWidth = 16;
@observable docsDraggedRowCol: number[] = [];
@observable _scroll = 0;
+ @observable _refList: any[] = [];
constructor(props: any) {
super(props);
@@ -157,8 +158,16 @@ export class CollectionNoteTakingView extends CollectionSubView() {
document.addEventListener('pointerup', this.removeDocDragHighlight, true);
this._disposers.layout_autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight =>
- layout_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', ''))))))
+ layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight)))
+ );
+
+ this._disposers.refList = reaction(
+ () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
+ ({ refList, autoHeight }) => {
+ if (autoHeight) refList.forEach(r => this.observer.observe(r));
+ else this.observer.disconnect();
+ },
+ { fireImmediately: true }
);
}
@@ -345,7 +354,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
// get the index for where you need to insert the doc you are currently dragging
const clientY = this.ScreenToLocalBoxXf().transformPoint(ex, ey)[1];
let dropInd = -1;
- let pos0 = (this.refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
+ let pos0 = (this._refList.lastElement() as HTMLDivElement).children[0].getBoundingClientRect().height + this.yMargin * 2;
colDocs.forEach((doc, i) => {
let pos1 = this.getDocHeight(doc) + 2 * this.gridGap;
pos1 += pos0;
@@ -424,7 +433,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
if (super.onInternalDrop(e, de)) {
// filter out the currently dragged docs from the child docs, since we will insert them later
const rowCol = this.docsDraggedRowCol;
- const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
+ const droppedDocs = this.childDocs.filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note).
const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments;
const docs = this.childDocList;
if (docs && newDocs.length) {
@@ -489,65 +498,45 @@ export class CollectionNoteTakingView extends CollectionSubView() {
headings = () => Array.from(this.Sections);
- refList: any[] = [];
-
editableViewProps = () => ({
GetValue: () => '',
SetValue: this.addGroup,
contents: '+ New Column',
});
+ refList = () => this._refList;
+
// sectionNoteTaking returns a CollectionNoteTakingViewColumn (which is an individual column)
- sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
- const type = 'number';
- return (
- <CollectionNoteTakingViewColumn
- key={heading?.heading ?? 'unset'}
- unobserveHeight={ref => 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._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
- 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.Contains(this.DocumentView?.())) {
- this._props.setHeight?.(height);
- }
- }
- })
- );
- this.observer.observe(ref);
- }
- }}
- PanelWidth={this._props.PanelWidth}
- select={this._props.select}
- addDocument={this.addDocument}
- chromeHidden={this.chromeHidden}
- colHeaderData={this.colHeaderData}
- Document={this.Document}
- TemplateDataDocument={this._props.TemplateDataDocument}
- resizeColumns={this.resizeColumns}
- renderChildren={this.children}
- numGroupColumns={this.numGroupColumns}
- gridGap={this.gridGap}
- pivotField={this.notetakingCategoryField}
- fieldKey={this.fieldKey}
- dividerWidth={this.DividerWidth}
- maxColWidth={this.maxColWidth}
- availableWidth={this.availableWidth}
- headings={this.headings}
- heading={heading?.heading ?? 'unset'}
- headingObject={heading}
- docList={docList}
- yMargin={this.yMargin}
- type={type}
- createDropTarget={this.createDashEventsTarget}
- screenToLocalTransform={this.ScreenToLocalBoxXf}
- editableViewProps={this.editableViewProps}
- />
- );
- };
+ sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => (
+ <CollectionNoteTakingViewColumn
+ key={heading?.heading ?? 'unset'}
+ PanelWidth={this._props.PanelWidth}
+ refList={this._refList}
+ select={this._props.select}
+ addDocument={this.addDocument}
+ chromeHidden={this.chromeHidden}
+ colHeaderData={this.colHeaderData}
+ Document={this.Document}
+ TemplateDataDocument={this._props.TemplateDataDocument}
+ resizeColumns={this.resizeColumns}
+ renderChildren={this.children}
+ numGroupColumns={this.numGroupColumns}
+ gridGap={this.gridGap}
+ pivotField={this.notetakingCategoryField}
+ fieldKey={this.fieldKey}
+ dividerWidth={this.DividerWidth}
+ maxColWidth={this.maxColWidth}
+ availableWidth={this.availableWidth}
+ headings={this.headings}
+ heading={heading?.heading ?? 'unset'}
+ headingObject={heading}
+ docList={docList}
+ yMargin={this.yMargin}
+ createDropTarget={this.createDashEventsTarget}
+ screenToLocalTransform={this.ScreenToLocalBoxXf}
+ editableViewProps={this.editableViewProps}
+ />
+ );
// addGroup is called when adding a new columnHeader, adding a SchemaHeaderField to our list of
// columnHeaders and resizing the existing columns to make room for our new one.
@@ -619,7 +608,7 @@ export class CollectionNoteTakingView extends CollectionSubView() {
return this.isContentActive() === false ? 'none' : undefined;
}
- observer: any;
+ observer = new _global.ResizeObserver(() => this._props.setHeight?.(this.headerMargin + Math.max(...this._refList.map(DivHeight))));
render() {
TraceMobx();
diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
index 38846c79d..db178d500 100644
--- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
+++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx
@@ -36,15 +36,13 @@ interface CSVFieldColumnProps {
yMargin: number;
numGroupColumns: number;
gridGap: number;
- type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
headings: () => object[];
select: (ctrlPressed: boolean) => void;
renderChildren: (docs: Doc[]) => JSX.Element[];
addDocument: (doc: Doc | Doc[]) => boolean;
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
- observeHeight: (myref: any) => void;
- unobserveHeight: (myref: any) => void;
+ refList: any[];
editableViewProps: () => any;
resizeColumns: (headers: SchemaHeaderField[]) => boolean;
maxColWidth: number;
@@ -78,15 +76,18 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
- if (ele) {
- this._ele = ele;
- this._props.observeHeight(ele);
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
- }
+ if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
+ else if (this._ele) this.props.refList.slice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = ele;
};
+ componentDidMount(): void {
+ this._ele && this.props.refList.push(this._ele);
+ }
+
componentWillUnmount() {
- this._props.unobserveHeight(this._ele);
+ this._ele && this.props.refList.splice(this._props.refList.indexOf(this._ele), 1);
+ this._ele = null;
}
@undoBatch
@@ -289,11 +290,10 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV
render() {
TraceMobx();
- const heading = this._heading;
return (
<div
className="collectionNoteTakingViewFieldColumn"
- key={heading}
+ key={this._heading}
style={{
width: this.columnWidth,
background: this._background,
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index d0df77cbe..7d7f0bb61 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -13,6 +13,7 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import './CollectionPileView.scss';
import { CollectionSubView } from './CollectionSubView';
+import { dropActionType } from '../../util/DragManager';
@observer
export class CollectionPileView extends CollectionSubView() {
@@ -72,7 +73,7 @@ export class CollectionPileView extends CollectionSubView() {
// pile children never have their contents active, but will be document active whenever the entire pile is.
childContentsActive={returnFalse}
childDocumentsActive={this._props.isDocumentActive}
- childDragAction="move"
+ childDragAction={dropActionType.move}
childClickScript={this.toggleIcon}
/>
</div>
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 54314f62c..dc391631a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -11,12 +11,11 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
import { SettingsManager } from '../../util/SettingsManager';
-import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from '../../util/UndoManager';
import { ContextMenu } from '../ContextMenu';
@@ -45,17 +44,16 @@ export type collectionStackingViewProps = {
@observer
export class CollectionStackingView extends CollectionSubView<Partial<collectionStackingViewProps>>() {
+ _disposers: { [key: string]: IReactionDisposer } = {};
_masonryGridRef: HTMLDivElement | null = null;
// used in a column dragger, likely due for the masonry grid view. We want to use this
_draggerRef = React.createRef<HTMLDivElement>();
- // Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view
- _pivotFieldDisposer?: IReactionDisposer;
- // Seems like we cause reaction in MobX get rid of our height once we exit this view
- _layout_autoHeightDisposer?: IReactionDisposer;
// keeping track of documents. Updated on internal and external drops. What's the difference?
_docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = [];
// Doesn't look like this field is being used anywhere. Obsolete?
_columnStart: number = 0;
+
+ @observable _refList: any[] = [];
// map of node headers to their heights. Used in Masonry
@observable _heightMap = new Map<string, number>();
// Assuming that this is the current css cursor style
@@ -207,27 +205,27 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this._props.setContentViewBox?.(this);
// reset section headers when a new filter is inputted
- this._pivotFieldDisposer = reaction(
+ this._disposers.pivotField = reaction(
() => this.pivotField,
() => (this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List())
);
- this._layout_autoHeightDisposer = reaction(
+ this._disposers.autoHeight = reaction(
() => this.layoutDoc._layout_autoHeight,
- layout_autoHeight =>
- layout_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))
- )
- )
+ layout_autoHeight => layout_autoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0)))
+ );
+ this._disposers.refList = reaction(
+ () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }),
+ ({ refList, autoHeight }) => {
+ this.observer.disconnect();
+ if (autoHeight) refList.forEach(r => this.observer.observe(r));
+ },
+ { fireImmediately: true }
);
}
componentWillUnmount() {
super.componentWillUnmount();
- this._pivotFieldDisposer?.();
- this._layout_autoHeightDisposer?.();
+ Object.keys(this._disposers).forEach(key => this._disposers[key]());
}
isAnyChildContentActive = () => this._props.isAnyChildContentActive();
@@ -523,7 +521,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
});
};
headings = () => Array.from(this.Sections);
- refList: any[] = [];
// what a section looks like if we're in stacking view
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
const key = this.pivotField;
@@ -536,23 +533,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return (
<CollectionStackingViewFieldColumn
- unobserveHeight={ref => 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._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
- 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.Contains(this.DocumentView?.())) {
- this._props.setHeight?.(height);
- }
- }
- })
- );
- this.observer.observe(ref);
- }
- }}
+ refList={this._refList}
addDocument={this.addDocument}
chromeHidden={this.chromeHidden}
colHeaderData={this.colHeaderData}
@@ -591,21 +572,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
Document={this.Document}
chromeHidden={this.chromeHidden}
pivotField={this.pivotField}
- unobserveHeight={ref => 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._layout_autoHeight && ref && this.refList.length && !SnappingManager.IsDragging) {
- const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace('px', '')), 0);
- this._props.setHeight?.(2 * this.headerMargin + height); // bcz: added 2x for header to fix problem with scrollbars appearing in Tools panel
- }
- })
- );
- this.observer.observe(ref);
- }
- }}
+ refList={this._refList}
key={heading ? heading.heading : ''}
rows={rows}
headings={this.headings}
@@ -709,7 +676,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@computed get backgroundEvents() {
return this._props.isContentActive() === false ? 'none' : undefined;
}
- observer: any;
+
+ observer = new _global.ResizeObserver(() => this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0))));
+
render() {
TraceMobx();
const editableViewProps = {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index c455f20d8..6a3cb759e 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -9,7 +9,7 @@ import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, NumCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
+import { DivHeight, DivWidth, emptyFunction, returnEmptyString, setupMoveUpEvents } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DragManager } from '../../util/DragManager';
@@ -44,8 +44,7 @@ interface CSVFieldColumnProps {
addDocument: (doc: Doc | Doc[]) => boolean;
createDropTarget: (ele: HTMLDivElement) => void;
screenToLocalTransform: () => Transform;
- observeHeight: (myref: any) => void;
- unobserveHeight: (myref: any) => void;
+ refList: any[];
}
@observer
@@ -53,7 +52,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
private dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
- @observable private _background = 'inherit';
+ @observable _background = 'inherit';
@observable _paletteOn = false;
@observable _heading = '';
@observable _color = '';
@@ -71,15 +70,14 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
// is that the only way to have drop targets?
createColumnDropRef = (ele: HTMLDivElement | null) => {
this.dropDisposer?.();
- if (ele) {
- this._ele = ele;
- this._props.observeHeight(ele);
- this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
- }
+ if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document);
+ else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = ele;
};
@action
componentDidMount() {
+ this._ele && this.props.refList.push(this._ele);
this._disposers.collapser = reaction(
() => this._props.headingObject?.collapsed,
collapsed => (this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false),
@@ -88,7 +86,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
}
componentWillUnmount() {
this._disposers.collapser?.();
- this._props.unobserveHeight(this._ele);
+ this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1);
+ this._ele = null;
}
//TODO: what is scripting? I found it in SetInPlace def but don't know what that is
@@ -217,8 +216,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const layoutItems: ContextMenuProps[] = [];
const docItems: ContextMenuProps[] = [];
const dataDoc = this._props.TemplateDataDocument || this._props.Document;
- const width = this._ele ? Number(getComputedStyle(this._ele).width.replace('px', '')) : 0;
- const height = this._ele ? Number(getComputedStyle(this._ele).height.replace('px', '')) : 0;
+ const width = this._ele ? DivWidth(this._ele) : 0;
+ const height = this._ele ? DivHeight(this._ele) : 0;
DocUtils.addDocumentCreatorMenuItems(
doc => {
FormattedTextBox.SetSelectOnLoad(doc);
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 38f6aa3e7..b92edd165 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,12 +1,10 @@
-import { toUpper } from 'lodash';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { Doc, Opt, StrListCast } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { RichTextField } from '../../../fields/RichTextField';
import { listSpec } from '../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
@@ -15,7 +13,7 @@ import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
-import { EditableView } from '../EditableView';
+import { FieldsDropdown } from '../FieldsDropdown';
import { DocumentView } from '../nodes/DocumentView';
import { FocusViewOptions } from '../nodes/FieldView';
import { PresBox } from '../nodes/trails';
@@ -192,51 +190,6 @@ export class CollectionTimeView extends CollectionSubView() {
ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
};
- @computed get _allFacets() {
- const facets = new Set<string>();
- this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key)));
- Doc.AreProtosEqual(this.dataDoc, this.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key)));
- return Array.from(facets);
- }
- menuCallback = (x: number, y: number) => {
- ContextMenu.Instance.clearItems();
- const keySet: Set<string> = new Set(['tags']);
-
- this.childLayoutPairs.map(pair =>
- this._allFacets
- .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string')
- .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
- .map(fieldKey => keySet.add(fieldKey))
- );
-
- const docItems: ContextMenuProps[] = Array.from(keySet).map(fieldKey =>
- ({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); // prettier-ignore
- docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' });
- ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' });
- ContextMenu.Instance.displayMenu(x, y, ':');
- };
-
- @computed get pivotKeyUI() {
- return (
- <div className={'pivotKeyEntry'}>
- <EditableView
- GetValue={returnEmptyString}
- SetValue={(value: any) => {
- if (value?.length) {
- this.layoutDoc._pivotField = value;
- return true;
- }
- return false;
- }}
- background={'#f1efeb'} // this._props.headingObject ? this._props.headingObject.color : "#f1efeb";
- contents={':' + StrCast(this.layoutDoc._pivotField)}
- showMenuOnLoad={true}
- display={'inline'}
- menuCallback={this.menuCallback}
- />
- </div>
- );
- }
render() {
let nonNumbers = 0;
@@ -263,7 +216,6 @@ export class CollectionTimeView extends CollectionSubView() {
return (
<div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this._props.PanelWidth(), height: '100%' }}>
- {this.pivotKeyUI}
{this.contents}
{!this._props.isSelected() || !doTimeline ? null : (
<>
@@ -272,6 +224,9 @@ export class CollectionTimeView extends CollectionSubView() {
<div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
</>
)}
+ <div style={{ right: 0, top: 0, position: 'absolute' }}>
+ <FieldsDropdown Document={this.Document} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} />
+ </div>
</div>
);
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 786301136..3b37bdcfa 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
+import { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -113,8 +113,8 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
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()) + 6;
+ const titleHeight = !this._titleRef ? this.marginTop() : DivHeight(this._titleRef);
+ const bodyHeight = Array.from(this.refList).reduce((p, r) => p + DivHeight(r), this.marginBot()) + 6;
this.layoutDoc._layout_autoHeightMargins = bodyHeight;
!this._props.dontRegisterView && this._props.setHeight?.(bodyHeight + titleHeight);
}
@@ -153,12 +153,18 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return res;
}
- protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
+ protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData) {
- const sameTree = Doc.AreProtosEqual(dragData.treeViewDoc, this.Document) ? true : false;
+ const sourceDragAction = dragData.dropAction;
+ const sameTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.Document);
const isAlreadyInTree = () => sameTree || dragData.draggedDocuments.some(d => d.embedContainer === this.Document && this.childDocs.includes(d));
- dragData.dropAction = dropAction && !isAlreadyInTree() ? dropAction : sameTree && dragData.dropAction !== 'inSame' ? 'same' : dragData.dropAction;
+ dragData.dropAction =
+ targetDropAction && !isAlreadyInTree() // if dropped document is not in the tree
+ ? targetDropAction // then use the target's drop action if it's specified
+ : !sameTree() || sourceDragAction === dropActionType.inPlace // if doc from another tree, or a non inPlace source drag action is specified
+ ? sourceDragAction // use the source dragAction
+ : dropActionType.same; // otherwise use same tree semantics to move within tree
e.stopPropagation();
}
};
@@ -287,7 +293,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
@observable _renderCount = 1;
@computed get treeViewElements() {
TraceMobx();
- const dragAction = StrCast(this.Document.childDragAction) as dropActionType;
+ const dragAction = StrCast(this.Document.childDragAction) as any as dropActionType;
const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before);
const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this._props.moveDocument?.(d, target, addDoc) || false;
if (this._renderCount < this.treeChildren.length) setTimeout(action(() => (this._renderCount = Math.min(this.treeChildren.length, this._renderCount + 20))));
@@ -414,7 +420,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', pointerEvents: 'all' }}>
{!this.buttonMenu && !this.noviceExplainer ? null : (
- <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = Number(getComputedStyle(r).height.replace(/px/, ''))))}>
+ <div className="documentButtonMenu" ref={action((r: HTMLDivElement | null) => r && (this._headerHeight = DivHeight(r)))}>
{this.buttonMenu}
{this.noviceExplainer}
</div>
@@ -473,7 +479,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
isAnnotationOverlayScrollable={true}
childDocumentsActive={this._props.isContentActive}
fieldKey={this._props.fieldKey + '_annotations'}
- dropAction="move"
+ dropAction={dropActionType.move}
select={emptyFunction}
addDocument={this.addAnnotationDocument}
removeDocument={this.remAnnotationDocument}
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 02aa76d82..699ec3b95 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -564,6 +564,12 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
const dim = Math.max(xbounds, ybounds);
return { l: bounds.x + xbounds / 2 - dim / 2, t: bounds.y + ybounds / 2 - dim / 2, cx: bounds.x + xbounds / 2, cy: bounds.y + ybounds / 2, dim };
}
+ @computed get xPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l));
+ }
+ @computed get yPadding() {
+ return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l));
+ }
childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null);
returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150);
miniDown = (e: React.PointerEvent) => {
@@ -620,6 +626,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyDoclist}
searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
fitContentsToBox={returnTrue}
+ xPadding={this.xPadding}
+ yPadding={this.yPadding}
/>
<div className="miniOverlay" onPointerDown={this.miniDown}>
<TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} />
@@ -630,7 +638,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps
render() {
return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : (
<div className="miniMap-hidden">
- <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement={'top-end'} popup={this.popup} />
+ <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SettingsManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
</div>
);
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 85f7cf7fe..4f2d8b9c0 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -439,7 +439,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
droppedDocuments: Doc[],
before: boolean,
inside: number | boolean,
- dropAction: dropActionType,
+ dropAction: dropActionType | undefined,
removeDocument: DragManager.RemoveFunction | undefined,
moveDocument: DragManager.MoveFunction | undefined,
forceAdd: boolean,
@@ -1161,7 +1161,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
const before = pt[1] < rect.top + rect.height / 2;
const inside = this.treeView.fileSysMode && !this.Document.isFolder ? false : pt[0] > rect.left + rect.width * 0.33 || (!before && this.treeViewOpen && this.childDocs?.length ? true : false);
- const docs = this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, undefined, false, false));
+ this.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, dropActionType.copy, undefined, undefined, false, false));
};
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 50a9feff8..877569ab3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -17,7 +17,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, numberValue, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -159,8 +159,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
: aggregateBounds(
this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
- NumCast(this.layoutDoc._xPadding, 10),
- NumCast(this.layoutDoc._yPadding, 10)
+ NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10),
+ NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10)
);
}
@computed get nativeWidth() {
@@ -462,7 +462,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 };
- const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
+ const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined);
de.moveDocument = this._props.moveDocument;
de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
DragManager.StartDocumentDrag(
@@ -760,7 +760,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
};
- @action
onPointerMove = (e: PointerEvent) => {
if (this.tryDragCluster(e, this._hitCluster)) {
e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
@@ -1344,7 +1343,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
renderCutoffProvider = computedFn(
function renderCutoffProvider(this: any, doc: Doc) {
- return !this._renderCutoffData.get(doc[Id] + '');
+ return this.Document.isTemplateDoc ? false : !this._renderCutoffData.get(doc[Id] + '');
}.bind(this)
);
@@ -1467,7 +1466,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!code.includes('dashDiv')) {
const script = CompileScript(code, { params: { docView: 'any' }, typecheck: false, editable: true });
if (script.compiled) script.run({ this: this.DocumentView?.() });
- } else code && !first && eval(code);
+ } else code && !first && eval?.(code);
},
{ fireImmediately: true }
);
@@ -1712,7 +1711,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) {
const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
- const loadIncrement = 5;
+ const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5;
for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index a417d777a..d0e59180d 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -635,8 +635,13 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
}
MarqueeRef: HTMLDivElement | null = null;
+ /**
+ * This is called for every drag movement when a document is dragged over this collection.
+ * If the document is dragged within 25 pixels of the edge of the collection and paused, this will
+ * auto scroll the collection so that it can be dragged farther (unless auto panning has been disabled)
+ */
@action
- onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+ onDragMovePause = (e: CustomEvent<React.DragEvent>) => {
if ((e as any).handlePan || this._props.isAnnotationOverlay) return;
(e as any).handlePan = true;
@@ -659,7 +664,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
<div
className="marqueeView"
ref={r => {
- r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+ r?.addEventListener('dashDragMovePause', this.onDragMovePause as any);
this.MarqueeRef = r;
}}
style={{
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index b181b59ce..125dd2781 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -1,10 +1,10 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { Button } from 'browndash-components';
-import { action, computed, makeObservable } from 'mobx';
+import { Button, IconButton } from 'browndash-components';
+import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse } from '../../../../Utils';
+import { FaChevronRight } from 'react-icons/fa';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { DragManager, dropActionType } from '../../../util/DragManager';
@@ -37,6 +37,8 @@ const resizerWidth = 8;
@observer
export class CollectionMulticolumnView extends CollectionSubView() {
+ @observable _startIndex = 0;
+
constructor(props: any) {
super(props);
makeObservable(this);
@@ -48,7 +50,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
*/
@computed
private get ratioDefinedDocs() {
- return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
+ return this.childLayouts.filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
}
@computed
@@ -57,6 +59,15 @@ export class CollectionMulticolumnView extends CollectionSubView() {
return ratioDocs.length ? Math.min(...ratioDocs.map(layout => NumCast(layout._dimMagnitude))) : 1;
}
+ @computed get maxShown() {
+ return NumCast(this.layoutDoc.layout_maxShown);
+ }
+
+ @computed
+ private get childLayouts() {
+ return (this.maxShown ? this.childLayoutPairs.slice(this._startIndex, this._startIndex + this.maxShown) : this.childLayoutPairs).map(pair => pair.layout);
+ }
+
/**
* This loops through all childLayoutPairs and extracts the values for _dimUnit
* and _dimMagnitude, ignoring any that are malformed. Additionally, it then
@@ -69,9 +80,9 @@ export class CollectionMulticolumnView extends CollectionSubView() {
private get resolvedLayoutInformation(): LayoutData {
let starSum = 0;
const widthSpecifiers: WidthSpecifier[] = [];
- this.childLayoutPairs.map(pair => {
- const unit = StrCast(pair.layout._dimUnit, '*');
- const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
+ this.childLayouts.map(layout => {
+ const unit = StrCast(layout._dimUnit, '*');
+ const magnitude = NumCast(layout._dimMagnitude, this.minimumDim);
if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
unit === DimUnit.Ratio && (starSum += magnitude);
widthSpecifiers.push({ magnitude, unit });
@@ -90,7 +101,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
*/
// setTimeout(() => {
// const { ratioDefinedDocs } = this;
- // if (this.childLayoutPairs.length) {
+ // if (this.childPairs.length) {
// const minimum = this.minimumDim;
// if (minimum !== 0) {
// ratioDefinedDocs.forEach(layout => layout._dimMagnitude = NumCast(layout._dimMagnitude, 1) / minimum, 1);
@@ -187,13 +198,14 @@ export class CollectionMulticolumnView extends CollectionSubView() {
return Transform.Identity(); // we're still waiting on promises to resolve
}
let offset = 0;
- for (const { layout: candidate } of this.childLayoutPairs) {
+ var xf = Transform.Identity();
+ this.childLayouts.map(candidate => {
if (candidate === layout) {
- return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0);
+ return (xf = this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0));
}
offset += this.lookupPixels(candidate) + resizerWidth;
- }
- return Transform.Identity(); // type coersion, this case should never be hit
+ });
+ return xf;
};
@undoBatch
@@ -297,15 +309,15 @@ export class CollectionMulticolumnView extends CollectionSubView() {
*/
@computed
private get contents(): JSX.Element[] | null {
- const { childLayoutPairs } = this;
const collector: JSX.Element[] = [];
- for (let i = 0; i < childLayoutPairs.length; i++) {
- const { layout } = childLayoutPairs[i];
+ this.childLayouts.forEach((layout, i) => {
collector.push(
<Tooltip title={'Tab: ' + StrCast(layout.title)} key={'wrapper' + i}>
<div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}>
{this.getDisplayDoc(layout)}
- <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+ {this.layoutDoc._chromeHidden ? null : (
+ <Button tooltip="Remove document from header bar" icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={undoable(e => this._props.removeDocument?.(layout), 'close doc')} color={SettingsManager.userColor} />
+ )}
<WidthLabel layout={layout} collectionDoc={this.Document} />
</div>
</Tooltip>,
@@ -317,10 +329,10 @@ export class CollectionMulticolumnView extends CollectionSubView() {
select={this._props.select}
columnUnitLength={this.getColumnUnitLength}
toLeft={layout}
- toRight={childLayoutPairs[i + 1]?.layout}
+ toRight={this.childLayouts[i + 1]}
/>
);
- }
+ });
collector.pop(); // removes the final extraneous resize bar
return collector;
}
@@ -339,6 +351,20 @@ export class CollectionMulticolumnView extends CollectionSubView() {
marginBottom: NumCast(this.Document._yMargin),
}}>
{this.contents}
+ {!this._startIndex ? null : (
+ <Tooltip title="scroll back">
+ <div style={{ position: 'absolute', bottom: 0, left: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+ <Button tooltip="Scroll back" icon={<FontAwesomeIcon icon="chevron-left" size="lg" />} onClick={action(e => (this._startIndex = Math.max(0, this._startIndex - this.maxShown)))} color={SettingsManager.userColor} />
+ </div>
+ </Tooltip>
+ )}
+ {this._startIndex > this.childLayoutPairs.length - 1 || !this.maxShown ? null : (
+ <Tooltip title="scroll forward">
+ <div style={{ position: 'absolute', bottom: 0, right: 0, background: SettingsManager.userVariantColor }} onClick={action(e => (this._startIndex = Math.min(this.childLayoutPairs.length - 1, this._startIndex + this.maxShown)))}>
+ <IconButton icon={<FaChevronRight />} color={SettingsManager.userColor} />
+ </div>
+ </Tooltip>
+ )}
</div>
);
}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 659f7ccdc..17bf3e50c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -1,7 +1,6 @@
import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { emptyFunction, returnFalse } from '../../../../Utils';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { DragManager, dropActionType } from '../../../util/DragManager';
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 31b4a2dd4..eee3836d2 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -10,7 +10,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty
import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils';
import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents';
import { DocumentManager } from '../../../util/DocumentManager';
-import { DragManager } from '../../../util/DragManager';
+import { DragManager, dropActionType } from '../../../util/DragManager';
import { SelectionManager } from '../../../util/SelectionManager';
import { undoable, undoBatch } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
@@ -981,7 +981,7 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
styleProvider={this.noOpacityStyleProvider}
waitForDoubleClickToClick={returnNever}
defaultDoubleClick={returnIgnore}
- dragAction="move"
+ dragAction={dropActionType.move}
onClickScriptDisable="always"
focus={this._props.schema.focusDocument}
childFilters={this._props.schema.childDocFilters}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index 001ad5ab6..46867665d 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -10,7 +10,7 @@ import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { RichTextField } from '../../../../fields/RichTextField';
import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
-import { FInfo } from '../../../documents/Documents';
+import { FInfo, FInfoFieldType } from '../../../documents/Documents';
import { DocFocusOrOpen } from '../../../util/DocumentManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, undoable } from '../../../util/UndoManager';
@@ -29,6 +29,7 @@ import { Popup, Size, Type } from 'browndash-components';
import { IconLookup, faCaretDown } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { SettingsManager } from '../../../util/SettingsManager';
+import { dropActionType } from '../../../util/DragManager';
export interface SchemaTableCellProps {
Document: Doc;
@@ -86,7 +87,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
isSelected: returnFalse,
setHeight: returnFalse,
select: emptyFunction,
- dragAction: 'move',
+ dragAction: dropActionType.move,
renderDepth: 1,
isContentActive: returnFalse,
whenChildContentsActiveChanged: emptyFunction,
@@ -149,7 +150,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
if (cellValue instanceof DateField) return ColumnType.Date;
if (cellValue instanceof RichTextField) return ColumnType.RTF;
if (typeof cellValue === 'number') return ColumnType.Any;
- if (typeof cellValue === 'string' && columnTypeStr !== 'enumeration') return ColumnType.Any;
+ if (typeof cellValue === 'string' && columnTypeStr !== FInfoFieldType.enumeration) return ColumnType.Any;
if (typeof cellValue === 'boolean') return ColumnType.Boolean;
if (columnTypeStr && columnTypeStr in FInfotoColType) {
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 51672513b..33704e8fe 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -78,7 +78,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole
}
if (SelectionManager.Views.length) {
SelectionManager.Docs.forEach(doc => {
- Doc.GetProto(doc).layout_headingColor = color;
+ doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color;
doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title');
});
} else {
@@ -98,7 +98,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
});
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
const selected = SelectionManager.Docs.lastElement();
// prettier-ignore
const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
@@ -112,16 +112,16 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
}],
['viewAll', {
checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
+ setDoc: (doc:Doc,dv:DocumentView) => {
+ if (persist) doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox;
+ else if (doc._freeform_fitContentsToBox) doc._freeform_fitContentsToBox = undefined;
+ else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce();
+ },
}],
['center', {
checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false),
setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
}],
- ['fitOnce', {
- checkResult: (doc:Doc) => false,
- setDoc: (doc:Doc, dv:DocumentView) => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce()
- }],
['clusters', {
waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index e694806a5..81dd0eb98 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -10,7 +10,7 @@ import { Cast, DocCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { SelectionManager } from '../../util/SelectionManager';
@@ -45,7 +45,7 @@ export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: Docume
const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
dragData.canEmbed = true;
- dragData.dropAction = 'embed';
+ dragData.dropAction = dropActionType.embed;
DragManager.StartDocumentDrag([dragEle], dragData, downX, downY, undefined);
}
@@ -89,7 +89,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
this,
e,
e => {
- const dragData = new DragManager.DocumentDragData([this._props.linkDoc], 'embed');
+ const dragData = new DragManager.DocumentDragData([this._props.linkDoc], dropActionType.embed);
dragData.dropPropertiesToRemove = ['hidden'];
DragManager.StartDocumentDrag([this._editRef.current!], dragData, e.x, e.y);
return true;
@@ -177,12 +177,12 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
className="linkMenu-name" //title="drag to view target. click to customize."
onPointerDown={this.onLinkButtonDown}>
<div className="linkMenu-item-buttons">
- <Tooltip title={<div className="dash-tooltip">Edit Link</div>}>
+ <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Edit Link</div>}>
<div className="linkMenu-icon-wrapper" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="linkMenu-icon" icon="edit" size="sm" />
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">Show/Hide Link</div>}>
+ <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Show/Hide Link</div>}>
<div title="click to show link" className="linkMenu-icon-wrapper" onPointerDown={this.onIconDown}>
<FontAwesomeIcon className="linkMenu-icon" icon={destinationIcon} size="sm" />
</div>
@@ -211,7 +211,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</p>
) : null}
<div className="linkMenu-title-wrapper">
- <Tooltip title={<div className="dash-tooltip">Follow Link</div>}>
+ <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Follow Link</div>}>
<p className="linkMenu-destination-title">
{this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)}
</p>
@@ -221,7 +221,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</div>
<div className="linkMenu-item-buttons">
- <Tooltip title={<div className="dash-tooltip">Delete Link</div>}>
+ <Tooltip disableInteractive={true} title={<div className="dash-tooltip">Delete Link</div>}>
<div className="linkMenu-deleteButton" onPointerDown={this.deleteLink} onClick={e => e.stopPropagation()}>
<FontAwesomeIcon className="fa-icon" icon="trash" size="sm" />
</div>
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 116dc48a6..ef8c045cc 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -6,7 +6,7 @@ import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents }
import { Doc, Opt } from '../../../fields/Doc';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
import { DocUtils, Docs } from '../../documents/Documents';
-import { DragManager } from '../../util/DragManager';
+import { DragManager, dropActionType } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
@@ -135,7 +135,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
this,
e,
e => {
- const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], 'move');
+ const de = new DragManager.DocumentDragData([DocCast(this.dataDoc[which])], dropActionType.move);
de.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => {
this.clearDoc(which);
return addDocument(doc);
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index c4dab16fb..5421c1b50 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -180,7 +180,6 @@
.documentView-titleWrapper,
.documentView-titleWrapper-hover {
- overflow: hidden;
color: $black;
transform-origin: top left;
top: 0;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d131f72d5..ee058c085 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,11 +1,10 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
-import { Dropdown, DropdownType, Type } from 'browndash-components';
import { Howl } from 'howler';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal';
-import { Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
+import { DivWidth, Utils, emptyFunction, isTargetChildOf as isParentOf, lightOrDark, returnEmptyString, returnFalse, returnTrue, returnVal, simulateMouseClick } from '../../../Utils';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -48,6 +47,7 @@ import { KeyValueBox } from './KeyValueBox';
import { LinkAnchorBox } from './LinkAnchorBox';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails';
+import { FieldsDropdown } from '../FieldsDropdown';
interface Window {
MediaRecorder: MediaRecorder;
}
@@ -413,7 +413,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) {
this.cleanupPointerEvents();
this._longPressSelector && clearTimeout(this._longPressSelector);
- this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
+ this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && dropActionType.embed) || ((this.Document.dragAction || this._props.dragAction || undefined) as dropActionType));
}
};
@@ -702,7 +702,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
panelHeight = () => this._props.PanelHeight() - this.headerMargin;
screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler;
- setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = height);
+ setHeight = (height: number) => !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height));
setContentView = action((view: ViewBoxInterface) => (this._componentView = view));
isContentActive = (): boolean | undefined => this._isContentActive;
childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)];
@@ -784,28 +784,25 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
- fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => {
- const filteredFields = Object.entries(DocOptions).reduce((set, [field, opts]) => (opts.filterable ? set.add(field) : set), new Set(reqdFields));
+ fieldsDropdown = (placeholder: string) => {
return (
- <div style={{ width: dropdownWidth }}>
- <div
- ref={action((r: any) => r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))}
- onPointerDown={action(e => (this._changingTitleField = true))}
- style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
- <Dropdown
- activeChanged={action(isOpen => !isOpen && (this._changingTitleField = false))}
- selectedVal={placeholder}
- setSelectedVal={onChange}
- color={SettingsManager.userColor}
- background={SettingsManager.userVariantColor}
- type={Type.TERT}
- closeOnSelect={true}
- dropdownType={DropdownType.SELECT}
- items={Array.from(filteredFields).map(facet => ({ val: facet, text: facet }))}
- width={100}
- fillWidth
- />
- </div>
+ <div
+ ref={action((r: any) => r && (this._titleDropDownInnerWidth = DivWidth(r)))}
+ onPointerDown={action(e => (this._changingTitleField = true))}
+ style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}>
+ <FieldsDropdown
+ Document={this.Document}
+ placeholder={placeholder}
+ selectFunc={action((field: string | number) => {
+ if (this.layoutDoc.layout_showTitle) {
+ this.layoutDoc._layout_showTitle = field;
+ } else if (!this._props.layout_showTitle) {
+ Doc.UserDoc().layout_showTitle = field;
+ }
+ this._changingTitleField = false;
+ })}
+ menuClose={action(() => (this._changingTitleField = false))}
+ />
</div>
);
};
@@ -839,22 +836,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
background,
pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this._props.isDocumentActive?.() ? 'all' : undefined,
}}>
- {!dropdownWidth
- ? null
- : this.fieldsDropdown(
- [StrCast(this.layoutDoc.layout_showTitle)],
- dropdownWidth,
- StrCast(this.layoutDoc.layout_showTitle).split(':')[0],
- action((field: string | number) => {
- if (this.layoutDoc.layout_showTitle) {
- this.layoutDoc._layout_showTitle = field;
- } else if (!this._props.layout_showTitle) {
- Doc.UserDoc().layout_showTitle = field;
- }
- this._changingTitleField = false;
- }),
- action(() => (this._changingTitleField = false))
- )}
+ {!dropdownWidth ? null : <div style={{ width: dropdownWidth }}>{this.fieldsDropdown(showTitle)}</div>}
<div
style={{
width: `calc(100% - ${dropdownWidth}px)`,
@@ -864,10 +846,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}}>
<EditableView
ref={this._titleRef}
- contents={showTitle
- .split(';')
- .map(field => targetDoc[field.trim()]?.toString())
- .join(' \\ ')}
+ contents={
+ showTitle
+ .split(';')
+ .map(field => targetDoc[field.trim()]?.toString())
+ .join(' \\ ') || '-unset-'
+ }
display="block"
oneLine={true}
fontSize={(this.titleHeight / 15) * 10}
diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx
index 2e03a766a..50d4c7c78 100644
--- a/src/client/views/nodes/EquationBox.tsx
+++ b/src/client/views/nodes/EquationBox.tsx
@@ -11,6 +11,7 @@ import { LightboxView } from '../LightboxView';
import './EquationBox.scss';
import { FieldView, FieldViewProps } from './FieldView';
import EquationEditor from './formattedText/EquationEditor';
+import { DivHeight, DivWidth } from '../../../Utils';
@observer
export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -57,8 +58,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() {
@action
keyPressed = (e: KeyboardEvent) => {
- const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', ''));
- const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace('px', ''));
+ const _height = DivHeight(this._ref.current!.element.current);
+ const _width = DivWidth(this._ref.current!.element.current);
if (e.key === 'Enter') {
const nextEq = Docs.Create.EquationDocument(e.shiftKey ? StrCast(this.dataDoc.text) : 'x', {
title: '# math',
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 3577cc8d9..91b6de80b 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SelectionManager } from '../../../util/SelectionManager';
import { SettingsManager } from '../../../util/SettingsManager';
@@ -314,6 +314,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
const tooltip = StrCast(this.Document.toolTip);
const script = ScriptCast(this.Document.onClick);
+ const double = ScriptCast(this.Document.onDoubleClick);
const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false;
// Colors
const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
@@ -330,7 +331,12 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
//background={SettingsManager.userBackgroundColor}
icon={this.Icon(color)!}
label={this.label}
- onPointerDown={() => script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false })}
+ onPointerDown={e =>
+ setupMoveUpEvents(this, e, returnTrue, emptyFunction, (e, doubleTap) => {
+ !doubleTap && script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
+ doubleTap && double.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false });
+ })
+ }
/>
);
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 923aead64..251235b93 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -141,6 +141,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
const targetField = Doc.LayoutFieldKey(layoutDoc);
const targetDoc = layoutDoc[DocData];
if (targetDoc[targetField] instanceof ImageField) {
+ added = true;
this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey);
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 40647feff..b2ae7201c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -31,6 +31,7 @@ import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
import { RecordingBox } from './RecordingBox';
import { PinProps, PresBox } from './trails';
import './VideoBox.scss';
+import { dropActionType } from '../../util/DragManager';
/**
* VideoBox
@@ -335,7 +336,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
this._props.addDocument?.(imageSnapshot);
const link = DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' });
link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3);
- setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, 'move', true));
+ setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true));
};
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2c5398e40..c9340edc0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -14,7 +14,7 @@ import { listSpec } from '../../../fields/Schema';
import { Cast, NumCast, StrCast, WebCast } from '../../../fields/Types';
import { ImageField, WebField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, emptyFunction, getWordAtPoint, lightOrDark, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, stringHash, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
@@ -83,7 +83,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
return this.webField?.toString() || '';
}
@computed get _urlHash() {
- return ""+ (stringHash(this._url)??'');
+ return '' + (stringHash(this._url) ?? '');
}
@computed get scrollHeight() {
return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight);
@@ -782,7 +782,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem
className="webBox-htmlSpan"
ref={action((r: any) => {
if (r) {
- this._scrollHeight = Number(getComputedStyle(r).height.replace('px', ''));
+ this._scrollHeight = DivHeight(r);
this.lighttext = Array.from(r.children).some((c: any) => c instanceof HTMLElement && lightOrDark(getComputedStyle(c).color) !== Colors.WHITE);
}
})}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index b49e7dcf0..5c4d850ad 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -4,24 +4,23 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
import { observer } from 'mobx-react';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
-import Select from 'react-select';
import { Doc, DocListCast, Field } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { listSpec } from '../../../../fields/Schema';
import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField';
-import { Cast, StrCast } from '../../../../fields/Types';
+import { Cast } from '../../../../fields/Types';
import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { CollectionViewType } from '../../../documents/DocumentTypes';
import { Transform } from '../../../util/Transform';
-import { undoable, undoBatch } from '../../../util/UndoManager';
+import { undoBatch } from '../../../util/UndoManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
+import { FilterPanel } from '../../FilterPanel';
import { ObservableReactComponent } from '../../ObservableReactComponent';
import { OpenWhere } from '../DocumentView';
import './DashFieldView.scss';
import { FormattedTextBox } from './FormattedTextBox';
-import { FilterPanel } from '../../FilterPanel';
export class DashFieldView {
dom: HTMLDivElement; // container for label and value
@@ -29,7 +28,7 @@ export class DashFieldView {
node: any;
tbox: FormattedTextBox;
- unclickable = () => !this.tbox._props.isSelected() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
+ unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview);
constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) {
this.node = node;
this.tbox = tbox;
@@ -108,12 +107,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
super(props);
makeObservable(this);
this._fieldKey = this._props.fieldKey;
- this._textBoxDoc = this._props.tbox.Document;
+ this._textBoxDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
if (this._props.docId) {
DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc)));
} else {
- this._dashDoc = this._props.tbox.Document;
+ this._dashDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc;
}
}
@@ -202,7 +201,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
style={{
width: this._props.width,
height: this._props.height,
- pointerEvents: this._props.tbox._props.isSelected() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
+ pointerEvents: this._props.tbox._props.rootSelected?.() || this._props.tbox.isAnyChildContentActive?.() ? undefined : 'none',
}}>
{this._props.hideKey ? null : (
<span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e5362762e..a9c78627d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -23,7 +23,7 @@ import { RichTextField } from '../../../../fields/RichTextField';
import { ComputedField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
-import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
+import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils';
import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
@@ -374,9 +374,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
// 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 (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) {
const numstring = NumCast(dataDoc[this.fieldKey], null);
- dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText);
- dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited
+ dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText ? new RichTextField(newJson, newText) : undefined;
textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.Document, text: newText });
+ this._applyingChange = ''; // turning this off here allows a Doc to retrieve data from template if noTemplate below is changed to false
+ dataDoc[this.fieldKey + '_noTemplate'] = newText ? true : false; // mark the data field as being split from the template if it has been edited
unchanged = false;
}
} else {
@@ -758,7 +759,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@action
toggleSidebar = (preview: boolean = false) => {
const defaultSidebar = 250;
- const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', ''));
+ const prevWidth = 1 - this.sidebarWidth() / DivWidth(this._ref.current!);
if (preview) this._showSidebar = true;
else {
this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index b8f6575dd..918987034 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -36,6 +36,7 @@ import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
+import { dropActionType } from '../../../util/DragManager';
export interface pinDataTypes {
scrollable?: boolean;
dataviz?: number[];
@@ -1074,7 +1075,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
};
childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
- removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.Document, this.fieldKey, doc);
+ removeDocument = (doc: Doc | Doc[]) => !(doc instanceof Doc ? [doc] : doc).map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)).some(p => !p);
getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
panelHeight = () => this._props.PanelHeight() - 40;
/**
@@ -2607,7 +2608,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
childIgnoreNativeSize={true}
moveDocument={returnFalse}
ignoreUnrendered={true}
- childDragAction="move"
+ childDragAction={dropActionType.move}
setContentViewBox={emptyFunction}
//childLayoutFitWidth={returnTrue}
childOpacity={returnOne}
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index 4155800b8..addad2bbc 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,6 +1,6 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, isDark, Size, Type } from 'browndash-components';
-import { action, computed, observable, reaction } from 'mobx';
+import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { FaBug } from 'react-icons/fa';
@@ -25,13 +25,24 @@ import { Colors } from '../global/globalEnums';
import { DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView';
import { DefaultStyleProvider } from '../StyleProvider';
import './TopBar.scss';
+import { dropActionType } from '../../util/DragManager';
+import { Flip } from 'react-awesome-reveal';
+import { ObservableReactComponent } from '../ObservableReactComponent';
/**
* ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user
* and settings and help buttons. Future scope for this bar is to include the collaborators that are on the same Dashboard.
*/
@observer
-export class TopBar extends React.Component {
+export class TopBar extends ObservableReactComponent<{}> {
+ static Instance: TopBar;
+ @observable private _flipDocumentation = 0;
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ TopBar.Instance = this;
+ }
+
navigateToHome = () => {
(CollectionDockingView.Instance?.CaptureThumbnail() ?? new Promise<void>(res => res())).then(() => {
Doc.ActivePage = 'home';
@@ -102,7 +113,7 @@ export class TopBar extends React.Component {
Document={selDoc}
docViewPath={returnEmptyDocViewList}
fieldKey="data"
- dropAction="embed"
+ dropAction={dropActionType.embed}
styleProvider={DefaultStyleProvider}
select={emptyFunction}
isContentActive={returnTrue}
@@ -161,7 +172,6 @@ export class TopBar extends React.Component {
</div>
) : null;
}
-
/**
* Returns the right hand side of the topbar.
* This part of the topbar includes information about the current user,
@@ -183,7 +193,9 @@ export class TopBar extends React.Component {
/>
) : null}
<IconButton tooltip={'Issue Reporter ⌘I'} size={Size.SMALL} color={this.color} onClick={ReportManager.Instance.open} icon={<FaBug />} />
- <IconButton tooltip={'Documentation ⌘D'} size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+ <Flip key={this._flipDocumentation}>
+ <IconButton tooltip={'Documentation ⌘D'} size={Size.SMALL} color={this.color} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
+ </Flip>
<IconButton tooltip={'Settings ⌘⇧S'} size={Size.SMALL} color={this.color} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
<IconButton
size={Size.SMALL}
@@ -198,6 +210,11 @@ export class TopBar extends React.Component {
);
}
+ /**
+ * Make the documentation icon flip around to draw attention to it.
+ */
+ FlipDocumentationIcon = action(() => (this._flipDocumentation = this._flipDocumentation + 1));
+
render() {
return (
//TODO:glr Add support for light / dark mode
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 56d50846a..b1bdd50a7 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -391,7 +391,10 @@ export class Doc extends RefField {
export namespace Doc {
export function SetContainer(doc: Doc, container: Doc) {
- container !== Doc.MyRecentlyClosed && (doc.embedContainer = container);
+ if (container !== Doc.MyRecentlyClosed) {
+ doc.embedContainer = container;
+ Doc.AddEmbedding(doc, doc);
+ }
}
export function RunCachedUpdate(doc: Doc, field: string) {
const update = doc[CachedUpdates][field];
@@ -604,7 +607,7 @@ export namespace Doc {
const dataDoc = doc[DocData];
const availableEmbeddings = Doc.GetEmbeddings(dataDoc);
const bestEmbedding = [...(dataDoc !== doc ? [doc] : []), ...availableEmbeddings].find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail);
- bestEmbedding && Doc.AddDocToList(dataDoc, 'protoEmbeddings', doc, undefined, undefined, undefined, undefined, undefined, true);
+ bestEmbedding && Doc.AddDocToList(dataDoc, 'proto_embeddings', doc, undefined, undefined, undefined, undefined, undefined, true);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -984,10 +987,9 @@ export namespace Doc {
Object.keys(doc)
.filter(key => key.startsWith('acl'))
.forEach(key => (delegate[key] = doc[key]));
- if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
- Doc.AddEmbedding(doc, delegate);
+ if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
return delegate;
}
return undefined;
@@ -1008,7 +1010,6 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = delegateProto;
delegate.author = Doc.CurrentUserEmail;
- Doc.AddEmbedding(delegateProto, delegate);
delegate[Initializing] = false;
delegateProto[Initializing] = false;
return delegate;
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 8eeb52709..1cacfe30c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -96,9 +96,9 @@ export const documentSchema = createSchema({
// drag drop properties
_dragOnlyWithinContainer: 'boolean', // whether document can be dropped into a different collection
dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document.
- dropAction: 'string', // override specifying what should happen when something is dropped on this document (can be "embed", "copy", "move")
- dragAction: 'string', // override specifying what should happen when this document s dragged (can be "embed", "copy", "move")
- childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (can be "embed" or "copy")
+ dropAction: 'string', // override specifying what should happen when something is dropped on this document (dropActionType)
+ dragAction: 'string', // override specifying what should happen when this document s dragged (dropActionType)
+ childDragAction: 'string', // specify the override for what should happen when the child of a collection is dragged from it and dropped (dropActionType)
dropPropertiesToRemove: listSpec('string'), // properties that should be removed from the embed/copy/etc of this document when it is dropped
});