aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts8
-rw-r--r--src/client/documents/Documents.ts47
-rw-r--r--src/client/util/CurrentUserUtils.ts92
-rw-r--r--src/client/util/DocumentManager.ts55
-rw-r--r--src/client/util/DragManager.ts4
-rw-r--r--src/client/util/DropConverter.ts17
-rw-r--r--src/client/util/LinkManager.ts4
-rw-r--r--src/client/util/Scripting.ts3
-rw-r--r--src/client/util/SearchUtil.ts7
-rw-r--r--src/client/util/SelectionManager.ts9
-rw-r--r--src/client/util/SettingsManager.tsx12
-rw-r--r--src/client/views/ContextMenu.tsx18
-rw-r--r--src/client/views/DocComponent.tsx10
-rw-r--r--src/client/views/DocumentDecorations.tsx5
-rw-r--r--src/client/views/FilterPanel.tsx6
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/InkControlPtHandles.tsx2
-rw-r--r--src/client/views/MainView.tsx5
-rw-r--r--src/client/views/PropertiesButtons.tsx2
-rw-r--r--src/client/views/PropertiesDocBacklinksSelector.tsx2
-rw-r--r--src/client/views/PropertiesDocContextSelector.tsx6
-rw-r--r--src/client/views/PropertiesSection.scss26
-rw-r--r--src/client/views/PropertiesView.tsx180
-rw-r--r--src/client/views/StyleProvider.scss9
-rw-r--r--src/client/views/StyleProvider.tsx28
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx12
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx7
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx73
-rw-r--r--src/client/views/collections/TabDocView.tsx1
-rw-r--r--src/client/views/collections/TreeView.scss10
-rw-r--r--src/client/views/collections/TreeView.tsx34
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx318
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss13
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx25
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx82
-rw-r--r--src/client/views/collections/collectionFreeForm/index.ts12
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss7
-rw-r--r--src/client/views/collections/collectionSchema/SchemaRowBox.tsx2
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTableCell.tsx39
-rw-r--r--src/client/views/global/globalScripts.ts38
-rw-r--r--src/client/views/linking/LinkMenuItem.scss32
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx36
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx15
-rw-r--r--src/client/views/nodes/DataVizBox/components/Histogram.tsx6
-rw-r--r--src/client/views/nodes/DataVizBox/components/LineChart.tsx6
-rw-r--r--src/client/views/nodes/DataVizBox/components/PieChart.tsx6
-rw-r--r--src/client/views/nodes/DocumentLinksButton.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx99
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.scss10
-rw-r--r--src/client/views/nodes/FontIconBox/FontIconBox.tsx8
-rw-r--r--src/client/views/nodes/LabelBox.tsx2
-rw-r--r--src/client/views/nodes/LinkBox.scss25
-rw-r--r--src/client/views/nodes/LinkBox.tsx281
-rw-r--r--src/client/views/nodes/LinkDescriptionPopup.tsx18
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx4
-rw-r--r--src/client/views/nodes/formattedText/DashDocCommentView.tsx39
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss10
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.tsx27
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx65
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx16
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts151
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts5
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts12
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx7
-rw-r--r--src/fields/Doc.ts45
-rw-r--r--src/fields/DocSymbols.ts1
-rw-r--r--src/fields/List.ts2
-rw-r--r--src/server/DashUploadUtils.ts25
70 files changed, 1063 insertions, 1138 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 502cf7db7..e8bd35ac4 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -514,8 +514,8 @@ export function intersectRect(r1: { left: number; top: number; width: number; he
return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
}
-export function stringHash(s?:string) {
- return !s? undefined: Math.abs(s.split('').reduce((a: any, b: any) => ((a) => a & a)((a << 5) - a + b.charCodeAt(0)),0));
+export function stringHash(s?: string) {
+ return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (a => a & a)((a << 5) - a + b.charCodeAt(0)), 0));
}
export function percent2frac(percent: string) {
@@ -852,9 +852,11 @@ export function setupMoveUpEvents(
(target as any)._downX = (target as any)._lastX = e.clientX;
(target as any)._downY = (target as any)._lastY = e.clientY;
(target as any)._noClick = false;
+ var moving = false;
const _moveEvent = (e: PointerEvent): void => {
- if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
+ if (moving || Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
+ moving = true;
if ((target as any)._doubleTime) {
clearTimeout((target as any)._doubleTime);
(target as any)._doubleTime = undefined;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ac418ecfe..2d2f5fe4a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -252,6 +252,7 @@ export class DocumentOptions {
layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected');
_layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown');
layout_borderRounding?: string;
+ _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false);
_layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false);
_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');
@@ -437,7 +438,7 @@ export class DocumentOptions {
clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false);
onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop
target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script)
-
+ tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true);
treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view');
treeView_HideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeView_FieldKey tag (presBox)");
treeView_HideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)');
@@ -594,13 +595,12 @@ export namespace Docs {
layout: { view: LinkBox, dataField: 'link' },
options: {
childDontRegisterViews: true,
- onClick: FollowLinkScript(),
layout_hideLinkAnchors: true,
_height: 1,
_width: 1,
link: '',
link_description: '',
- backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area
+ color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area
_dropPropertiesToRemove: new List(['onClick']),
},
},
@@ -1459,7 +1459,7 @@ export namespace DocUtils {
const makeLink = action((linkDoc: Doc, showPopup?: number[]) => {
if (showPopup) {
- LinkManager.currentLink = linkDoc;
+ LinkManager.Instance.currentLink = linkDoc;
TaskCompletionBox.textDisplayed = 'Link Created';
TaskCompletionBox.popupX = showPopup[0];
@@ -1667,10 +1667,37 @@ export namespace DocUtils {
}
export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void {
+ const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+ .filter(btnDoc => !btnDoc.hidden)
+ .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
+ .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc.title)
+ .map((dragDoc, i) => ({
+ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
+ event: undoable((args: { x: number; y: number }) => {
+ const newDoc = DocUtils.copyDragFactory(dragDoc);
+ if (newDoc) {
+ newDoc.author = Doc.CurrentUserEmail;
+ newDoc.x = x;
+ newDoc.y = y;
+ EquationBox.SelectOnLoad = newDoc[Id];
+ if (newDoc.type === DocumentType.RTF) FormattedTextBox.SetSelectOnLoad(newDoc);
+ if (pivotField) {
+ newDoc[pivotField] = pivotValue;
+ }
+ docAdder?.(newDoc);
+ }
+ }, StrCast(dragDoc.title)),
+ icon: Doc.toIcon(dragDoc),
+ })) as ContextMenuProps[];
+ ContextMenu.Instance.addItem({
+ description: 'Create document',
+ subitems: documentList,
+ icon: 'file',
+ });
!simpleMenu &&
ContextMenu.Instance.addItem({
- description: 'Quick Notes',
- subitems: DocListCast((Doc.UserDoc()['template_notes'] as Doc).data).map((note, i) => ({
+ description: 'Styled Notes',
+ subitems: DocListCast((Doc.UserDoc().template_notes as Doc).data).map((note, i) => ({
description: ':' + StrCast(note.title),
event: undoable((args: { x: number; y: number }) => {
const textDoc = Docs.Create.TextDocument('', {
@@ -1691,14 +1718,14 @@ export namespace DocUtils {
})) as ContextMenuProps[],
icon: 'sticky-note',
});
- const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data)
+ const userDocList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[1]?.data)
.filter(btnDoc => !btnDoc.hidden)
.map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null))
.filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title)
.map((dragDoc, i) => ({
description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''),
event: undoable((args: { x: number; y: number }) => {
- const newDoc = DocUtils.copyDragFactory(dragDoc);
+ const newDoc = DocUtils.delegateDragFactory(dragDoc);
if (newDoc) {
newDoc.author = Doc.CurrentUserEmail;
newDoc.x = x;
@@ -1714,8 +1741,8 @@ export namespace DocUtils {
icon: Doc.toIcon(dragDoc),
})) as ContextMenuProps[];
ContextMenu.Instance.addItem({
- description: 'Create document',
- subitems: documentList,
+ description: 'User Templates',
+ subitems: userDocList,
icon: 'file',
});
} // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView)
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 8f46f844c..714e33d25 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -17,7 +17,7 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
import { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents";
import { DashboardView } from "../views/DashboardView";
import { OverlayView } from "../views/OverlayView";
-import { TreeViewType } from "../views/collections/CollectionTreeView";
+import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
import { Colors } from "../views/global/globalEnums";
import { media_state } from "../views/nodes/AudioBox";
import { OpenWhere } from "../views/nodes/DocumentView";
@@ -61,49 +61,16 @@ export let resolvedPorts: { server: number, socket: number };
export class CurrentUserUtils {
// initializes experimental advanced template views - slideView, headerView
- static setupExperimentalTemplateButtons(doc: Doc, tempDocs:Opt<Doc>, userBtns:Doc[]) {
- const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [
- {
- btnOpts: { title: "slide", icon: "address-card" },
- templateOpts: { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, isSystem: true },
- template: (opts:DocumentOptions) => Docs.Create.MultirowDocument(
- [
- Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }),
- Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
- ], opts)
- },
- {
- btnOpts: { title: "mobile", icon: "mobile" },
- templateOpts: { title: "NEW MOBILE BUTTON", onClick: undefined, },
- template: (opts:DocumentOptions) => this.mobileButton(opts,
- [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }),
- this.mobileTextContainer({},
- [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])
- ]
- )
- },
- ];
- const requiredTypes = [...requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => {
- const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title);
- const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' };
- const assignBtnAndTempOpts = (templateBtn:Opt<Doc>, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => {
- if (templateBtn) {
- DocUtils.AssignOpts(templateBtn,btnOpts);
- DocUtils.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions);
- }
- return templateBtn;
- };
- return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts);
- }), ...userBtns];
-
+ static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
+ const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
const reqdOpts:DocumentOptions = {
- title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true,
+ title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
_dragOnlyWithinContainer: true, _layout_hideContextMenu: true, isSystem: true, _forceActive: true,
_layout_autoHeight: true, _width: 500, _height: 300, _layout_fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
};
const reqdScripts = { dropConverter : "convertToButtons(dragData)" };
- const reqdFuncs = { hidden: "IsNoviceMode()" };
- return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs);
+ const reqdFuncs = { /* hidden: "IsNoviceMode()" */ };
+ return DocUtils.AssignScripts(DocUtils.AssignOpts(userDocTemplates, reqdOpts, userTemplates) ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs);
}
/// Initializes templates for editing click funcs of a document
@@ -111,7 +78,7 @@ export class CurrentUserUtils {
const tempClicks = DocCast(doc[field]);
const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true};
const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [
- { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"},
+ { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([this])))"},
{ opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
const reqdClickList = reqdTempOpts.map(opts => {
const allOpts = {...reqdClickOpts, ...opts.opts};
@@ -148,7 +115,7 @@ export class CurrentUserUtils {
static setupNoteTemplates(doc: Doc, field="template_notes") {
const tempNotes = DocCast(doc[field]);
const reqdTempOpts:DocumentOptions[] = [
- { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"},
+ { noteType: "Postit", backgroundColor: "yellow", icon: "sticky-note"},
{ noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" },
{ noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }];
const reqdNoteList = reqdTempOpts.map(opts => {
@@ -257,6 +224,16 @@ export class CurrentUserUtils {
MakeTemplate(Doc.GetProto(header), true, "Untitled Header");
return header;
}
+ const slideView = (opts:DocumentOptions) => {
+ const slide = Docs.Create.MultirowDocument(
+ [
+ Docs.Create.MulticolumnDocument([], { title: "hero", _height: 200, isSystem: true }),
+ Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
+ ], opts);
+
+ MakeTemplate(Doc.GetProto(slide), true, "Untitled Slide View");
+ 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
@@ -279,6 +256,7 @@ export class CurrentUserUtils {
{key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }},
{key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }},
{key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _layout_autoHeight: true, 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: "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,
@@ -307,6 +285,7 @@ export class CurrentUserUtils {
{ toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)},
{ toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
+ { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide),openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}},
{ toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} },
{ toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script
// { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack />" as any, openFactoryLocation: OpenWhere.overlay},
@@ -330,7 +309,7 @@ export class CurrentUserUtils {
});
const reqdOpts:DocumentOptions = {
- title: "Basic Item Creators", _layout_showTitle: "title", _xMargin: 0, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true, isSystem: true,
+ 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'
};
@@ -464,16 +443,17 @@ export class CurrentUserUtils {
/// Initializes the panel of draggable tools that is opened from the left sidebar.
static setupToolsBtnPanel(doc: Doc, field:string) {
const myTools = DocCast(doc[field]);
- const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined);
- const tempBtns = DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined;
- const userTemplateBtns = DocListCast(tempBtns?.data).filter(btn => !btn.isSystem);
- const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc, tempBtns, userTemplateBtns);
+ const allTools = DocListCast(myTools?.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);
+ //doc.myUserBtns = new PrefetchProxy(userBtns);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
layout_explainer: "This is a palette of documents that can be created.",
_layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _chromeHidden: true,
};
- return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]);
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, userBtns]);
}
/// initializes the left sidebar dashboard pane
@@ -522,29 +502,21 @@ export class CurrentUserUtils {
static setupFilesystem(doc: Doc, field:string) {
var myFilesystem = DocCast(doc[field]);
- const newFolder = `TreeView_addNewFolder()`;
const newFolderOpts: DocumentOptions = {
- _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
+ _forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
title: "New folder", color: Colors.BLACK, btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true
};
- const newFolderScript = { onClick: newFolder};
+ const newFolderScript = { onClick: CollectionTreeView.AddTreeFunc};
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,
isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true,
treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed",
- childContextMenuLabels: new List<string>(["Create new folder"]),
- childContextMenuIcons: new List<string>(["plus"]),
layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard."
};
const fileFolders = new Set(DocListCast(DocCast(doc[field])?.data));
- myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders));
- const childContextMenuScripts = [newFolder];
- if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) {
- myFilesystem.childContextMenuScripts = new List<ScriptField>(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!));
- }
- return myFilesystem;
+ return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders));
}
/// initializes the panel displaying docs that have been recently closed
@@ -564,8 +536,8 @@ export class CurrentUserUtils {
toolTip: "Empty recently closed",};
DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("this.target")});
- if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("self"))) {
- recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("self"))!])
+ if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("this"))) {
+ recentlyClosed.contextMenuScripts = new List<ScriptField>([ScriptField.MakeScript(clearAll("this"))!])
}
return recentlyClosed;
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index f730d17fe..7407fa2b3 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,6 +1,6 @@
import { Howl } from 'howler';
import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, Opt } from '../../fields/Doc';
import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
@@ -28,8 +28,6 @@ export class DocumentManager {
//global holds all of the nodes (regardless of which collection they're in)
@observable _documentViews = new Set<DocumentView>();
@observable.shallow public CurrentlyLoading: Doc[] = [];
- @observable.shallow public LinkAnchorBoxViews: DocumentView[] = [];
- @observable.shallow public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = [];
@computed public get DocumentViews() {
return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view)));
}
@@ -90,37 +88,14 @@ export class DocumentManager {
@action
public AddView = (view: DocumentView) => {
- if (view._props.LayoutTemplateString?.includes(KeyValueBox.name)) return;
- if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const viewAnchorIndex = view._props.LayoutTemplateString.includes('link_anchor_2') ? 'link_anchor_2' : 'link_anchor_1';
- const link = view.Document;
- this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.Document, link) && !dv._props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView =>
- this.LinkedDocumentViews.push({
- a: viewAnchorIndex === 'link_anchor_2' ? otherView : view,
- b: viewAnchorIndex === 'link_anchor_2' ? view : otherView,
- l: link,
- })
- );
- this.LinkAnchorBoxViews.push(view);
- } else {
+ if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) &&
+ !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
this.AddDocumentView(view);
- }
- this.callAddViewFuncs(view);
+ this.callAddViewFuncs(view);
+ } // prettier-ignore
};
public RemoveView = action((view: DocumentView) => {
- this.LinkedDocumentViews.slice().forEach(
- action(pair => {
- if (pair.a === view || pair.b === view) {
- const li = this.LinkedDocumentViews.indexOf(pair);
- li !== -1 && this.LinkedDocumentViews.splice(li, 1);
- }
- })
- );
-
- if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const index = this.LinkAnchorBoxViews.indexOf(view);
- this.LinkAnchorBoxViews.splice(index, 1);
- } else {
+ if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
this.DeleteDocumentView(view);
}
SelectionManager.DeselectView(view);
@@ -234,6 +209,20 @@ export class DocumentManager {
finished?.();
};
+ public static LinkCommonAncestor(linkDoc: Doc) {
+ const anchor = (which: number) => {
+ const anch = DocCast(linkDoc['link_anchor_' + which]);
+ const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
+ return DocumentManager.Instance.getDocumentView(anchor);
+ };
+ const anchor1 = anchor(1);
+ const anchor2 = anchor(2);
+ return anchor1
+ ?.docViewPath()
+ .reverse()
+ .find(ancestor => anchor2?.docViewPath().includes(ancestor));
+ }
+
// shows a documentView by:
// traverses down through the viewPath of contexts to the view:
// focusing on each context
@@ -355,8 +344,8 @@ export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomC
});
}
};
- if (Doc.IsDataProto(doc) && DocListCast(doc.proto_embeddings).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
- doc = DocListCast(doc.proto_embeddings).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
+ if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
+ doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
}
if (doc.hidden) {
doc.hidden = false;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a6ad0f1b3..1f093a33c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -504,9 +504,9 @@ 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.ctrlKey ? 'embed' : dragData.defaultDropAction;
+ dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.shiftKey ? 'move' : e.ctrlKey ? 'embed' : dragData.defaultDropAction;
}
- if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header') && dragData.draggedDocuments.length === 1) {
+ 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) {
startWindowDragTimer = setTimeout(async () => {
startWindowDragTimer = undefined;
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 8c3b56452..54066d267 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -3,7 +3,7 @@ import { DocData } from '../../fields/DocSymbols';
import { ObjectField } from '../../fields/ObjectField';
import { RichTextField } from '../../fields/RichTextField';
import { listSpec } from '../../fields/Schema';
-import { ScriptField } from '../../fields/ScriptField';
+import { ComputedField, ScriptField } from '../../fields/ScriptField';
import { Cast, StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
import { Docs } from '../documents/Documents';
@@ -39,15 +39,12 @@ function makeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = und
any = makeTemplate(d, false) || any;
}
});
- if (first) {
- if (!docs.length) {
- // bcz: feels hacky : if the root level document has items, it's not a field template
- any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData]) || any;
- }
- }
- if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
+ if (first && !docs.length) {
+ // bcz: feels hacky : if the root level document has items, it's not a field template
+ any = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || any;
+ } else if (layoutDoc[fieldKey] instanceof RichTextField || layoutDoc[fieldKey] instanceof ImageField) {
if (!StrCast(layoutDoc.title).startsWith('-')) {
- any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData]);
+ any = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true);
}
}
rename && (doc.title = rename);
@@ -76,12 +73,14 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
_nativeHeight: 100,
_width: 100,
_height: 100,
+ _layout_hideContextMenu: true,
backgroundColor: StrCast(doc.backgroundColor),
title: StrCast(layoutDoc.title),
btnType: ButtonType.ClickButton,
icon: 'bolt',
isSystem: false,
});
+ dbox.title = ComputedField.MakeFunction('this.dragFactory.title');
dbox.dragFactory = layoutDoc;
dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined;
dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)');
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 353f28a92..dd3b9bd07 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -23,8 +23,8 @@ import { ScriptingGlobals } from './ScriptingGlobals';
export class LinkManager {
@observable static _instance: LinkManager;
@observable.shallow userLinkDBs: Doc[] = [];
- @observable public static currentLink: Opt<Doc> = undefined;
- @observable public static currentLinkAnchor: Opt<Doc> = undefined;
+ @observable public currentLink: Opt<Doc> = undefined;
+ @observable public currentLinkAnchor: Opt<Doc> = undefined;
public static get Instance() {
return LinkManager._instance;
}
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index dfaacf318..f5e162d16 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -61,7 +61,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
const compiledFunction = (() => {
try {
return new Function(...paramNames, `return ${script}`);
- } catch {
+ } catch (e) {
+ console.log(e);
return undefined;
}
})();
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 218667d3e..fff2737b6 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -8,7 +8,7 @@ import { DocOptions, FInfo } from '../documents/Documents';
export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
- export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean) {
+ export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean, onlyKeys?: string[]) {
const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = matchKeyNames
? []
@@ -27,8 +27,9 @@ export namespace SearchUtil {
const dtype = StrCast(doc.type) as DocumentType;
if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) {
const hlights = new Set<string>();
- SearchUtil.documentKeys(doc).forEach(
- key => (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
+ (onlyKeys ?? SearchUtil.documentKeys(doc)).forEach(
+ key =>
+ (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
matchKeyNames ? key : Field.toString(doc[key] as Field))
&& hlights.add(key)
); // prettier-ignore
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index f2a327445..36b926053 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -38,6 +38,7 @@ export class SelectionManager {
this.Instance.SelectedViews.push(docView);
docView.IsSelected = true;
docView._props.whenChildContentsActiveChanged(true);
+ docView.ComponentView?.select?.(false, false);
}
});
@@ -51,9 +52,11 @@ export class SelectionManager {
public static DeselectAll = (except?: Doc): void => {
const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
- LinkManager.currentLink = undefined;
- LinkManager.currentLinkAnchor = undefined;
- runInAction(() => (this.Instance.SelectedSchemaDocument = undefined));
+ runInAction(() => {
+ LinkManager.Instance.currentLink = undefined;
+ LinkManager.Instance.currentLinkAnchor = undefined;
+ this.Instance.SelectedSchemaDocument = undefined;
+ });
this.Instance.SelectedViews.forEach(dv => {
dv.IsSelected = false;
dv._props.whenChildContentsActiveChanged(false);
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 5bf9e5b00..682704770 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -317,7 +317,17 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
{/* <NumberInput/> */}
<Group formLabel={'Default Font'}>
- <NumberDropdown color={SettingsManager.userColor} numberDropdownType={'input'} min={0} max={50} step={2} type={Type.TERT} number={0} unit={'px'} setNumber={() => {}} />
+ <NumberDropdown
+ color={SettingsManager.userColor}
+ numberDropdownType="slider"
+ min={0}
+ max={50}
+ step={2}
+ type={Type.PRIM}
+ number={NumCast(Doc.UserDoc().fontSize, Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')))}
+ unit={'px'}
+ setNumber={val => (Doc.UserDoc().fontSize = val + 'px')}
+ />
<Dropdown
items={fontFamilies.map(val => {
return {
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 8dcdd80e5..8c3c9df2e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -183,11 +183,12 @@ export class ContextMenu extends ObservableReactComponent<{}> {
@computed get menuItems() {
if (!this._searchString) {
- return this._items.map((item, ind) => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={ind + item.description} closeMenu={this.closeMenu} />);
+ return this._items.map((item, ind) => <ContextMenuItem key={item.description + ind} {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} closeMenu={this.closeMenu} />);
}
return this.filteredItems.map((value, index) =>
Array.isArray(value) ? (
<div
+ key={index + value.join(' -> ')}
className="contextMenu-group"
style={{
background: StrCast(SettingsManager.userVariantColor),
@@ -204,7 +205,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
return this._showSearch ? 1 : this._items.reduce((p, mi) => p + ((mi as any).noexpand ? 1 : (mi as any).subitems?.length || 1), 0) > 15;
}
+ _searchRef = React.createRef<HTMLInputElement>(); // bcz: we shouldn't need this, since we set autoFocus on the <input> tag, but for some reason we do...
+
render() {
+ this.itemsNeedSearch && setTimeout(() => this._searchRef.current?.focus());
return (
<div
className="contextMenu-cont"
@@ -226,7 +230,17 @@ export class ContextMenu extends ObservableReactComponent<{}> {
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
- <input style={{ color: 'black' }} className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
+ <input
+ ref={this._searchRef}
+ style={{ color: 'black' }}
+ className="contextMenu-item contextMenu-description search"
+ type="text"
+ placeholder="Filter Menu..."
+ value={this._searchString}
+ onKeyDown={this.onKeyDown}
+ onChange={this.onChange}
+ autoFocus
+ />
</span>
)}
{this.menuItems}
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 73fa6709c..3d5a5b945 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -33,8 +33,10 @@ export interface ViewBoxInterface {
getView?: (doc: Doc, options: FocusViewOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox
addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
+ removeDocument?: (doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean) => boolean; // add a document (used only by collections)
select?: (ctrlKey: boolean, shiftKey: boolean) => void;
focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
+ viewTransition?: () => Opt<string>; // duration of a view transition animation
isAnyChildContentActive?: () => boolean; // is any child content of the document active
onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
@@ -47,17 +49,17 @@ export interface ViewBoxInterface {
setData?: (data: Field | Promise<RefField | undefined>) => boolean;
componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null;
dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void;
- dragConfig?: (dragData: DragManager.DocumentDragData) => void;
+ dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag)
incrementalRendering?: () => void;
infoUI?: () => JSX.Element | null;
- screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>;
+ screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }>;
ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number };
snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number };
search?: (str: string, bwd?: boolean, clear?: boolean) => boolean;
}
/**
- * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's
+ * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's
* (note: this should not be used for the 'Box' views that render the contents of Doc views)
* Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal)
* */
@@ -189,7 +191,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() {
toRemove.forEach(doc => {
leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true);
- Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc, true);
+ Doc.RemoveEmbedding(doc, doc);
doc.embedContainer = undefined;
if (recent && !dontAddToRemoved) {
doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2193acf62..e9c4d9cc5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -114,6 +114,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) {
this._titleControlString = this._accumulatedTitle;
} else if (this._titleControlString.startsWith('#')) {
+ if (this._accumulatedTitle.startsWith('-->#')) {
+ SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`)));
+ }
const titleFieldKey = this._titleControlString.substring(1);
UndoManager.RunInBatch(
() =>
@@ -283,7 +286,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
} else {
var openDoc = selectedDocs[0].Document;
if (openDoc.layout_fieldKey === 'layout_icon') {
- openDoc = DocListCast(openDoc.proto_embeddings).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
+ openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc);
Doc.deiconifyView(openDoc);
}
LightboxView.Instance.SetLightboxDoc(
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index c4f65a5ca..818c81c9a 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -117,8 +117,8 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
// return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]);
// }
- gatherFieldValues(childDocs: Doc[], facetKey: string) {
- const valueSet = new Set<string>(StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]));
+ 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;
if (subDocs.length > 0) {
@@ -165,7 +165,7 @@ export class FilterPanel extends ObservableReactComponent<filterProps> {
@computed get activeRenderedFacetInfos() {
return new Set(
Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => {
- const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader);
+ const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.targetDoc.childFilters));
let nonNumbers = 0;
let minVal = Number.MAX_VALUE,
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index d134d9e7b..733383002 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -160,7 +160,7 @@ export class KeyManager {
if (LightboxView.LightboxDoc) {
LightboxView.Instance.SetLightboxDoc(undefined);
SelectionManager.DeselectAll();
- } else DocumentDecorations.Instance.onCloseClick(true);
+ } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true);
return { stopPropagation: true, preventDefault: true };
}
break;
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 7dd57e04d..31b13d2c8 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -189,6 +189,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
@observable _overStart: boolean = false;
@observable _overEnd: boolean = false;
+ _throttle = 0; // need to throttle dragging since the position may change when the control points change. this allows the stroke to settle so that we don't get increasingly bad jitter
@action
dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => {
SnappingManager.SetIsDragging(true);
@@ -196,6 +197,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
this,
e,
action(e => {
+ if (this._throttle++ % 2 !== 0) return false;
if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink');
// compute stretch factor by finding scaling along axis between start and end points
const p1 = pt1();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index eca0aca4c..b6cb845a6 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -10,6 +10,7 @@ import * as React from 'react';
import '../../../node_modules/browndash-components/dist/styles/global.min.css';
import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils';
import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { DocCast, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
@@ -50,7 +51,6 @@ import { CollectionDockingView } from './collections/CollectionDockingView';
import { CollectionMenu } from './collections/CollectionMenu';
import { TabDocView } from './collections/TabDocView';
import './collections/TreeView.scss';
-import { CollectionFreeFormLinksView } from './collections/collectionFreeForm';
import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
import { CollectionLinearView } from './collections/collectionLinear';
import { LinkMenu } from './linking/LinkMenu';
@@ -72,7 +72,6 @@ import { PresBox } from './nodes/trails';
import { AnchorMenu } from './pdf/AnchorMenu';
import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
import { TopBar } from './topbar/TopBar';
-import { DocData } from '../../fields/DocSymbols';
const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
const _global = (window /* browser */ || global) /* node */ as any;
@@ -641,7 +640,6 @@ export class MainView extends ObservableReactComponent<{}> {
}
@computed get mainDocView() {
const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView;
- console.log('Header = ' + this._hideUI + ' ' + this.headerBarDocHeight?.() + ' ' + headerBar);
return (
<>
{headerBar}
@@ -1037,7 +1035,6 @@ export class MainView extends ObservableReactComponent<{}> {
{/* <InkTranscription /> */}
{this.snapLines}
<LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
- <CollectionFreeFormLinksView />
<OverlayView />
<GPTPopup key="gptpopup" />
<SchemaCSVPopUp key="schemacsvpopup" />
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index bba6285c2..cb38ab602 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -411,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
docView.noOnClick();
switch (onClick) {
case 'enterPortal':
- docView.makeIntoPortal();
+ DocUtils.makeIntoPortal(docView.Document, docView.layoutDoc, docView.allLinks);
break;
case 'toggleDetail':
docView.setToggleDetail();
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx
index 244cd4aa0..cf5105efc 100644
--- a/src/client/views/PropertiesDocBacklinksSelector.tsx
+++ b/src/client/views/PropertiesDocBacklinksSelector.tsx
@@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component<PropertiesDo
const linkAnchor = this.props.Document;
const other = LinkManager.getOppositeAnchor(link, linkAnchor);
const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other;
- LinkManager.currentLink = link;
+ LinkManager.Instance.currentLink = link;
if (otherdoc) {
otherdoc.hidden = false;
this.props.addDocTab(Doc.IsDataProto(otherdoc) ? Doc.MakeDelegate(otherdoc) : otherdoc, OpenWhere.toggleRight);
diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx
index 54f141a36..361451c4d 100644
--- a/src/client/views/PropertiesDocContextSelector.tsx
+++ b/src/client/views/PropertiesDocContextSelector.tsx
@@ -28,14 +28,14 @@ export class PropertiesDocContextSelector extends ObservableReactComponent<Prope
if (!this._props.DocView) return [];
const target = this._props.DocView._props.Document;
const targetContext = this._props.DocView.containerViewPath?.().lastElement()?.Document;
- const embeddings = DocListCast(target.proto_embeddings);
+ 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 => DocListCast(container.proto_embeddings));
+ const containerSets = Array.from(containerProtos.keys()).map(container => Doc.GetEmbeddings(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 => DocListCast(dp.proto_embeddings));
+ const doclayoutSets = Array.from(containers.keys()).map(dp => Doc.GetEmbeddings(dp));
const doclayouts = Array.from(
doclayoutSets
.reduce((p, set) => {
diff --git a/src/client/views/PropertiesSection.scss b/src/client/views/PropertiesSection.scss
index 3f92a70f8..d32da1bf1 100644
--- a/src/client/views/PropertiesSection.scss
+++ b/src/client/views/PropertiesSection.scss
@@ -4,20 +4,20 @@
.propertiesView-content {
padding: 10px;
}
+}
- .propertiesView-sectionTitle {
- text-align: center;
- display: flex;
- padding: 3px 10px;
- font-size: 14px;
- font-weight: bold;
- justify-content: space-between;
- align-items: center;
+.propertiesView-sectionTitle {
+ text-align: center;
+ display: flex;
+ padding: 3px 10px;
+ font-size: 14px;
+ font-weight: bold;
+ justify-content: space-between;
+ align-items: center;
- .propertiesView-sectionTitle-icon {
- width: 20px;
- height: 20px;
- align-items: flex-end;
- }
+ .propertiesView-sectionTitle-icon {
+ width: 20px;
+ height: 20px;
+ align-items: flex-end;
}
}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 208ed56c9..e4e7bec32 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -41,6 +41,7 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView';
import { StyleProviderFuncType } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails';
+import { LinkBox } from './nodes/LinkBox';
const _global = (window /* browser */ || global) /* node */ as any;
interface PropertiesViewProps {
@@ -69,6 +70,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.Document || Doc.ActiveDashboard;
}
+ @computed get selectedLink() {
+ return this.selectedDocumentView?.ComponentView instanceof LinkBox ? this.selectedDocumentView.Document : LinkManager.Instance.currentLink;
+ }
+
@computed get selectedLayoutDoc() {
return SelectionManager.SelectedSchemaDoc || this.selectedDocumentView?.layoutDoc || Doc.ActiveDashboard;
}
@@ -110,7 +115,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
componentDidMount() {
this._disposers.link = reaction(
- () => LinkManager.currentLink,
+ () => this.selectedLink,
link => {
link && this.CloseAll();
link && (this.openLinks = true);
@@ -173,10 +178,14 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
if (this.dataDoc && this.selectedDoc) {
const ids = new Set<string>(reqdKeys);
const docs: Doc[] = SelectionManager.Views.length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc));
- docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && ids.add(key)));
+ docs.forEach(doc =>
+ Object.keys(doc)
+ .filter(filter)
+ .forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))
+ );
// prettier-ignore
- Array.from(ids).filter(filter).sort().map(key => {
+ Array.from(ids).sort().map(key => {
const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1;
const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key);
const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field);
@@ -263,20 +272,19 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get contextCount() {
if (this.selectedDocumentView) {
const target = this.selectedDocumentView.Document;
- const embeddings = DocListCast(target.proto_embeddings);
- return embeddings.length - 1;
+ return Doc.GetEmbeddings(target).length - 1;
} else {
return 0;
}
}
@computed get links() {
- const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
+ const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
return !selAnchor ? null : <PropertiesDocBacklinksSelector Document={selAnchor} hideTitle={true} addDocTab={this._props.addDocTab} />;
}
@computed get linkCount() {
- const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc;
+ const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc;
var counter = 0;
LinkManager.Links(selAnchor).forEach((l, i) => counter++);
@@ -572,19 +580,19 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
return (
<div>
<EditableText val={title} setVal={this.setTitle} color={this.color} type={Type.SEC} formLabel={'Title'} fillWidth />
- {LinkManager.currentLinkAnchor ? (
+ {LinkManager.Instance.currentLinkAnchor ? (
<p className="propertiesView-titleExtender">
<>
<b>Anchor:</b>
- {LinkManager.currentLinkAnchor.title}
+ {LinkManager.Instance.currentLinkAnchor.title}
</>
</p>
) : null}
- {LinkManager.currentLink?.title ? (
+ {this.selectedLink?.title ? (
<p className="propertiesView-titleExtender">
<>
<b>Link:</b>
- {LinkManager.currentLink.title}
+ {this.selectedLink.title}
</>
</p>
) : null}
@@ -738,7 +746,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
switch (field) {
case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break;
case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break;
- case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
+ case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.[DocData].stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break;
case 'wid':
const oldWidth = NumCast(selDoc._width);
const oldHeight = NumCast(selDoc._height);
@@ -783,7 +791,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
};
getField(key: string) {
- return Field.toString(this.selectedDoc?.[key] as Field);
+ return Field.toString(this.selectedDoc?.[DocData][key] as Field);
}
@computed get shapeXps() {
@@ -798,6 +806,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get shapeWid() {
return NumCast(this.selectedDoc?._width);
}
+ @computed get strokeThk() {
+ return NumCast(this.selectedDoc?.[DocData].stroke_width);
+ }
set shapeXps(value) {
this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100);
}
@@ -810,6 +821,9 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
set shapeHgt(value) {
this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100);
}
+ set strokeThk(value) {
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Math.round(value * 100) / 100);
+ }
@computed get hgtInput() {
return this.inputBoxDuo(
@@ -842,16 +856,16 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
private _lastDash: any = '2';
@computed get colorFil() {
- return StrCast(this.selectedDoc?.fillColor);
+ return StrCast(this.selectedDoc?.[DocData].fillColor);
}
@computed get colorStk() {
- return StrCast(this.selectedDoc?.color);
+ return StrCast(this.selectedDoc?.[DocData].color);
}
set colorFil(value) {
- this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined);
+ this.selectedDoc && (this.selectedDoc[DocData].fillColor = value ? value : undefined);
}
set colorStk(value) {
- this.selectedDoc && (this.selectedDoc.color = value ? value : undefined);
+ this.selectedDoc && (this.selectedDoc[DocData].color = value ? value : undefined);
}
colorButton(value: string, type: string, setter: () => void) {
@@ -940,19 +954,19 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
set dashdStk(value) {
value && (this._lastDash = value);
- this.selectedDoc && (this.selectedDoc.stroke_dash = value ? this._lastDash : undefined);
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_dash = value ? this._lastDash : undefined);
}
set markScal(value) {
- this.selectedDoc && (this.selectedDoc.stroke_markerScale = Number(value));
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_markerScale = Number(value));
}
set widthStk(value) {
- this.selectedDoc && (this.selectedDoc.stroke_width = Number(value));
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_width = Number(value));
}
set markHead(value) {
- this.selectedDoc && (this.selectedDoc.stroke_startMarker = value);
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_startMarker = value);
}
set markTail(value) {
- this.selectedDoc && (this.selectedDoc.stroke_endMarker = value);
+ this.selectedDoc && (this.selectedDoc[DocData].stroke_endMarker = value);
}
@computed get stkInput() {
@@ -993,53 +1007,11 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@computed get widthAndDash() {
return (
<div className="widthAndDash">
- <div className="width">
- <div className="width-top">
- <div className="width-title">Width:</div>
- <div className="width-input">{this.stkInput}</div>
- </div>
- <input
- className="width-range"
- type="range"
- style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- defaultValue={Number(this.widthStk)}
- min={1}
- max={100}
- onChange={action(e => (this.widthStk = e.target.value))}
- onMouseDown={e => {
- this._widthUndo = UndoManager.StartBatch('width undo');
- }}
- onMouseUp={e => {
- this._widthUndo?.end();
- this._widthUndo = undefined;
- }}
- />
- </div>
+ <div className="width">{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}</div>
+ <div className="width">{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}</div>
<div className="arrows">
<div className="arrows-head">
- <div className="width-top">
- <div className="width-title">Arrow Scale:</div>
- {/* <div className="width-input">{this.markScalInput}</div> */}
- </div>
- <input
- className="width-range"
- type="range"
- defaultValue={this.markScal}
- style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
- min={0}
- max={10}
- onChange={action(e => (this.markScal = +e.target.value))}
- onMouseDown={e => {
- this._widthUndo = UndoManager.StartBatch('scale undo');
- }}
- onMouseUp={e => {
- this._widthUndo?.end();
- this._widthUndo = undefined;
- }}
- />
- </div>
- <div className="arrows-head">
<div className="arrows-head-title">Arrow Head: </div>
<input key="markHead" className="arrows-head-input" type="checkbox" checked={this.markHead !== ''} onChange={undoBatch(action(() => (this.markHead = this.markHead ? '' : 'arrow')))} />
</div>
@@ -1223,10 +1195,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
}
@computed get description() {
- return Field.toString(LinkManager.currentLink?.link_description as any as Field);
+ return Field.toString(this.selectedLink?.link_description as any as Field);
}
@computed get relationship() {
- return StrCast(LinkManager.currentLink?.link_relationship);
+ return StrCast(this.selectedLink?.link_relationship);
}
@observable private relationshipButtonColor: string = '';
@@ -1236,7 +1208,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
handleDescriptionChange = undoable(
action((value: string) => {
- if (LinkManager.currentLink && this.selectedDoc) {
+ if (this.selectedLink) {
this.setDescripValue(value);
}
}),
@@ -1245,7 +1217,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
handlelinkRelationshipChange = undoable(
action((value: string) => {
- if (LinkManager.currentLink && this.selectedDoc) {
+ if (this.selectedLink) {
this.setlinkRelationshipValue(value);
}
}),
@@ -1254,17 +1226,17 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
@undoBatch
setDescripValue = action((value: string) => {
- if (LinkManager.currentLink) {
- Doc.GetProto(LinkManager.currentLink).link_description = value;
+ if (this.selectedLink) {
+ this.selectedLink[DocData].link_description = value;
}
});
@undoBatch
setlinkRelationshipValue = action((value: string) => {
- if (LinkManager.currentLink) {
- const prevRelationship = StrCast(LinkManager.currentLink.link_relationship);
- LinkManager.currentLink.link_relationship = value;
- Doc.GetProto(LinkManager.currentLink).link_relationship = value;
+ if (this.selectedLink) {
+ const prevRelationship = StrCast(this.selectedLink.link_relationship);
+ this.selectedLink.link_relationship = value;
+ Doc.GetProto(this.selectedLink).link_relationship = value;
const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList);
const linkRelationshipSizes = NumListCast(Doc.UserDoc().link_relationshipSizes);
const linkColorList = StrListCast(Doc.UserDoc().link_ColorList);
@@ -1348,20 +1320,20 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
};
toggleLinkProp = (e: React.PointerEvent, prop: string) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop]))));
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop]))));
};
@computed get destinationAnchor() {
- const ldoc = LinkManager.currentLink;
- const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
+ const ldoc = this.selectedLink;
+ const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch;
return ldoc ? DocCast(ldoc.link_anchor_2) : ldoc;
}
@computed get sourceAnchor() {
- const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor;
+ const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor;
- return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink);
+ return selAnchor ?? (this.selectedLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(this.selectedLink, this.destinationAnchor) : this.selectedLink);
}
toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => {
@@ -1387,7 +1359,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
autoComplete={'off'}
id="link_relationship_input"
- value={StrCast(LinkManager.currentLink?.link_relationship)}
+ value={StrCast(this.selectedLink?.link_relationship)}
onKeyDown={this.onRelationshipKey}
onBlur={this.onSelectOutRelationship}
onChange={e => this.handlelinkRelationshipChange(e.currentTarget.value)}
@@ -1404,7 +1376,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
autoComplete="off"
style={{ textAlign: 'left', color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}
id="link_description_input"
- value={StrCast(LinkManager.currentLink?.link_description)}
+ value={StrCast(this.selectedLink?.link_description)}
onKeyDown={this.onDescriptionKey}
onBlur={this.onSelectOutDesc}
onChange={e => this.handleDescriptionChange(e.currentTarget.value)}
@@ -1426,7 +1398,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
const zoom = Number((NumCast(this.sourceAnchor?.followLinkZoomScale, 1) * 100).toPrecision(3));
const targZoom = this.sourceAnchor?.followLinkZoom;
const indent = 30;
- const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
+ const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
return (
<>
@@ -1439,36 +1411,6 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
<p>Description</p>
{this.editDescription}
</div>
- <div className="propertiesView-input inline">
- <p>Show link</p>
- <button
- style={{ background: !LinkManager.currentLink?.link_displayLine ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_displayLine')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
- <p>Auto-move anchors</p>
- <button
- style={{ background: !LinkManager.currentLink?.link_autoMoveAnchors ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_autoMoveAnchors')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" />
- </button>
- </div>
- <div className="propertiesView-input inline" style={{ marginLeft: 10 }}>
- <p>Display arrow</p>
- <button
- style={{ background: !LinkManager.currentLink?.link_displayArrow ? '' : '#4476f7', borderRadius: 3 }}
- onPointerDown={e => this.toggleLinkProp(e, 'link_displayArrow')}
- onClick={e => e.stopPropagation()}
- className="propertiesButton">
- <FontAwesomeIcon className="fa-icon" icon={faArrowRight as IconLookup} size="lg" />
- </button>
- </div>
</div>
{!hasSelectedAnchor ? null : (
<div className="propertiesView-section">
@@ -1487,7 +1429,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
<option value={OpenWhere.add}>Opening in new tab</option>
<option value={OpenWhere.replace}>Replacing current tab</option>
<option value={OpenWhere.inParent}>Opening in same collection</option>
- {LinkManager.currentLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
+ {this.selectedLink?.linksToAnnotation ? <option value="openExternal">Open in external page</option> : null}
</select>
</div>
<div className="propertiesView-input inline first" style={{ display: 'grid', gridTemplateColumns: '84px calc(100% - 134px) 50px' }}>
@@ -1672,7 +1614,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
render() {
const isNovice = Doc.noviceMode;
- const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(LinkManager.currentLink!);
+ const hasSelectedAnchor = LinkManager.Links(this.sourceAnchor).includes(this.selectedLink!);
if (!this.selectedDoc && !this.isPres) {
return (
<div className="propertiesView" style={{ width: this._props.width }}>
@@ -1693,7 +1635,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
minWidth: this._props.width,
}}>
<div className="propertiesView-propAndInfoGrouping">
- <div className="propertiesView-title" style={{ width: this._props.width }}>
+ <div className="propertiesView-sectionTitle" style={{ width: this._props.width }}>
Properties
<div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/properties')}>
<IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
@@ -1703,12 +1645,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
<div className="propertiesView-name">{this.editableTitle}</div>
<div className="propertiesView-type"> {this.currentType} </div>
+ {this.fieldsSubMenu}
{this.optionsSubMenu}
{this.linksSubMenu}
- {!LinkManager.currentLink || !this.openLinks ? null : this.linkProperties}
+ {!this.selectedLink || !this.openLinks ? null : this.linkProperties}
{this.inkSubMenu}
{this.contextsSubMenu}
- {this.fieldsSubMenu}
{isNovice ? null : this.sharingSubMenu}
{this.filtersSubMenu}
{isNovice ? null : this.layoutSubMenu}
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 4d3096f71..30a026dbc 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -1,5 +1,7 @@
.styleProvider-filter,
.styleProvider-audio,
+.styleProvider-paint,
+.styleProvider-paint-selected,
.styleProvider-lock {
font-size: 10;
width: 15;
@@ -28,6 +30,13 @@
.styleProvider-audio {
right: 30;
}
+.styleProvider-paint-selected,
+.styleProvider-paint {
+ top: 15;
+}
+.styleProvider-paint-selected {
+ right: -30;
+}
.styleProvider-lock:hover,
.styleProvider-audio:hover,
.styleProvider-filter:hover {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 5a0167338..0794efe4c 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -9,7 +9,7 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
import { FaFilter } from 'react-icons/fa';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
import { DocViews } from '../../fields/DocSymbols';
-import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
import { DashColor, lightOrDark, Utils } from '../../Utils';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
@@ -53,6 +53,16 @@ export enum StyleProp {
function toggleLockedPosition(doc: Doc) {
UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
}
+function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldViewProps & DocumentViewProps>) {
+ const scriptProps = {
+ this: doc,
+ _readOnly_: false,
+ documentView: props?.DocumentView?.(),
+ value: undefined,
+ };
+ e.stopPropagation();
+ UndoManager.RunInBatch(() => doc && ScriptCast(doc.onPaint).script.run(scriptProps), 'togglePaintView');
+}
export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
return `M ${pw * 0.5} ${ph * inset} C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset} C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3} C ${
@@ -121,10 +131,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
case StyleProp.DocContents:return undefined;
case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
case StyleProp.Opacity: return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
- case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(doc?._text_fontSize, StrCast(Doc.UserDoc().fontSize)));
- case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
- case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
- case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
+ case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize));
+ case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily));
+ case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(Doc.UserDoc().fontWeight));
+ case StyleProp.FillColor: return StrCast(doc?._fillColor, StrCast(doc?.fillColor, StrCast(doc?.backgroundColor, 'transparent')));
case StyleProp.ShowCaption:return props?.hideCaptions || doc?._type_collection === CollectionViewType.Carousel ? undefined: StrCast(doc?._layout_showCaption);
case StyleProp.TitleHeight:return (props?.ScreenToLocalTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30);
case StyleProp.ShowTitle:
@@ -181,7 +191,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
: 0;
case StyleProp.BackgroundColor: {
if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
- let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
+ let docColor: Opt<string> = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
// prettier-ignore
switch (doc?.type) {
case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
@@ -267,6 +277,11 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
<FontAwesomeIcon icon='lock' size="lg" />
</div>
);
+ const paint = () => !doc?.onPaint ? null : (
+ <div className={`styleProvider-paint${props?.DocumentView?.().IsSelected ? "-selected":""}`} onClick={e => togglePaintView(e, doc, props)}>
+ <FontAwesomeIcon icon='pen' size="lg" />
+ </div>
+ );
const filter = () => {
const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
const showFilterIcon =
@@ -329,6 +344,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
};
return (
<>
+ {paint()}
{lock()}
{filter()}
{audio()}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 87973fd81..31ca86f0f 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -490,7 +490,7 @@ export class CollectionDockingView extends CollectionSubView() {
// 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.RemoveDocFromList(tab.DashDoc[DocData], 'proto_embeddings', tab.DashDoc);
+ Doc.RemoveEmbedding(tab.DashDoc, tab.DashDoc);
}
if (CollectionDockingView.Instance) {
const dview = CollectionDockingView.Instance.Document;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 89e72152a..54314f62c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -135,14 +135,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return docs.map((d, i) => {
const height = () => this.getDocHeight(d);
const width = () => this.getDocWidth(d);
+ const trans = () => this.getDocTransition(d);
// assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns
const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
// just getting the style
- const style = this.isStackingView ? { margin: this.Document._stacking_alignCenter ? 'auto' : undefined, width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
+ const style = this.isStackingView ? { margin: this.Document._stacking_alignCenter ? 'auto' : undefined, transition: trans(), width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
// So we're choosing whether we're going to render a column or a masonry doc
return (
<div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
- {this.getDisplayDoc(d, width, i)}
+ {this.getDisplayDoc(d, width, trans, i)}
</div>
);
});
@@ -309,7 +310,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
- getDisplayDoc(doc: Doc, width: () => number, count: number) {
+ getDisplayDoc(doc: Doc, width: () => number, trans: () => string, count: number) {
const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
const height = () => this.getDocHeight(doc);
const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight()));
@@ -330,6 +331,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
layout_fitWidth={this.childFitWidth}
isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive}
onKey={this.onKeyDown}
+ DataTransition={trans}
onBrowseClickScript={this._props.onBrowseClickScript}
isDocumentActive={this.isContentActive}
LayoutTemplate={this._props.childLayoutTemplate}
@@ -379,6 +381,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
return maxWidth;
}
+ getDocTransition(d?: Doc) {
+ if (!d) return '';
+ return StrCast(d.dataTransition);
+ }
getDocHeight(d?: Doc) {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index ee5147428..38f6aa3e7 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -200,8 +200,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
menuCallback = (x: number, y: number) => {
ContextMenu.Instance.clearItems();
- const docItems: ContextMenuProps[] = [];
- const keySet: Set<string> = new Set();
+ const keySet: Set<string> = new Set(['tags']);
this.childLayoutPairs.map(pair =>
this._allFacets
@@ -209,7 +208,9 @@ export class CollectionTimeView extends CollectionSubView() {
.filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0]))
.map(fieldKey => keySet.add(fieldKey))
);
- Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' }));
+
+ 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, ':');
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 741013148..786301136 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,8 +8,8 @@ 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 } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
+import { 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';
import { SelectionManager } from '../../util/SelectionManager';
@@ -27,6 +27,7 @@ import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionSubView } from './CollectionSubView';
import './CollectionTreeView.scss';
import { TreeView } from './TreeView';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
const _global = (window /* browser */ || global) /* node */ as any;
export type collectionTreeViewProps = {
@@ -51,6 +52,7 @@ export enum TreeViewType {
@observer
export class CollectionTreeView extends CollectionSubView<Partial<collectionTreeViewProps>>() {
+ public static AddTreeFunc = 'addTreeFolder(this.embedContainer)';
private _treedropDisposer?: DragManager.DragDropDisposer;
private _mainEle?: HTMLDivElement;
private _titleRef?: HTMLDivElement | HTMLInputElement | null;
@@ -140,6 +142,17 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
if ((this._mainEle = ele)) this._treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.Document, this.onInternalPreDrop.bind(this));
};
+ protected onInternalDrop(e: Event, de: DragManager.DropEvent) {
+ const res = super.onInternalDrop(e, de);
+ if (res && de.complete.docDragData) {
+ if (this.Document !== Doc.MyRecentlyClosed)
+ de.complete.docDragData.droppedDocuments.forEach(doc => {
+ if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+ });
+ }
+ return res;
+ }
+
protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData) {
@@ -150,9 +163,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
}
};
- configDrag = (dragData: DragManager.DocumentDragData) => {
- dragData.treeViewDoc = this.Document;
- };
+ dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document);
screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight);
@@ -163,34 +174,41 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
const value = DocListCast(targetDataDoc[this._props.fieldKey]);
const result = value.filter(v => !docs.includes(v));
if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll();
- if (result.length !== value.length && doc instanceof Doc) {
- const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
- const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
- this._props.removeDocument?.(doc);
- if (ind > 0 && prev) {
- FormattedTextBox.SetSelectOnLoad(prev);
- DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
+ if (result.length !== value.length) {
+ if (doc instanceof Doc) {
+ const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc);
+ const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1];
+ this._props.removeDocument?.(doc);
+ if (ind > 0 && prev) {
+ FormattedTextBox.SetSelectOnLoad(prev);
+ DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false);
+ }
+ return true;
}
- return true;
+ return this._props.removeDocument?.(doc) ?? false;
}
return false;
};
@action
addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => {
- const doAddDoc = (doc: Doc | Doc[]) =>
- (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => {
- const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before);
- res && Doc.SetContainer(doc, this.Document);
- return res;
- }, true);
+ const doclist = docs instanceof Doc ? [docs] : docs;
+ const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true);
if (this.Document.resolvedDataDoc instanceof Promise) return false;
- return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs);
+ const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs);
+ res &&
+ doclist.forEach(doc => {
+ Doc.SetContainer(doc, this.Document);
+ if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc);
+ });
+ return res;
};
onContextMenu = (e: React.MouseEvent): void => {
// need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
+ const layoutItems: ContextMenuProps[] = [];
+ const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc;
+ menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' });
if (!Doc.noviceMode) {
- const layoutItems: ContextMenuProps[] = [];
layoutItems.push({
description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'),
event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient),
@@ -198,7 +216,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
});
layoutItems.push({ description: (this.Document.treeView_HideHeaderFields ? 'Show' : 'Hide') + ' Header Fields', event: () => (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' });
layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' });
- ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
+ }
+ ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
+ if (!Doc.noviceMode) {
const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...');
const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : [];
onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' });
@@ -467,4 +487,13 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree
</div>
);
}
+ static addTreeFolder(container: Doc) {
+ TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
+ const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true };
+ return Doc.AddDocToList(container, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
+ }
}
+
+ScriptingGlobals.add(function addTreeFolder(doc: Doc) {
+ CollectionTreeView.addTreeFolder(doc);
+});
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 9bc3ef822..02aa76d82 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -520,6 +520,7 @@ interface TabMiniThumbProps {
miniLeft: () => number;
}
+@observer
class TabMiniThumb extends React.Component<TabMiniThumbProps> {
render() {
return <div className="miniThumb" style={{ width: `${this.props.miniWidth()}% `, height: `${this.props.miniHeight()}% `, left: `${this.props.miniLeft()}% `, top: `${this.props.miniTop()}% ` }} />;
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 0a1946f09..2ab1a5ac1 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -128,6 +128,10 @@
position: relative;
z-index: 1;
+ .treeView-rightButtons > .iconButton-container {
+ min-height: unset;
+ }
+
.treeView-background {
width: 100%;
height: 100%;
@@ -220,13 +224,13 @@
}
.treeView-header-above {
- border-top: black 1px solid;
+ border-top: red 1px solid;
}
.treeView-header-below {
- border-bottom: black 1px solid;
+ border-bottom: red 1px solid;
}
.treeView-header-inside {
- border: black 1px solid;
+ border: red 1px solid;
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index be5737a25..85f7cf7fe 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -383,7 +383,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
makeFolder = () => {
const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true });
TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView };
- return this._props.addDocument(folder);
+ return this.localAdd(folder);
};
preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => {
@@ -424,6 +424,16 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
return false;
};
+ localAdd = (doc: Doc | Doc[]) => {
+ const innerAdd = (doc: Doc) => {
+ const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
+ const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
+ dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
+ return added;
+ };
+ return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
+ };
+
dropping: boolean = false;
dropDocuments(
droppedDocuments: Doc[],
@@ -436,16 +446,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
canEmbed?: boolean
) {
const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before);
- const localAdd = (doc: Doc | Doc[]) => {
- const innerAdd = (doc: Doc) => {
- const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField;
- const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc);
- dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer));
- return added;
- };
- return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean);
- };
- const addDoc = inside ? localAdd : parentAddDoc;
+
+ const addDoc = inside ? this.localAdd : parentAddDoc;
const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd;
if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) {
const move = (!dropAction || canEmbed || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument;
@@ -839,14 +841,13 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
};
contextMenuItems = () => {
const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' };
- const folderOp = this.childDocs?.length ? [makeFolder] : [];
const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(this), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' };
const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' };
const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' };
return [
...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)),
...(this.Document.isFolder
- ? folderOp
+ ? [makeFolder]
: Doc.IsSystem(this.Document)
? []
: this.treeView.fileSysMode && this.Document === this.Document[DocData]
@@ -993,6 +994,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
onClickScript={this.onChildClick}
onDoubleClickScript={this.onChildDoubleClick}
dragAction={this._props.dragAction}
+ dragConfig={this.treeView.dragConfig}
moveDocument={this.move}
removeDocument={this._props.removeDoc}
ScreenToLocalTransform={this.getTransform}
@@ -1328,9 +1330,3 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
});
}
}
-
-ScriptingGlobals.add(function TreeView_addNewFolder() {
- TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
- const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true };
- return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
-});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
deleted file mode 100644
index b44acfce8..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.collectionfreeformlinkview-linkLine {
- stroke: black;
- opacity: 0.8;
- stroke-width: 3px;
- transition: opacity 0.5s ease-in;
- fill: transparent;
-}
-.collectionfreeformlinkview-linkText {
- stroke: rgb(0, 0, 0);
- pointer-events: all;
- cursor: move;
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
deleted file mode 100644
index f0a31a8c6..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ /dev/null
@@ -1,318 +0,0 @@
-import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, Field } from '../../../../fields/Doc';
-import { Brushed, DocCss } from '../../../../fields/DocSymbols';
-import { Id } from '../../../../fields/FieldSymbols';
-import { List } from '../../../../fields/List';
-import { Cast, NumCast, StrCast } from '../../../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { LinkManager } from '../../../util/LinkManager';
-import { SelectionManager } from '../../../util/SelectionManager';
-import { SettingsManager } from '../../../util/SettingsManager';
-import { SnappingManager } from '../../../util/SnappingManager';
-import { Colors } from '../../global/globalEnums';
-import { DocumentView } from '../../nodes/DocumentView';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
-import './CollectionFreeFormLinkView.scss';
-
-export interface CollectionFreeFormLinkViewProps {
- A: DocumentView;
- B: DocumentView;
- LinkDocs: Doc[];
-}
-
-@observer
-export class CollectionFreeFormLinkView extends ObservableReactComponent<CollectionFreeFormLinkViewProps> {
- @observable _opacity: number = 0;
- @observable _start = 0;
- _anchorDisposer: IReactionDisposer | undefined;
- _timeout: NodeJS.Timeout | undefined;
- constructor(props: any) {
- super(props);
- makeObservable(this);
- }
-
- componentWillUnmount() {
- this._anchorDisposer?.();
- }
- @action timeout: any = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25)));
- componentDidMount() {
- this._anchorDisposer = reaction(
- () => [
- this._props.A.screenToViewTransform(),
- Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
- Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
- this._props.B.screenToViewTransform(),
- Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
- Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss],
- ],
- action(() => {
- this._start = Date.now();
- this._timeout && clearTimeout(this._timeout);
- this._timeout = setTimeout(this.timeout, 25);
- setTimeout(this.placeAnchors, 10); // when docs are dragged, their transforms will update before a render has been performed. placeanchors needs to come after a render to find things in the dom. a 0 timeout will still come before the render
- }),
- { fireImmediately: true }
- );
- }
- placeAnchors = () => {
- const { A, B, LinkDocs } = this._props;
- const linkDoc = LinkDocs[0];
- if (SnappingManager.IsDragging || !A.ContentDiv || !B.ContentDiv) return;
- setTimeout(
- action(() => (this._opacity = 0.75)),
- 0
- ); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
- setTimeout(
- action(() => (!LinkDocs.length || !(linkDoc.link_displayLine || Doc.UserDoc().showLinkLines)) && (this._opacity = 0.05)),
- 750
- ); // this will unhighlight the link line.
- const a = A.ContentDiv.getBoundingClientRect();
- const b = B.ContentDiv.getBoundingClientRect();
- const { left: aleft, top: atop, width: awidth, height: aheight } = A.ContentDiv.parentElement!.getBoundingClientRect();
- const { left: bleft, top: btop, width: bwidth, height: bheight } = B.ContentDiv.parentElement!.getBoundingClientRect();
- const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2);
- const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y);
-
- // really hacky stuff to make the LinkAnchorBox display where we want it to:
- // if there's an element in the DOM with a classname containing a link anchor's id,
- // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
- // otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_1 as Doc)[Id])).lastElement();
- const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_2 as Doc)[Id])).lastElement();
- if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return;
- if (!targetAhyperlink) {
- if (linkDoc.link_autoMoveAnchors) {
- linkDoc.link_anchor_1_x = ((apt.point.x - aleft) / awidth) * 100;
- linkDoc.link_anchor_1_y = ((apt.point.y - atop) / aheight) * 100;
- }
- } else {
- const m = targetAhyperlink.getBoundingClientRect();
- const mp = A.screenToViewTransform().transformPoint(m.right, m.top + 5);
- const mpx = mp[0] / A._props.PanelWidth();
- const mpy = mp[1] / A._props.PanelHeight();
- if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100;
- if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100;
- if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0;
- else linkDoc.opacity = 1;
- }
- if (!targetBhyperlink) {
- if (linkDoc.link_autoMoveAnchors) {
- linkDoc.link_anchor_2_x = ((bpt.point.x - bleft) / bwidth) * 100;
- linkDoc.link_anchor_2_y = ((bpt.point.y - btop) / bheight) * 100;
- }
- } else {
- const m = targetBhyperlink.getBoundingClientRect();
- const mp = B.screenToViewTransform().transformPoint(m.right, m.top + 5);
- const mpx = mp[0] / B._props.PanelWidth();
- const mpy = mp[1] / B._props.PanelHeight();
- if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100;
- if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100;
- if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0;
- else linkDoc.opacity = 1;
- }
- };
-
- pointerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(
- this,
- e,
- (e, down, delta) => {
- this._props.LinkDocs[0].link_relationship_OffsetX = NumCast(this._props.LinkDocs[0].link_relationship_OffsetX) + delta[0];
- this._props.LinkDocs[0].link_relationship_OffsetY = NumCast(this._props.LinkDocs[0].link_relationship_OffsetY) + delta[1];
- return false;
- },
- emptyFunction,
- action(() => {
- SelectionManager.DeselectAll();
- SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
- LinkManager.currentLink = this._props.LinkDocs[0];
- this.toggleProperties();
- // OverlayView.Instance.addElement(
- // <LinkEditor sourceDoc={this._props.A.Document} linkDoc={this._props.LinkDocs[0]}
- // showLinks={action(() => { })}
- // />, { x: 300, y: 300 });
- })
- );
- };
-
- visibleY = (el: any) => {
- let rect = el.getBoundingClientRect();
- const top = rect.top,
- height = rect.height;
- var el = el.parentNode;
- while (el && el !== document.body) {
- if (el.className === 'tabDocView-content') break;
- rect = el.getBoundingClientRect?.();
- if (rect?.width) {
- if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom;
- // Check if the element is out of view due to a container scrolling
- if (top + height <= rect.top && getComputedStyle(el).overflow === 'hidden') return rect.top;
- }
- el = el.parentNode;
- }
- // Check its within the document viewport
- return top; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
- };
- visibleX = (el: any) => {
- let rect = el.getBoundingClientRect();
- const left = rect.left,
- width = rect.width;
- var el = el.parentNode;
- while (el && el !== document.body) {
- rect = el?.getBoundingClientRect();
- if (rect?.width) {
- if (left <= rect.right === false && getComputedStyle(el).overflow === 'hidden') return rect.right;
- // Check if the element is out of view due to a container scrolling
- if (left + width <= rect.left && getComputedStyle(el).overflow === 'hidden') return rect.left;
- }
- el = el.parentNode;
- }
- // Check its within the document viewport
- return left; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden";
- };
-
- @action
- toggleProperties = () => {
- if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
- SettingsManager.Instance.propertiesWidth = 250;
- }
- };
-
- @action
- onClickLine = () => {
- SelectionManager.DeselectAll();
- SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true);
- LinkManager.currentLink = this._props.LinkDocs[0];
- this.toggleProperties();
- };
-
- @computed.struct get renderData() {
- this._start;
- SnappingManager.IsDragging;
- const { A, B, LinkDocs } = this._props;
- if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
- const acont = A.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
- const bcont = B.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
- const adiv = acont.length ? acont[0] : A.ContentDiv;
- const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
- for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
- for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return;
- const a = adiv.getBoundingClientRect();
- const b = bdiv.getBoundingClientRect();
- const atop = this.visibleY(adiv);
- const btop = this.visibleY(bdiv);
- if (!a.width || !b.width) return undefined;
- const aDocBounds = (A._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
- const bDocBounds = (B._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 };
- const aleft = this.visibleX(adiv);
- const bleft = this.visibleX(bdiv);
- const aclipped = aleft !== a.left || atop !== a.top;
- const bclipped = bleft !== b.left || btop !== b.top;
- if (aclipped && bclipped) return undefined;
- const clipped = aclipped || bclipped;
- const pt1inside = NumCast(LinkDocs[0].link_anchor_1_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_1_y) % 100 !== 0;
- const pt2inside = NumCast(LinkDocs[0].link_anchor_2_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_2_y) % 100 !== 0;
- const pt1 = [aleft + a.width / 2, atop + a.height / 2];
- const pt2 = [bleft + b.width / 2, btop + b.width / 2];
- const pt2vec = pt2inside ? [-0.7071, 0.7071] : [(bDocBounds.left + bDocBounds.right) / 2 - pt2[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt2[1]];
- const pt1vec = pt1inside ? [-0.7071, 0.7071] : [(aDocBounds.left + aDocBounds.right) / 2 - pt1[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt1[1]];
- const pt1len = Math.sqrt(pt1vec[0] * pt1vec[0] + pt1vec[1] * pt1vec[1]);
- const pt2len = Math.sqrt(pt2vec[0] * pt2vec[0] + pt2vec[1] * pt2vec[1]);
- const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
- const pt1norm = clipped ? [0, 0] : [-(pt1vec[0] / pt1len) * ptlen, -(pt1vec[1] / pt1len) * ptlen];
- const pt2norm = clipped ? [0, 0] : [-(pt2vec[0] / pt2len) * ptlen, -(pt2vec[1] / pt2len) * ptlen];
- const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1;
- const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1;
- const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen];
- const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen];
- const aActive = A.IsSelected || A.Document[Brushed];
- const bActive = B.IsSelected || B.Document[Brushed];
-
- const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
- const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
- const link = this._props.LinkDocs[0];
- return {
- a,
- b,
- pt1norm,
- pt2norm,
- aActive,
- bActive,
- textX,
- textY,
- // fully connected
- // pt1,
- // pt2,
- // this code adds space between links
- pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt1,
- pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt2,
- };
- }
-
- render() {
- if (!this.renderData) return null;
-
- const link = this._props.LinkDocs[0];
- const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
- const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship
- const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>;
- const linkColorList = Doc.UserDoc().link_ColorList as List<string>;
- const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>;
- const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
- const linkDescription = Field.toString(link.link_description as any as Field).split('\n')[0];
-
- const linkSize = Doc.noviceMode || currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
-
- //access stroke color using index of the relationship in the color list (default black)
- const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex];
- // const hexStroke = this.rgbToHex(stroke)
-
- //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has)
- //thickness varies linearly from 3px to 12px for increasing link count
- const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px';
-
- const arrowScale = NumCast(link.link_displayArrow_scale, 3);
- return link.opacity === 0 || !a.width || !b.width || (!(Doc.UserDoc().showLinkLines || link.link_displayLine) && !aActive && !bActive) ? null : (
- <>
- <defs>
- <marker id={`${link[Id] + 'arrowhead'}`} markerWidth={`${4 * arrowScale}`} markerHeight={`${3 * arrowScale}`} refX="0" refY={`${1.5 * arrowScale}`} orient="auto">
- <polygon points={`0 0, ${3 * arrowScale} ${1.5 * arrowScale}, 0 ${3 * arrowScale}`} fill={stroke} />
- </marker>
- <filter id="outline">
- <feMorphology in="SourceAlpha" result="expanded" operator="dilate" radius="1" />
- <feFlood floodColor={`${Colors.DARK_GRAY}`} />
- <feComposite in2="expanded" operator="in" />
- <feComposite in="SourceGraphic" />
- </filter>
- <filter x="0" y="0" width="1" height="1" id={`${link[Id] + 'background'}`}>
- <feFlood floodColor={`${StrCast(link._backgroundColor, 'white')}`} result="bg" />
- <feMerge>
- <feMergeNode in="bg" />
- <feMergeNode in="SourceGraphic" />
- </feMerge>
- </filter>
- </defs>
- <path
- filter={LinkManager.currentLink === link ? 'url(#outline)' : ''}
- fill="pink"
- stroke="antiquewhite"
- strokeWidth="4"
- className="collectionfreeformlinkview-linkLine"
- style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }}
- onClick={this.onClickLine}
- d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
- markerEnd={link.link_displayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
- />
- {textX === undefined || !linkDescription ? null : (
- <text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
- <tspan>&nbsp;</tspan>
- <tspan dy="2">{linkDescription.substring(0, 50) + (linkDescription.length > 50 ? '...' : '')}</tspan>
- <tspan dy="2">&nbsp;</tspan>
- </text>
- )}
- </>
- );
- }
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
deleted file mode 100644
index 4ada1731f..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss
+++ /dev/null
@@ -1,13 +0,0 @@
-// TODO: change z-index to -1 when a modal is active?
-
-.collectionfreeformlinksview-svgCanvas {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
-}
-.collectionfreeformlinksview-container {
- pointer-events: none;
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
deleted file mode 100644
index e5b6c366f..000000000
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { computed } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Id } from '../../../../fields/FieldSymbols';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { LightboxView } from '../../LightboxView';
-import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView';
-import './CollectionFreeFormLinksView.scss';
-
-@observer
-export class CollectionFreeFormLinksView extends React.Component {
- @computed get uniqueConnections() {
- return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews))
- .filter(c => !LightboxView.LightboxDoc || (LightboxView.Contains(c.a) && LightboxView.Contains(c.b)))
- .map(c => <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />);
- }
-
- render() {
- return (
- <div className="collectionfreeformlinksview-container">
- <svg className="collectionfreeformlinksview-svgCanvas">{this.uniqueConnections}</svg>
- </div>
- );
- }
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ec3ac4174..a69030019 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,11 +5,12 @@ import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
import { DocData, Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
+import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
@@ -22,8 +23,8 @@ import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
-import { FollowLinkScript } from '../../../util/LinkFollower';
import { ReplayMovements } from '../../../util/ReplayMovements';
+import { CompileScript } from '../../../util/Scripting';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
import { freeformScrollMode } from '../../../util/SettingsManager';
@@ -75,11 +76,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, '');
@computed get paintFunc() {
- const paintFunc = StrCast(this.Document[DocData].paintFunc).trim();
+ const field = this.layoutDoc[this.fieldKey];
+ const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim();
return !paintFunc
? ''
- : `const dashDiv = document.querySelector('#${this._paintedId}');
- (async () => { ${paintFunc} })()`;
+ : paintFunc.includes('dashDiv')
+ ? `const dashDiv = document.querySelector('#${this._paintedId}');
+ (async () => { ${paintFunc} })()`
+ : paintFunc;
}
constructor(props: any) {
super(props);
@@ -233,6 +237,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick);
onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
elementFunc = () => this._layoutElements;
+ viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined);
fitContentOnce = () => {
const vals = this.fitToContentVals;
this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -371,28 +376,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
- if (this.layoutDoc._autoArrange || de.metaKey) {
- const sorted = this.childLayoutPairs.slice().sort((a, b) => NumCast(a.layout.y) - NumCast(b.layout.y));
- sorted.splice(
- sorted.findIndex(pair => pair.layout === refDoc),
- 1
- );
- if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
- const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2;
- const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
- const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
-
- let lastx = NumCast(refDoc.x);
- let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
- runInAction(() =>
- sorted.slice(1).forEach((pair, i) => {
- lastx = pair.layout.x = lastx + deltax;
- lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
- })
- );
- }
- }
-
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
return true;
}
@@ -419,27 +402,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const [x, y] = this.screenToFreeformContentsXf.transformPoint(de.x, de.y);
let added = false;
// do nothing if link is dropped into any freeform view parent of dragged document
- const source =
- !linkDragData.dragDocument.embedContainer || linkDragData.dragDocument.embedContainer !== this.Document
- ? Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' })
- : Docs.Create.FontIconDocument({
- title: 'anchor',
- icon_label: '',
- followLinkToggle: true,
- icon: 'map-pin',
- x,
- y,
- backgroundColor: '#ACCEF7',
- layout_hideAllLinks: true,
- layout_hideLinkButton: true,
- _width: 15,
- _height: 15,
- _xPadding: 0,
- onClick: FollowLinkScript(),
- });
+ const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x, y, title: 'dropped annotation' });
added = this._props.addDocument?.(source) ? true : false;
de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
-
+ if (de.complete.linkDocument) {
+ de.complete.linkDocument.layout_isSvg = true;
+ this.addDocument(de.complete.linkDocument);
+ }
e.stopPropagation();
!added && e.preventDefault();
return added;
@@ -1008,7 +977,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!this.isAnnotationOverlay && clamp) {
// this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
- const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
+ const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc && doc.type !== DocumentType.LINK);
const measuredDocs = docs
.map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc._width), height: NumCast(doc._height) } }))
.filter(({ pos, size }) => pos && size)
@@ -1494,7 +1463,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.paintFunc = reaction(
() => ({ code: this.paintFunc, first: this._firstRender, width: this.Document._width, height: this.Document._height }),
- ({ code, first }) => code && !first && eval(code),
+ ({ code, first }) => {
+ 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);
+ },
{ fireImmediately: true }
);
@@ -1641,9 +1615,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
toggleResetView = () => {
this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey];
if (this.dataDoc[this.autoResetFieldKey]) {
- this.dataDoc[this.panXFieldKey + '_reset'] = this.dataDoc[this.panXFieldKey];
- this.dataDoc[this.panYFieldKey + '_reset'] = this.dataDoc[this.panYFieldKey];
- this.dataDoc[this.scaleFieldKey + '_reset'] = this.dataDoc[this.scaleFieldKey];
+ this.dataDoc[this.panXFieldKey + '_reset'] = this.layoutDoc[this.panXFieldKey];
+ this.dataDoc[this.panYFieldKey + '_reset'] = this.layoutDoc[this.panYFieldKey];
+ this.dataDoc[this.scaleFieldKey + '_reset'] = this.layoutDoc[this.scaleFieldKey];
}
};
@@ -1654,12 +1628,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : [];
!this.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' });
!this.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' });
- !Doc.noviceMode &&
- appearanceItems.push({
- description: 'Toggle auto arrange',
- event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
- icon: 'compress-arrows-alt',
- });
if (this._props.setContentViewBox === emptyFunction) {
!appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
return;
diff --git a/src/client/views/collections/collectionFreeForm/index.ts b/src/client/views/collections/collectionFreeForm/index.ts
index 702dc8d42..9a54ce63a 100644
--- a/src/client/views/collections/collectionFreeForm/index.ts
+++ b/src/client/views/collections/collectionFreeForm/index.ts
@@ -1,7 +1,5 @@
-export * from "./CollectionFreeFormLayoutEngines";
-export * from "./CollectionFreeFormLinkView";
-export * from "./CollectionFreeFormLinksView";
-export * from "./CollectionFreeFormRemoteCursors";
-export * from "./CollectionFreeFormView";
-export * from "./MarqueeOptionsMenu";
-export * from "./MarqueeView"; \ No newline at end of file
+export * from './CollectionFreeFormLayoutEngines';
+export * from './CollectionFreeFormRemoteCursors';
+export * from './CollectionFreeFormView';
+export * from './MarqueeOptionsMenu';
+export * from './MarqueeView';
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index ee61c4141..fed4e89cf 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -132,7 +132,7 @@
.row-menu {
display: flex;
- justify-content: flex-end;
+ justify-content: center;
}
}
@@ -228,7 +228,10 @@
display: flex;
flex-direction: row;
min-width: 50px;
- justify-content: flex-end;
+ justify-content: center;
+ .iconButton-container {
+ min-width: unset !important;
+ }
}
.row-cells {
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index f2fe0dde7..39fea2d2e 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -121,7 +121,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
}}>
<IconButton
- tooltip="whether document interations are enabled"
+ tooltip="whether document interactions are enabled"
icon={this.Document._lockedPosition ? <CgLockUnlock size="12px" /> : <CgLock size="12px" />}
size={Size.XSMALL}
onPointerDown={e =>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index dbaa6e110..001ad5ab6 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -24,6 +24,11 @@ import { KeyValueBox } from '../../nodes/KeyValueBox';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
import { ColumnType, FInfotoColType } from './CollectionSchemaView';
import './CollectionSchemaView.scss';
+import 'react-datepicker/dist/react-datepicker.css';
+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';
export interface SchemaTableCellProps {
Document: Doc;
@@ -162,7 +167,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />;
case ColumnType.RTF: return <SchemaRTFCell {...this._props} />;
case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => val.toString())} />;
- case ColumnType.Date: // return <SchemaDateCell {...this._props} />;
+ case ColumnType.Date: return <SchemaDateCell {...this._props} />;
default: return this.defaultCellContent;
}
}
@@ -260,19 +265,39 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp
return DateCast(this._props.Document[this._props.fieldKey]);
}
- @action
- handleChange = (date: any) => {
+ handleChange = undoable((date: Date | null) => {
// const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
// if (script.compiled) {
// this.applyToDoc(this._document, this._props.row, this._props.col, script.run);
// } else {
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
- this._props.Document[this._props.fieldKey] = new DateField(date as Date);
+ date && (this._props.Document[this._props.fieldKey] = new DateField(date));
//}
- };
+ }, 'date change');
render() {
- return <DatePicker dateFormat={'Pp'} selected={this.date.date} onChange={(date: any) => this.handleChange(date)} />;
+ const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props);
+ return (
+ <>
+ <div style={{ pointerEvents: 'none' }}>
+ <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={e => {}} />
+ </div>
+ {pointerEvents === 'none' ? null : (
+ <Popup
+ icon={<FontAwesomeIcon size="sm" icon="caret-down" />}
+ size={Size.XSMALL}
+ type={Type.TERT}
+ color={SettingsManager.userColor}
+ background={SettingsManager.userBackgroundColor}
+ popup={
+ <div style={{ width: 'fit-content', height: '200px' }}>
+ <DatePicker open={true} dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={this.handleChange} />
+ </div>
+ }
+ />
+ )}
+ </>
+ );
}
}
@observer
@@ -394,7 +419,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
}),
}}
menuPortalTarget={this._props.menuTarget}
- menuPosition={'absolute'}
+ menuPosition="absolute"
placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')}
options={options}
isMulti={false}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3084a7972..51672513b 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -20,6 +20,7 @@ import { DocumentView } from '../nodes/DocumentView';
import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
import { WebBox } from '../nodes/WebBox';
import { VideoBox } from '../nodes/VideoBox';
+import { DocData } from '../../../fields/DocSymbols';
ScriptingGlobals.add(function IsNoneSelected() {
return SelectionManager.Views.length <= 0;
@@ -42,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
} else if (selectedViews.length) {
if (checkResult) {
const selView = selectedViews.lastElement();
- const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent';
}
selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent');
selectedViews.forEach(dv => {
- const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
+ const fieldKey = dv.Document._layout_isSvg ? 'fillColor' : 'backgroundColor';
const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
if (contentFrameNumber !== undefined) {
@@ -57,16 +58,16 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
obj[fieldKey] = color;
CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj);
} else {
- dv.Document['_' + fieldKey] = color;
+ dv.Document[DocData][fieldKey] = color;
}
});
} else {
- const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.currentLink ? [LinkManager.currentLink] : [];
+ const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : [];
if (checkResult) {
return selected.lastElement()?._backgroundColor ?? 'transparent';
}
SetActiveFillColor(color ?? 'transparent');
- selected.forEach(doc => (doc._backgroundColor = color));
+ selected.forEach(doc => (doc[DocData].backgroundColor = color));
}
});
@@ -126,11 +127,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
}],
- ['arrange', {
- waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
- checkResult: (doc:Doc) => BoolCast(doc?._autoArrange, false),
- setDoc: (doc:Doc,dv:DocumentView) => doc._autoArrange = !doc._autoArrange,
- }],
['flashcards', {
checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
@@ -343,24 +339,24 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
// prettier-ignore
const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
['inkMask', {
- checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask())),
- setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask),
+ checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())),
+ setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask),
setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
}],
['fillColor', {
- checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"),
- setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
+ checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"),
+ setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)),
setMode: () => SetActiveFillColor(StrCast(value)),
}],
[ 'strokeWidth', {
- checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()),
- setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)),
- setMode: () => { SetActiveInkWidth(value.toString()); setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+ checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()),
+ setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)),
+ setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
['strokeColor', {
- checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
- setInk: (doc: Doc) => (doc.color = String(value)),
- setMode: () => { SetActiveInkColor(StrCast(value)); setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
+ checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()),
+ setInk: (doc: Doc) => (doc[DocData].color = String(value)),
+ setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);},
}],
]);
@@ -368,7 +364,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | '
return map.get(option)?.checkResult();
}
map.get(option)?.setMode();
- SelectionManager.Docs.filter(doc => doc.type === DocumentType.INK).map(doc => map.get(option)?.setInk(doc));
+ SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc));
});
/** WEB
diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss
index 44c74236f..66ddd6eca 100644
--- a/src/client/views/linking/LinkMenuItem.scss
+++ b/src/client/views/linking/LinkMenuItem.scss
@@ -18,6 +18,21 @@
align-items: center;
display: flex;
+ .linkMenu-icon-wrapper {
+ //border: 0.5px solid rgb(161, 161, 161);
+ margin-right: 2px;
+ padding-right: 2px;
+
+ .linkMenu-icon {
+ float: left;
+ width: 12px;
+ height: 12px;
+ padding: 1px;
+ margin-right: 3px;
+ // color: rgb(161, 161, 161);
+ }
+ }
+
.linkMenu-text {
width: 100%;
@@ -34,22 +49,6 @@
display: flex;
align-items: center;
min-height: 20px;
-
- .destination-icon-wrapper {
- //border: 0.5px solid rgb(161, 161, 161);
- margin-right: 2px;
- padding-right: 2px;
-
- .destination-icon {
- float: left;
- width: 12px;
- height: 12px;
- padding: 1px;
- margin-right: 3px;
- // color: rgb(161, 161, 161);
- }
- }
-
.linkMenu-destination-title {
text-decoration: none;
font-size: 13px;
@@ -111,7 +110,6 @@
//@extend: right;
position: relative;
display: flex;
- opacity: 0;
.linkMenu-deleteButton {
width: 20px;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index ad6deeefb..e694806a5 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -9,6 +9,7 @@ import { Doc } from '../../../fields/Doc';
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 { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
@@ -74,6 +75,15 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
return this._props.sourceDoc;
}
+ onIconDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, returnFalse, returnFalse, () => {
+ const ancestor = DocumentManager.LinkCommonAncestor(this._props.linkDoc);
+ if (!ancestor?.ComponentView?.removeDocument?.(this._props.linkDoc)) {
+ ancestor?.ComponentView?.addDocument?.(this._props.linkDoc);
+ }
+ });
+ };
+
onEdit = (e: React.PointerEvent) => {
setupMoveUpEvents(
this,
@@ -92,8 +102,8 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight);
} else {
SelectionManager.SelectView(this._props.docView, false);
- LinkManager.currentLink = this._props.linkDoc === LinkManager.currentLink ? undefined : this._props.linkDoc;
- LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined;
+ LinkManager.Instance.currentLink = this._props.linkDoc === LinkManager.Instance.currentLink ? undefined : this._props.linkDoc;
+ LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined;
if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250)));
@@ -159,7 +169,7 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
style={{
fontSize: this._hover ? 'larger' : undefined,
fontWeight: this._hover ? 'bold' : undefined,
- background: LinkManager.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
+ background: LinkManager.Instance.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
}}>
<div className="linkMenu-item-content expand-two">
<div
@@ -168,8 +178,13 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
onPointerDown={this.onLinkButtonDown}>
<div className="linkMenu-item-buttons">
<Tooltip title={<div className="dash-tooltip">Edit Link</div>}>
- <div className="button" ref={this._editRef} onPointerDown={this.onEdit} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon="edit" size="sm" />
+ <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>}>
+ <div title="click to show link" className="linkMenu-icon-wrapper" onPointerDown={this.onIconDown}>
+ <FontAwesomeIcon className="linkMenu-icon" icon={destinationIcon} size="sm" />
</div>
</Tooltip>
</div>
@@ -196,12 +211,11 @@ export class LinkMenuItem extends ObservableReactComponent<LinkMenuItemProps> {
</p>
) : null}
<div className="linkMenu-title-wrapper">
- <div className="destination-icon-wrapper">
- <FontAwesomeIcon className="destination-icon" icon={destinationIcon} size="sm" />
- </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>
+ <Tooltip 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>
+ </Tooltip>
</div>
{!this._props.linkDoc.link_description ? null : <p className="linkMenu-description">{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}</p>}
</div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 0ae4ed62c..a0a64ab59 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -33,7 +33,6 @@ export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentView
opacity?: number;
highlight?: boolean;
transition?: string;
- dataTransition?: string;
RenderCutoffProvider: (doc: Doc) => boolean;
CollectionFreeFormView: CollectionFreeFormView;
}
@@ -56,7 +55,6 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon
@observable Height = this.props.height;
@observable AutoDim = this.props.autoDim;
@observable Transition = this.props.transition;
- @observable DataTransition = this.props.dataTransition;
CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking
RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking
@@ -83,7 +81,6 @@ export class CollectionFreeFormDocumentViewWrapper extends ObservableReactCompon
w_Height = () => this.Height; // prettier-ignore
w_AutoDim = () => this.AutoDim; // prettier-ignore
w_Transition = () => this.Transition; // prettier-ignore
- w_DataTransition = () => this.DataTransition; // prettier-ignore
PanelWidth = () => this._props.autoDim ? this._props.PanelWidth?.() : this.Width; // prettier-ignore
PanelHeight = () => this._props.autoDim ? this._props.PanelHeight?.() : this.Height; // prettier-ignore
@@ -117,7 +114,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
w_Transition: () => string | undefined;
w_Width: () => number;
w_Height: () => number;
- w_DataTransition: () => string | undefined;
PanelWidth: () => number;
PanelHeight: () => number;
RenderCutoffProvider: (doc: Doc) => boolean;
@@ -288,14 +284,21 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this._props.PanelWidth(),
height: this._props.PanelHeight(),
transform: `translate(${this._props.w_X()}px, ${this._props.w_Y()}px) rotate(${NumCast(this._props.w_Rotation?.())}deg)`,
- transition: this._props.w_Transition?.() ?? (this._props.w_DataTransition?.() || this._props.w_Transition?.()),
+ transition: this._props.w_Transition?.() || StrCast(this.Document.dataTransition),
zIndex: this._props.w_ZIndex?.(),
display: this._props.w_Width?.() ? undefined : 'none',
}}>
{this._props.RenderCutoffProvider(this.Document) ? (
<div style={{ position: 'absolute', width: this._props.PanelWidth(), height: this._props.PanelHeight(), background: 'lightGreen' }} />
) : (
- <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} />
+ <DocumentView
+ {...passOnProps}
+ DataTransition={this._props.w_Transition}
+ CollectionFreeFormDocumentView={this.returnThis}
+ styleProvider={this.styleProvider}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ isGroupActive={this.isGroupActive}
+ />
)}
</div>
);
diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
index c0e738cfa..6672603f3 100644
--- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx
+++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx
@@ -436,9 +436,9 @@ export class Histogram extends ObservableReactComponent<HistogramProps> {
this.updateBarColors();
this._histogramData;
var curSelectedBarName = '';
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_histogram_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_histogram_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_histogram_defaultColor) this._props.layoutDoc.dataViz_histogram_defaultColor = '#69b3a2';
if (!this._props.layoutDoc.dataViz_histogram_barColors) this._props.layoutDoc.dataViz_histogram_barColors = new List<string>();
diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
index 8268d22b6..e093ec648 100644
--- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx
@@ -394,9 +394,9 @@ export class LineChart extends ObservableReactComponent<LineChartProps> {
}
render() {
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_lineChart_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_lineChart_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none';
var selectedTitle = "";
diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
index 54a83879c..fc23f47de 100644
--- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx
+++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx
@@ -332,9 +332,9 @@ export class PieChart extends ObservableReactComponent<PieChartProps> {
};
render() {
- var titleAccessor: any = '';
- if (this._props.axes.length == 2) titleAccessor = 'dataViz_pie_title' + this._props.axes[0] + '-' + this._props.axes[1];
- else if (this._props.axes.length > 0) titleAccessor = 'dataViz_pie_title' + this._props.axes[0];
+ var titleAccessor: any = 'dataViz_pie_title';
+ if (this._props.axes.length == 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1];
+ else if (this._props.axes.length > 0) titleAccessor = titleAccessor + this._props.axes[0];
if (!this._props.layoutDoc[titleAccessor]) this._props.layoutDoc[titleAccessor] = this.defaultGraphTitle;
if (!this._props.layoutDoc.dataViz_pie_sliceColors) this._props.layoutDoc.dataViz_pie_sliceColors = new List<string>();
var selected: string;
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index d1805308d..2a68d2bf6 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -157,7 +157,7 @@ export class DocumentLinksButton extends ObservableReactComponent<DocumentLinksB
startLink = DocumentLinksButton.StartLinkView?.ComponentView?.getAnchor?.(true) || startLink;
const linkDoc = DocUtils.MakeLink(startLink, endLink, { link_relationship: DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined });
- LinkManager.currentLink = linkDoc;
+ LinkManager.Instance.currentLink = linkDoc;
if (linkDoc) {
if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2e1ba2a7e..73c13b5dd 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -20,13 +20,14 @@ import { DocServer } from '../../DocServer';
import { Networking } from '../../Network';
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes';
-import { DocOptions, DocUtils, Docs, FInfo } from '../../documents/Documents';
+import { DocOptions, DocUtils, Docs } from '../../documents/Documents';
import { DictationManager } from '../../util/DictationManager';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType } from '../../util/DragManager';
import { FollowLinkScript } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SearchUtil } from '../../util/SearchUtil';
import { SelectionManager } from '../../util/SelectionManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
@@ -103,9 +104,11 @@ export interface DocumentViewProps extends FieldViewSharedProps {
dontHideOnDrag?: boolean;
suppressSetHeight?: boolean;
onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
+ DataTransition?: () => string | undefined;
NativeWidth?: () => number;
NativeHeight?: () => number;
contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[];
+ dragConfig?: (data: DragManager.DocumentDragData) => void;
dragStarting?: () => void;
dragEnding?: () => void;
}
@@ -114,7 +117,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
// this makes mobx trace() statements more descriptive
public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
-
/**
* This function is filled in by MainView to allow non-viewBox views to add Docs as tabs without
* needing to know about/reference MainView
@@ -288,7 +290,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
dragData.moveDocument = this._props.moveDocument;
dragData.draggedViews = [docView];
dragData.canEmbed = this.Document.dragAction ?? this._props.dragAction ? true : false;
- this._componentView?.dragConfig?.(dragData);
+ (this._props.dragConfig ?? this._componentView?.dragConfig)?.(dragData);
DragManager.StartDocumentDrag(
selected.map(dv => dv.ContentDiv!),
dragData,
@@ -359,7 +361,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
this._singleClickFunc =
// prettier-ignore
clickFunc ?? (() => (sendToBack ? documentView._props.bringToFront?.(this.Document, true) :
- this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ??
this._props.select(e.ctrlKey||e.shiftKey, e.metaKey)));
const waitFordblclick = this._props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick;
if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') {
@@ -456,11 +457,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
setToggleDetail = undoable(
- () =>
- (this.Document.onClick = ScriptField.MakeScript(
+ (defaultLayout: string, scriptFieldKey: 'onClick') =>
+ (this.Document[scriptFieldKey] = ScriptField.MakeScript(
`toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey)
.replace('layout_', '')
- .replace(/^layout$/, 'detail')}")`,
+ .replace(/^layout$/, 'detail')}", "${defaultLayout}")`,
{ documentView: 'any' }
)),
'set toggle detail'
@@ -488,7 +489,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
if (linkDoc) {
de.complete.linkDocument = linkDoc;
linkDoc.layout_isSvg = true;
- this._docView?.CollectionFreeFormView?.addDocument(linkDoc);
+ DocumentManager.LinkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc);
}
}
e.stopPropagation();
@@ -725,7 +726,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
};
removeLinkByHiding = (link: Doc) => () => (link.link_displayLine = false);
- allLinkEndpoints = () => {
+ @computed get allLinkEndpoints() {
// the small blue dots that mark the endpoints of links
if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null;
return this.filteredLinks.map(link => (
@@ -749,9 +750,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
/>
</div>
));
- };
+ }
- viewBoxContents = () => {
+ @computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
@@ -777,10 +778,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
setTitleFocus={this.setTitleFocus}
hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
/>
- {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()}
+ {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
</div>
);
- };
+ }
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) => {
@@ -813,7 +814,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
* setting layout_showTitle using the format: field1[;field2[...][:hover]]
* from the UI, this is done by clicking the title field and prefixin the format with '#'. eg., #field1[;field2;...][:hover]
**/
- titleView = () => {
+ @computed get titleView() {
const showTitle = this.layout_showTitle?.split(':')[0];
const showTitleHover = this.layout_showTitle?.includes(':hover');
@@ -887,9 +888,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
</div>
</div>
);
- };
+ }
- captionView = () => {
+ @computed get captionView() {
return !this.layout_showCaption ? null : (
<div
className="documentView-captionWrapper"
@@ -912,7 +913,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
/>
</div>
);
- };
+ }
renderDoc = (style: object) => {
TraceMobx();
@@ -932,15 +933,15 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'),
fontSize: Cast(this.Document._text_fontSize, 'string', null),
transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined,
- transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
+ transition: !this._animateScalingTo ? this._props.DataTransition?.() : `transform ${this.animateScaleTime() / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`,
}}>
{this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? (
- this.viewBoxContents()
+ this.viewBoxContents
) : (
<div className="documentView-styleWrapper">
- {this.titleView()}
- {this.viewBoxContents()}
- {this.captionView()}
+ {this.titleView}
+ {this.viewBoxContents}
+ {this.captionView}
</div>
)}
{this.widgetDecorations ?? null}
@@ -1074,6 +1075,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
private _disposers: { [name: string]: IReactionDisposer } = {};
private _viewTimer: NodeJS.Timeout | undefined;
private _animEffectTimer: NodeJS.Timeout | undefined;
+ public Guid = Utils.GenerateGuid(); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
@computed public static get exploreMode() {
return () => (SnappingManager.ExploreMode ? ScriptField.MakeScript('CollectionBrowseClick(documentView, clientX, clientY)', { documentView: 'any', clientX: 'number', clientY: 'number' })! : undefined);
@@ -1150,6 +1152,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
componentWillUnmount() {
+ this._viewTimer && clearTimeout(this._viewTimer);
runInAction(() => this.Document[DocViews].delete(this));
Object.values(this._disposers).forEach(disposer => disposer?.());
!BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this);
@@ -1173,21 +1176,23 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
return this._props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.Document['link_anchor_2']) : this._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.Document['link_anchor_1']) : undefined;
}
- @computed get getBounds() {
- if (!this._docViewInternal?._contentDiv || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
+ @computed get getBounds(): Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }> {
+ if (!this.ContentDiv || Doc.AreProtosEqual(this.Document, Doc.UserDoc())) {
return undefined;
}
- if (this._docViewInternal._componentView?.screenBounds?.()) {
- return this._docViewInternal._componentView.screenBounds();
+ if (this.ComponentView?.screenBounds?.()) {
+ return this.ComponentView.screenBounds();
}
const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- const docuBox = this._docViewInternal._contentDiv.getElementsByClassName('linkAnchorBox-cont');
- if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined };
+ const docuBox = this.ContentDiv.getElementsByClassName('linkAnchorBox-cont');
+ if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined };
}
- return { left, top, right, bottom };
+ // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox
+ const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.());
+ return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() };
}
@computed get nativeWidth() {
@@ -1212,7 +1217,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
};
public noOnClick = () => this._docViewInternal?.noOnClick();
public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle);
- public setToggleDetail = () => this._docViewInternal?.setToggleDetail();
+ public setToggleDetail = (defaultLayout = '', scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(defaultLayout, scriptFieldKey);
public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY);
public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents();
public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource);
@@ -1332,6 +1337,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
}
};
+ DataTransition = () => this._props.DataTransition?.() || StrCast(this.Document.dataTransition);
ShouldNotScale = () => this.shouldNotScale;
NativeWidth = () => this.effectiveNativeWidth;
NativeHeight = () => this.effectiveNativeHeight;
@@ -1384,7 +1390,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
return (
- <div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
+ <div id={this.Guid} className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
{!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
@@ -1397,6 +1403,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
<DocumentViewInternal
{...this._props}
fieldKey={this.LayoutFieldKey}
+ DataTransition={this.DataTransition}
DocumentView={this.selfView}
docViewPath={this.docViewPath}
PanelWidth={this.PanelWidth}
@@ -1460,13 +1467,13 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView
LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0);
});
-ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) {
- if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(false, 'layout');
+ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string, defaultLayout = '') {
+ if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true);
else dv.switchViews(true, detailLayoutKeySuffix, undefined, true);
});
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
- const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data);
+ const collectedLinks = DocListCast(linkCollection[DocData].data);
let wid = NumCast(linkSource._width);
let embedding: Doc | undefined;
const links = LinkManager.Links(linkSource);
@@ -1485,3 +1492,27 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise
return links;
});
+ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
+ const tag = StrCast(collection.title).split('-->')[1];
+ const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
+ const collectionDocs = DocListCast(collection[DocData].data).concat(collection);
+ let wid = 100;
+ let created = false;
+ const matchedDocs = matchedTags
+ .filter(tagDoc => !Doc.AreProtosEqual(collection, tagDoc))
+ .map(tagDoc => {
+ let embedding = collectionDocs.find(doc => Doc.AreProtosEqual(tagDoc, doc));
+ if (!embedding) {
+ embedding = Doc.MakeEmbedding(tagDoc);
+ embedding.x = wid;
+ embedding.y = 0;
+ embedding._lockedPosition = false;
+ wid += NumCast(tagDoc._width);
+ created = true;
+ }
+ return embedding;
+ });
+
+ created && (collection[DocData].data = new List<Doc>(matchedDocs));
+ return true;
+});
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss
index db2ffa756..2db285910 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.scss
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss
@@ -1,5 +1,15 @@
@import '../../global/globalCssVariables.module.scss';
+// bcz: something's messed up with the IconButton css. this mostly fixes the fit-all button, the color buttons, the undo +/- expander and the dropdown doc type list (eg 'text')
+.iconButton-container {
+ width: unset !important;
+ min-width: 30px !important;
+ height: unset !important;
+ min-height: 30px;
+ .color {
+ height: 3px !important;
+ }
+}
.menuButton {
height: 100%;
display: flex;
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 8290e102c..3577cc8d9 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -381,7 +381,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
case ButtonType.ColorButton: return this.colorButton;
case ButtonType.MultiToggleButton: return this.multiToggleButton;
case ButtonType.ToggleButton: return this.toggleButton;
- case ButtonType.ClickButton:
+ case ButtonType.ClickButton:return <IconButton {...btnProps} size={Size.MEDIUM} color={color} />;
case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
case ButtonType.TextButton: return <Button {...btnProps} color={color}
background={SettingsManager.userBackgroundColor} text={StrCast(this.dataDoc.buttonText)}/>;
@@ -392,6 +392,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
};
render() {
- return <div onContextMenu={this.specificContextMenu}>{this.renderButton()}</div>;
+ return (
+ <div style={{ margin: 'auto', width: '100%' }} onContextMenu={this.specificContextMenu}>
+ {this.renderButton()}
+ </div>
+ );
}
}
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index 10eeff08d..fd3074a88 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -88,7 +88,7 @@ export class LabelBox extends ViewBoxBaseComponent<LabelBoxProps>() {
@observable _mouseOver = false;
@computed get hoverColor() {
- return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor);
+ return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor);
}
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 767f0291b..484fb301e 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -5,3 +5,28 @@
.linkBox-container {
width: 100%;
}
+
+.linkBox {
+ transition: inherit;
+ pointer-events: none;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ path {
+ transition: inherit;
+ fill: transparent;
+ }
+ svg {
+ transition: inherit;
+ overflow: visible;
+ }
+ text {
+ cursor: default;
+ text-anchor: middle;
+ font-size: 12;
+ stroke: black;
+ }
+ circle {
+ cursor: default;
+ }
+}
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 8b6293806..decdbb240 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -1,14 +1,17 @@
-import { Bezier } from 'bezier-js';
-import { computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
+import Xarrow from 'react-xarrows';
+import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils';
+import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
-import { Transform } from '../../util/Transform';
-import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { LinkManager } from '../../util/LinkManager';
+import { SnappingManager } from '../../util/SnappingManager';
import { ViewBoxBaseComponent } from '../DocComponent';
+import { EditableView } from '../EditableView';
+import { LightboxView } from '../LightboxView';
import { StyleProp } from '../StyleProvider';
import { ComparisonBox } from './ComparisonBox';
import { FieldView, FieldViewProps } from './FieldView';
@@ -19,152 +22,178 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
public static LayoutString(fieldKey: string = 'link') {
return FieldView.LayoutString(LinkBox, fieldKey);
}
+ disposer: IReactionDisposer | undefined;
+ @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor
+ @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor
constructor(props: FieldViewProps) {
super(props);
makeObservable(this);
}
+ @computed get anchor1() { return this.anchor(1); } // prettier-ignore
+ @computed get anchor2() { return this.anchor(2); } // prettier-ignore
- onClickScriptDisable = returnAlways;
- @computed get anchor1() {
- const anchor1 = DocCast(this.dataDoc.link_anchor_1);
- const anchor_1 = anchor1?.layout_unrendered ? DocCast(anchor1.annotationOn) : anchor1;
- return DocumentManager.Instance.getDocumentView(anchor_1, this.DocumentView?.().containerViewPath?.().lastElement());
- }
- @computed get anchor2() {
- const anchor2 = DocCast(this.dataDoc.link_anchor_2);
- const anchor_2 = anchor2?.layout_unrendered ? DocCast(anchor2.annotationOn) : anchor2;
- return DocumentManager.Instance.getDocumentView(anchor_2, this.DocumentView?.().containerViewPath?.().lastElement());
- }
- screenBounds = () => {
- if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) {
- const a_invXf = this.anchor1.screenToViewTransform().inverse();
- const b_invXf = this.anchor2.screenToViewTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.Document._width), NumCast(this.anchor1.Document._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.Document._width), NumCast(this.anchor2.Document._height)) };
-
- const pts = [] as number[][];
- pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]);
- pts.push(Utils.getNearestPointInPerimeter(a_scrBds.tl[0], a_scrBds.tl[1], a_scrBds.br[0] - a_scrBds.tl[0], a_scrBds.br[1] - a_scrBds.tl[1], (b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2));
- pts.push(Utils.getNearestPointInPerimeter(b_scrBds.tl[0], b_scrBds.tl[1], b_scrBds.br[0] - b_scrBds.tl[0], b_scrBds.br[1] - b_scrBds.tl[1], (a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2));
- pts.push([(b_scrBds.tl[0] + b_scrBds.br[0]) / 2, (b_scrBds.tl[1] + b_scrBds.br[1]) / 2]);
- const agg = aggregateBounds(
- pts.map(pt => ({ x: pt[0], y: pt[1] })),
- 0,
- 0
- );
- return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined };
- }
- return undefined;
+ anchor = (which: number) => {
+ const anch = DocCast(this.dataDoc['link_anchor_' + which]);
+ const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
+ return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement());
};
- disposer: IReactionDisposer | undefined;
+ componentWillUnmount() {
+ this.disposer?.();
+ }
componentDidMount() {
this._props.setContentViewBox?.(this);
this.disposer = reaction(
- () => {
- if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) {
- const a = (this.anchor1 ?? this.anchor2)!;
- const b = (this.anchor2 ?? this.anchor1)!;
-
- const parxf = this.DocumentView?.().containerViewPath?.().lastElement().ComponentView as CollectionFreeFormView;
- const this_xf = parxf?.screenToFreeformContentsXf ?? Transform.Identity; //this.ScreenToLocalTransform();
- const a_invXf = a.screenToViewTransform().inverse();
- const b_invXf = b.screenToViewTransform().inverse();
- const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.Document._width), NumCast(a.Document._height)) };
- const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.Document._width), NumCast(b.Document._height)) };
- const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) };
- const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) };
-
- const ppt1 = [(a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2];
- const pt1 = Utils.getNearestPointInPerimeter(a_bds.tl[0], a_bds.tl[1], a_bds.br[0] - a_bds.tl[0], a_bds.br[1] - a_bds.tl[1], (b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2);
- const pt2 = Utils.getNearestPointInPerimeter(b_bds.tl[0], b_bds.tl[1], b_bds.br[0] - b_bds.tl[0], b_bds.br[1] - b_bds.tl[1], (a_bds.tl[0] + a_bds.br[0]) / 2, (a_bds.tl[1] + a_bds.br[1]) / 2);
- const ppt2 = [(b_bds.tl[0] + b_bds.br[0]) / 2, (b_bds.tl[1] + b_bds.br[1]) / 2];
-
- const pts = [ppt1, pt1, pt2, ppt2].map(pt => [pt[0], pt[1]]);
- const [lx, rx, ty, by] = [Math.min(pt1[0], pt2[0]), Math.max(pt1[0], pt2[0]), Math.min(pt1[1], pt2[1]), Math.max(pt1[1], pt2[1])];
- return { pts, lx, rx, ty, by };
- }
- return undefined;
- },
- params => {
- this.renderProps = params;
- if (params) {
- if (
- Math.abs(params.lx - NumCast(this.layoutDoc.x)) > 1e-5 ||
- Math.abs(params.ty - NumCast(this.layoutDoc.y)) > 1e-5 ||
- Math.abs(params.rx - params.lx - NumCast(this.layoutDoc._width)) > 1e-5 ||
- Math.abs(params.by - params.ty - NumCast(this.layoutDoc._height)) > 1e-5
- ) {
- this.layoutDoc.x = params?.lx;
- this.layoutDoc.y = params?.ty;
- this.layoutDoc._width = params.rx - params?.lx;
- this.layoutDoc._height = params?.by - params?.ty;
- }
- } else {
- this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width));
- this.layoutDoc._height = Math.max(50, NumCast(this.layoutDoc._height));
- }
+ () => ({ drag: SnappingManager.IsDragging }),
+ ({ drag }) => {
+ !LightboxView.Contains(this.DocumentView?.()) &&
+ setTimeout(
+ // need to wait for drag manager to set 'hidden' flag on dragged DOM elements
+ action(() => {
+ const a = this.anchor1,
+ b = this.anchor2;
+ let a1 = a && document.getElementById(a.Guid);
+ let a2 = b && document.getElementById(b.Guid);
+ // test whether the anchors themselves are hidden,...
+ if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true;
+ else {
+ // .. or whether and of their DOM parents are hidden
+ for (; a1 && !a1.hidden; a1 = a1.parentElement);
+ for (; a2 && !a2.hidden; a2 = a2.parentElement);
+ this._hide = a1 || a2 ? true : false;
+ }
+ })
+ );
},
{ fireImmediately: true }
);
}
- componentWillUnmount(): void {
- this.disposer?.();
- }
- @observable renderProps: { lx: number; rx: number; ty: number; by: number; pts: number[][] } | undefined = undefined;
+
render() {
- if (this.renderProps) {
+ if (this._hide) return null;
+ const a = this.anchor1;
+ const b = this.anchor2;
+ this._forceAnimate;
+ const docView = this._props.docViewPath().lastElement();
+
+ if (a && b && !LightboxView.Contains(docView)) {
+ // text selection bounds are not directly observable, so we have to
+ // force an update when anything that could affect them changes (text edits causing reflow, scrolling)
+ a.Document[a.LayoutFieldKey];
+ b.Document[b.LayoutFieldKey];
+ a.Document.layout_scrollTop;
+ b.Document.layout_scrollTop;
+
+ const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove)
+ const bxf = b.screenToViewTransform();
+ const scale = docView?.screenToViewTransform().Scale ?? 1;
+ const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition
+ const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation)
+
+ // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext <a>),
+ // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
+ // otherwise, we just use the computed nearest point on the document boundary to the target Document
+ const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement();
+ const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement();
+
+ const aid = targetAhyperlink?.id || a.Document[Id];
+ const bid = targetBhyperlink?.id || b.Document[Id];
+ if (!document.getElementById(aid) || !document.getElementById(bid)) {
+ setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01)));
+ return null;
+ }
+
+ if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation
const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting);
const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined;
+ const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color);
+ const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily);
+ const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize);
+ const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor));
+ const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document;
- const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] })));
- const text = bez.get(0.5);
- const linkDesc = StrCast(this.dataDoc.link_description) || 'description';
- const strokeWidth = NumCast(this.dataDoc.stroke_width, 4);
- const dash = StrCast(this.Document.stroke_dash);
- const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined;
- const { pts, lx, ty, rx, by } = this.renderProps;
+ const strokeWidth = NumCast(stroke_width, 4);
+ const linkDesc = StrCast(this.dataDoc.link_description) || ' ';
+ const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '');
return (
- <div style={{ transition: 'inherit', pointerEvents: 'none', position: 'absolute', width: '100%', height: '100%' }}>
- <svg width={Math.max(100, rx - lx)} height={Math.max(100, by - ty)} style={{ transition: 'inherit', overflow: 'visible' }}>
- <defs>
- <filter x="0" y="0" width="1" height="1" id={`${this.Document[Id] + 'background'}`}>
- <feFlood floodColor={`${StrCast(this.layoutDoc._backgroundColor, 'lightblue')}`} result="bg" />
- <feMerge>
- <feMergeNode in="bg" />
- <feMergeNode in="SourceGraphic" />
- </feMerge>
- </filter>
- </defs>
- <path
- className="collectionfreeformlinkview-linkLine"
- style={{
- pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'visibleStroke', //
- stroke: highlightColor ?? 'lightblue',
- strokeDasharray,
- strokeWidth,
- transition: 'inherit',
- }}
- d={`M ${pts[1][0] - lx} ${pts[1][1] - ty} C ${pts[1][0] + pts[1][0] - pts[0][0] - lx} ${pts[1][1] + pts[1][1] - pts[0][1] - ty},
- ${pts[2][0] + pts[2][0] - pts[3][0] - lx} ${pts[2][1] + pts[2][1] - pts[3][1] - ty}, ${pts[2][0] - lx} ${pts[2][1] - ty}`}
+ <>
+ {!highlightColor ? null : (
+ <Xarrow
+ divContainerStyle={{ transform: `scale(${scale})` }}
+ start={aid}
+ end={bid} //
+ strokeWidth={strokeWidth + Math.max(2, strokeWidth * 0.1)}
+ showHead={stroke_startMarker ? true : false}
+ showTail={stroke_endMarker ? true : false}
+ headSize={NumCast(stroke_markerScale, 3)}
+ tailSize={NumCast(stroke_markerScale, 3)}
+ tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ color={highlightColor}
/>
- <text
- filter={`url(#${this.Document[Id] + 'background'})`}
- style={{ pointerEvents: this._props.pointerEvents?.() === 'none' ? 'none' : 'all', textAnchor: 'middle', fontSize: '12', stroke: 'black' }}
- x={text.x - lx}
- y={text.y - ty}>
- <tspan>&nbsp;</tspan>
- <tspan dy="2">{linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}</tspan>
- <tspan dy="2">&nbsp;</tspan>
- </text>
- </svg>
- </div>
+ )}
+ <Xarrow
+ divContainerStyle={{ transform: `scale(${scale})` }}
+ start={aid}
+ end={bid} //
+ strokeWidth={strokeWidth}
+ dashness={Number(stroke_dash) ? true : false}
+ showHead={stroke_startMarker ? true : false}
+ showTail={stroke_endMarker ? true : false}
+ headSize={NumCast(stroke_markerScale, 3)}
+ tailSize={NumCast(stroke_markerScale, 3)}
+ tailShape={stroke_endMarker === 'dot' ? 'circle' : 'arrow1'}
+ headShape={stroke_startMarker === 'dot' ? 'circle' : 'arrow1'}
+ color={color}
+ labels={
+ <div
+ style={{
+ borderRadius: '8px',
+ pointerEvents: this._props.isDocumentActive?.() ? 'all' : undefined,
+ fontSize,
+ fontFamily /*, fontStyle: 'italic'*/,
+ color: fontColor || lightOrDark(DashColor(color).fade(0.5).toString()),
+ paddingLeft: 4,
+ paddingRight: 4,
+ paddingTop: 3,
+ paddingBottom: 3,
+ background: DashColor((!docView?.isSelected() && highlightColor) || color)
+ .fade(0.5)
+ .toString(),
+ }}>
+ <EditableView
+ key="editableView"
+ oneLine
+ contents={labelText}
+ height={fontSize + 4}
+ fontSize={fontSize}
+ GetValue={() => linkDesc}
+ SetValue={action(val => {
+ this.Document[DocData].link_description = val;
+ return true;
+ })}
+ />
+
+ {/* <EditableText
+ placeholder={labelText}
+ background={color}
+ color={fontColor || lightOrDark(DashColor(color).fade(0.5).toString())}
+ type={Type.PRIM}
+ val={StrCast(this.Document[DocData].link_description)}
+ setVal={action(val => (this.Document[DocData].link_description = val))}
+ fillWidth
+ /> */}
+ </div>
+ }
+ passProps={{}}
+ />
+ </>
);
}
return (
<div className={`linkBox-container${this._props.isContentActive() ? '-interactive' : ''}`} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) }}>
<ComparisonBox
- {...this._props} //
+ {...this.props} //
fieldKey="link_anchor"
setHeight={emptyFunction}
dontRegisterView={true}
diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx
index 13f0ac4fc..1645d0813 100644
--- a/src/client/views/nodes/LinkDescriptionPopup.tsx
+++ b/src/client/views/nodes/LinkDescriptionPopup.tsx
@@ -1,10 +1,11 @@
-import { action, makeObservable, observable } from 'mobx';
+import { action, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DocData } from '../../../fields/DocSymbols';
import { LinkManager } from '../../util/LinkManager';
import './LinkDescriptionPopup.scss';
import { TaskCompletionBox } from './TaskCompletedBox';
+import { StrCast } from '../../../fields/Types';
@observer
export class LinkDescriptionPopup extends React.Component<{}> {
@@ -31,21 +32,26 @@ export class LinkDescriptionPopup extends React.Component<{}> {
onDismiss = (add: boolean) => {
this.display = false;
if (add) {
- LinkManager.currentLink && (LinkManager.currentLink[DocData].link_description = this.description);
+ LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[DocData].link_description = this.description);
}
+ this.description = '';
};
@action
onClick = (e: PointerEvent) => {
if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) {
this.display = false;
+ this.description = '';
TaskCompletionBox.taskCompleted = false;
}
};
- @action
componentDidMount() {
document.addEventListener('pointerdown', this.onClick, true);
+ reaction(
+ () => this.display,
+ display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description))
+ );
}
componentWillUnmount() {
@@ -65,8 +71,10 @@ export class LinkDescriptionPopup extends React.Component<{}> {
className="linkDescriptionPopup-input"
onKeyDown={e => e.stopPropagation()}
onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)}
- placeholder={'(Optional) Enter link description...'}
- onChange={e => this.descriptionChanged(e)}></input>
+ value={this.description}
+ placeholder={this.description || '(Optional) Enter link description...'}
+ onChange={e => this.descriptionChanged(e)}
+ />
<div className="linkDescriptionPopup-btn">
<div className="linkDescriptionPopup-btn-dismiss" onPointerDown={e => this.onDismiss(false)}>
{' '}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 3f793b85e..ae25ff179 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -152,8 +152,8 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps
returnFalse,
emptyFunction,
action(() => {
- LinkManager.currentLink = this._linkDoc;
- LinkManager.currentLinkAnchor = this._linkSrc;
+ LinkManager.Instance.currentLink = this._linkDoc;
+ LinkManager.Instance.currentLinkAnchor = this._linkSrc;
this._props.DocumentView?.().select(false);
if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) {
SettingsManager.Instance.propertiesWidth = 250;
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
index b7d2a24c2..a72ed1813 100644
--- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx
@@ -3,6 +3,8 @@ import * as ReactDOM from 'react-dom/client';
import { Doc } from '../../../../fields/Doc';
import { DocServer } from '../../../DocServer';
import * as React from 'react';
+import { IReactionDisposer, computed, reaction } from 'mobx';
+import { NumCast } from '../../../../fields/Types';
// creates an inline comment in a note when '>>' is typed.
// the comment sits on the right side of the note and vertically aligns with its anchor in the text.
@@ -10,8 +12,10 @@ import * as React from 'react';
export class DashDocCommentView {
dom: HTMLDivElement; // container for label and value
root: any;
+ node: any;
constructor(node: any, view: any, getPos: any) {
+ this.node = node;
this.dom = document.createElement('div');
this.dom.style.width = node.attrs.width;
this.dom.style.height = node.attrs.height;
@@ -32,10 +36,14 @@ export class DashDocCommentView {
};
this.root = ReactDOM.createRoot(this.dom);
- this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} docId={node.attrs.docId} />);
+ this.root.render(<DashDocCommentViewInternal view={view} getPos={getPos} setHeight={this.setHeight} docId={node.attrs.docId} />);
(this as any).dom = this.dom;
}
+ setHeight = (hgt: number) => {
+ !this.node.attrs.reflow && DocServer.GetRefField(this.node.attrs.docId).then(doc => doc instanceof Doc && (this.dom.style.height = hgt + ''));
+ };
+
destroy() {
this.root.unmount();
}
@@ -51,9 +59,15 @@ interface IDashDocCommentViewInternal {
docId: string;
view: any;
getPos: any;
+ setHeight: (height: number) => void;
}
export class DashDocCommentViewInternal extends React.Component<IDashDocCommentViewInternal> {
+ _reactionDisposer: IReactionDisposer | undefined;
+
+ @computed get _dashDoc() {
+ return DocServer.GetRefField(this.props.docId);
+ }
constructor(props: any) {
super(props);
this.onPointerLeaveCollapsed = this.onPointerLeaveCollapsed.bind(this);
@@ -61,15 +75,32 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
this.onPointerUpCollapsed = this.onPointerUpCollapsed.bind(this);
this.onPointerDownCollapsed = this.onPointerDownCollapsed.bind(this);
}
+ componentDidMount(): void {
+ this._reactionDisposer?.();
+ this._dashDoc.then(
+ doc =>
+ doc instanceof Doc &&
+ (this._reactionDisposer = reaction(
+ () => NumCast((doc as Doc)._height),
+ hgt => this.props.setHeight(hgt),
+ {
+ fireImmediately: true,
+ }
+ ))
+ );
+ }
+ componentWillUnmount(): void {
+ this._reactionDisposer?.();
+ }
onPointerLeaveCollapsed(e: any) {
- DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
+ this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowUnhighlight());
e.preventDefault();
e.stopPropagation();
}
onPointerEnterCollapsed(e: any) {
- DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
+ this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc, false));
e.preventDefault();
e.stopPropagation();
}
@@ -82,7 +113,7 @@ export class DashDocCommentViewInternal extends React.Component<IDashDocCommentV
const tr = this.props.view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true });
this.props.view.dispatch(tr.setSelection(TextSelection.create(tr.doc, this.props.getPos() + (expand ? 2 : 1)))); // update the attrs
setTimeout(() => {
- expand && DocServer.GetRefField(this.props.docId).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
+ expand && this._dashDoc.then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc));
try {
this.props.view.dispatch(this.props.view.state.tr.setSelection(TextSelection.create(this.props.view.state.tr.doc, this.props.getPos() + (expand ? 2 : 1))));
} catch (e) {}
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 3426ba1a7..7a0ff8776 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -5,6 +5,16 @@
display: inline-flex;
align-items: center;
+ select {
+ display: none;
+ }
+
+ &:hover {
+ select {
+ display: unset;
+ }
+ }
+
.dashFieldView-enumerables {
width: 10px;
height: 10px;
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx
index 18286267a..ec0b76aa8 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.tsx
+++ b/src/client/views/nodes/formattedText/DashFieldView.tsx
@@ -4,21 +4,24 @@ 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 { Doc, Field } from '../../../../fields/Doc';
+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 } from '../../../../fields/Types';
+import { Cast, StrCast } 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 { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell';
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
@@ -97,7 +100,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
_reactionDisposer: IReactionDisposer | undefined;
_textBoxDoc: Doc;
_fieldKey: string;
- _fieldStringRef = React.createRef<HTMLSpanElement>();
+ _fieldRef = React.createRef<HTMLDivElement>();
@observable _dashDoc: Doc | undefined = undefined;
@observable _expanded = false;
@@ -180,10 +183,22 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
});
};
+ @undoBatch
+ selectVal = (event: React.ChangeEvent<HTMLSelectElement> | undefined) => {
+ event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value);
+ };
+
+ @computed get values() {
+ const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []);
+
+ return vals.strings.map(facet => ({ value: facet, label: facet }));
+ }
+
render() {
return (
<div
className="dashFieldView"
+ ref={this._fieldRef}
style={{
width: this._props.width,
height: this._props.height,
@@ -194,8 +209,12 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi
{(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey}
</span>
)}
-
{this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent}
+ <select onChange={this.selectVal} style={{ height: '10px', width: '15px', fontSize: '12px', background: 'transparent' }}>
+ {this.values.map(val => (
+ <option value={val.value}>{val.label}</option>
+ ))}
+ </select>
</div>
);
}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0b857794b..f2c4c6c8f 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -13,7 +13,7 @@ import { EditorView } from 'prosemirror-view';
import * as React from 'react';
import { BsMarkdownFill } from 'react-icons/bs';
import { DateField } from '../../../../fields/DateField';
-import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkTool } from '../../../../fields/InkField';
@@ -67,7 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu';
import { RichTextRules } from './RichTextRules';
import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
-import { CollectionView } from '../../collections/CollectionView';
+import Select from 'react-select';
// import * as applyDevTools from 'prosemirror-dev-tools';
@observer
export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
@@ -361,7 +361,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
accumTags.push(node.attrs.fieldKey);
}
});
- dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined;
+ if (accumTags.some(atag => !StrListCast(dataDoc.tags).includes(atag))) {
+ dataDoc.tags = new List<string>(Array.from(new Set<string>(accumTags)));
+ }
let unchanged = true;
if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) {
@@ -487,14 +489,29 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
};
// creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@'
+ /**
+ * Searches the text for occurences of any strings that match the names of 'published' documents. These document
+ * names will begin with an '@' prefix. However, valid matches within the text can have any of the following formats:
+ * name, @<name>, or ^@<name>
+ * The last of these is interpreted as an include directive when converting the text into evaluated code in the paint
+ * function of a freeform view that is driven by the text box's text. The include directive will copy the code of the published
+ * document into the code being evaluated.
+ */
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
const editorView = this._editorView;
if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.Document)) {
const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
var alink: Doc | undefined;
this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
- const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
- if (!sel.$anchor.pos || [autoLinkTerm, StrCast(target.title)].includes(editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim())) {
+ if (
+ !sel.$anchor.pos ||
+ autoLinkTerm ===
+ editorView.state.doc
+ .textBetween(sel.$anchor.pos - 1, sel.$to.pos)
+ .trim()
+ .replace(/[\^@]+/, '')
+ ) {
+ const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
tr = tr.addMark(sel.from, sel.to, splitter);
tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
@@ -667,12 +684,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
let index = 0,
foundAt;
const ep = this.getNodeEndpoints(pm.state.doc, node);
- const regexp = new RegExp(find.replace('*', ''), 'i');
+ const regexp = new RegExp(find, 'i');
if (regexp) {
- while (ep && (foundAt = node.textContent.slice(index).search(regexp)) > -1) {
- const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1));
- ret.push(sel);
- index = index + foundAt + find.length;
+ var blockOffset = 0;
+ for (var i = 0; i < node.childCount; i++) {
+ var textContent = '';
+ while (i < node.childCount && node.child(i).type === pm.state.schema.nodes.text) {
+ textContent += node.child(i).textContent;
+ i++;
+ }
+ while (ep && (foundAt = textContent.slice(index).search(regexp)) > -1) {
+ const sel = new TextSelection(pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + 1), pm.state.doc.resolve(ep.from + index + blockOffset + foundAt + find.length + 1));
+ ret.push(sel);
+ index = index + foundAt + find.length;
+ }
+ blockOffset += textContent.length;
+ if (i < node.childCount) blockOffset += node.child(i).nodeSize;
}
}
} else {
@@ -933,17 +960,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR),
icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars',
});
- optionItems.push({
- description: 'Make Paint Function',
- event: () => {
- this.dataDoc.layout_painted = CollectionView.LayoutString('painted');
- this.layoutDoc.layout_fieldKey = 'layout_painted';
- this.layoutDoc.type_collection = CollectionViewType.Freeform;
- this.DocumentView?.().setToggleDetail();
- this.dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${this.fieldKey}']?.Text)`);
- },
- icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye',
- });
!Doc.noviceMode &&
optionItems.push({
description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`,
@@ -1176,8 +1192,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._cachedLinks = LinkManager.Links(this.Document);
this._disposers.breakupDictation = reaction(() => Doc.RecordingEvent, this.breakupDictation);
this._disposers.layout_autoHeight = reaction(
- () => this.layout_autoHeight,
- layout_autoHeight => layout_autoHeight && this.tryUpdateScrollHeight()
+ () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize }),
+ (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight())
);
this._disposers.highlights = reaction(
() => Array.from(FormattedTextBox._globalHighlights).slice(),
@@ -1538,15 +1554,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
this._downX = e.clientX;
this._downY = e.clientY;
this._downTime = Date.now();
- this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
FormattedTextBoxComment.textBox = this;
if (e.button === 0 && this._props.rootSelected?.() && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (e.clientX < this.ProseRef!.getBoundingClientRect().right) {
// stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes.
e.stopPropagation(); // if the text box's content is active, then it consumes all down events
document.addEventListener('pointerup', this.onSelectEnd);
+ (this.ProseRef?.children?.[0] as any).focus();
}
}
+ this._hadDownFocus = this.ProseRef?.children[0].className.includes('focused') ?? false;
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
}
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 5858c3b11..cd0cdaa74 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -141,12 +141,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const activeSizes = active.activeSizes;
const activeColors = active.activeColors;
const activeHighlights = active.activeHighlights;
+ const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc();
+ const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey);
this.activeListType = this.getActiveListStyle();
this._activeAlignment = this.getActiveAlignment();
- this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
- this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0];
- this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
+ this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(refDoc[refField + 'fontFamily'], 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various';
+ this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(refDoc[refField + 'fontSize'], '10px')) : activeSizes[0];
+ this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(refDoc[refField + 'fontColor'], 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...';
this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...';
// update link in current selection
@@ -358,8 +360,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
}
- } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) {
- SelectionManager.Views.forEach(dv => (dv.Document._text_fontSize = fontSize));
+ } else if (SelectionManager.Views.length) {
+ SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize));
} else Doc.UserDoc().fontSize = fontSize;
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
@@ -369,6 +371,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family });
this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true);
this.view.focus();
+ } else if (SelectionManager.Views.length) {
+ SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family));
} else Doc.UserDoc().fontFamily = family;
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
};
@@ -387,6 +391,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color });
this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true);
this.view.focus();
+ } else if (SelectionManager.Views.length) {
+ SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color));
} else Doc.UserDoc().fontColor = color;
this.updateMenu(this.view, undefined, this.props, this.layoutDoc);
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index be32a2c4a..9bd41f42c 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -4,8 +4,7 @@ import { Doc, StrListCast } from '../../../../fields/Doc';
import { DocData } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
-import { ComputedField } from '../../../../fields/ScriptField';
-import { NumCast } from '../../../../fields/Types';
+import { NumCast, StrCast } from '../../../../fields/Types';
import { Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
@@ -15,6 +14,7 @@ import { RichTextMenu } from './RichTextMenu';
import { schema } from './schema_rts';
import { CollectionView } from '../../collections/CollectionView';
import { CollectionViewType } from '../../../documents/DocumentTypes';
+import { ContextMenu } from '../../ContextMenu';
export class RichTextRules {
public Document: Doc;
@@ -68,23 +68,20 @@ export class RichTextRules {
),
// ``` create code block
- textblockTypeInputRule(/^```$/, schema.nodes.code_block),
-
- new InputRule(new RegExp(/^\^@paint/), (state, match, start, end) => {
- const { dataDoc, layoutDoc, fieldKey } = this.TextBox;
- layoutDoc.type_collection = CollectionViewType.Freeform;
- dataDoc.layout_painted = CollectionView.LayoutString('painted');
- const layoutFieldKey = layoutDoc.layout_fieldKey;
- layoutDoc.layout_fieldKey = 'layout_painted';
- this.TextBox.DocumentView?.().setToggleDetail();
- layoutDoc.layout_fieldKey = layoutFieldKey;
- dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${fieldKey}']?.Text)`);
- const comment = '/* this is now a paint func */';
- const tr = state.tr
- .deleteRange(start, end)
- .insertText(comment)
- .insert(start + comment.length, schema.nodes.code_block.create());
- return tr.setSelection(new TextSelection(tr.doc.resolve(start + comment.length + 2)));
+ new InputRule(/^```$/, (state, match, start, end) => {
+ let $start = state.doc.resolve(start);
+ if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null;
+
+ // this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script
+ this.TextBox.layoutDoc.type_collection = CollectionViewType.Freeform; // make it a freeform when rendered as a collection since those are the only views that know about the paint function
+ const paintedField = 'layout_' + this.TextBox.fieldKey + 'Painted'; // make a layout field key for storing the CollectionView jsx string pointing to the textbox's text
+ this.TextBox.dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey);
+ const layoutFieldKey = StrCast(this.TextBox.layoutDoc.layout_fieldKey); // save the current layout fieldkey
+ this.TextBox.layoutDoc.layout_fieldKey = paintedField; // setup the paint layout field key
+ this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''), 'onPaint'); // create the script to toggle between the painted and regular view
+ this.TextBox.layoutDoc.layout_fieldKey = layoutFieldKey; // restore the layout field key to text
+
+ return state.tr.delete(start, end).setBlockType(start, start, schema.nodes.code_block);
}),
// %<font-size> set the font size
@@ -94,6 +91,32 @@ export class RichTextRules {
}),
//Create annotation to a field on the text document
+ new InputRule(new RegExp(/>::$/), (state, match, start, end) => {
+ const creator = (doc: Doc) => {
+ const textDoc = this.Document[DocData];
+ const numInlines = NumCast(textDoc.inlineTextCount);
+ textDoc.inlineTextCount = numInlines + 1;
+ const node = (state.doc.resolve(start) as any).nodeAfter;
+ const newNode = schema.nodes.dashComment.create({ docId: doc[Id], reflow: false });
+ const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: doc[Id], float: 'right' });
+ const sm = state.storedMarks || undefined;
+ this.TextBox.EditorView?.dispatch(
+ node
+ ? this.TextBox.EditorView.state.tr
+ .insert(start, newNode)
+ .replaceRangeWith(start + 1, end + 2, dashDoc)
+ .insertText(' ', start + 2)
+ .setStoredMarks([...node.marks, ...(sm ? sm : [])])
+ : this.TextBox.EditorView.state.tr
+ );
+ };
+ DocUtils.addDocumentCreatorMenuItems(creator, creator, 200, 200);
+ const cm = ContextMenu.Instance;
+ cm.displayMenu(200, 200, undefined, true);
+
+ return null;
+ }),
+ //Create annotation to a field on the text document
new InputRule(new RegExp(/>>$/), (state, match, start, end) => {
const textDoc = this.Document[DocData];
const numInlines = NumCast(textDoc.inlineTextCount);
@@ -117,7 +140,7 @@ export class RichTextRules {
textDoc[inlineLayoutKey] = FormattedTextBox.LayoutString(inlineFieldKey); // create a layout string for the layout key that will render the annotation text
textDoc[inlineFieldKey] = ''; // set a default value for the annotation
const node = (state.doc.resolve(start) as any).nodeAfter;
- const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id] });
+ const newNode = schema.nodes.dashComment.create({ docId: textDocInline[Id], reflow: true });
const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 35, title: 'dashDoc', docId: textDocInline[Id], float: 'right' });
const sm = state.storedMarks || undefined;
const replaced = node
@@ -241,7 +264,7 @@ export class RichTextRules {
return null;
}),
- // stop using active style
+ // toggle alternate text UI %/
new InputRule(new RegExp(/%\//), (state, match, start, end) => {
setTimeout(this.TextBox.cycleAlternateText);
return state.tr.deleteRange(start, end);
@@ -265,49 +288,57 @@ export class RichTextRules {
// [[fieldKey]] => show field
// [[fieldKey=value]] => show field and also set its value
// [[fieldKey:docTitle]] => show field of doc
- new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => {
- const fieldKey = match[1];
- const docTitle = match[3]?.replace(':', '');
- const value = match[2]?.substring(1);
- const linkToDoc = (target: Doc) => {
- const rstate = this.TextBox.EditorView?.state;
- const selection = rstate?.selection.$from.pos;
- if (rstate) {
- this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
- }
+ new InputRule(
+ new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-z,A-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/),
+ (state, match, start, end) => {
+ const fieldKey = match[1];
+ const docTitle = match[3]?.replace(':', '');
+ const value = match[2]?.substring(1);
+ const linkToDoc = (target: Doc) => {
+ const rstate = this.TextBox.EditorView?.state;
+ const selection = rstate?.selection.$from.pos;
+ if (rstate) {
+ this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
+ }
- DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
+ DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' });
- const fstate = this.TextBox.EditorView?.state;
- if (fstate && selection) {
- this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
- }
- };
- const getTitledDoc = (docTitle: string) => {
- if (!DocServer.FindDocByTitle(docTitle)) {
- Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
- }
- const titledDoc = DocServer.FindDocByTitle(docTitle);
- return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
- };
- if (!fieldKey) {
- if (docTitle) {
- const target = getTitledDoc(docTitle);
- if (target) {
- setTimeout(() => linkToDoc(target));
- return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate && selection) {
+ this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection))));
+ }
+ };
+ const getTitledDoc = (docTitle: string) => {
+ if (!DocServer.FindDocByTitle(docTitle)) {
+ Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true }));
}
+ const titledDoc = DocServer.FindDocByTitle(docTitle);
+ return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc;
+ };
+ if (!fieldKey) {
+ if (docTitle) {
+ const target = getTitledDoc(docTitle);
+ if (target) {
+ setTimeout(() => linkToDoc(target));
+ return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
+ }
+ }
+ return state.tr;
}
- return state.tr;
- }
- if (value !== '' && value !== undefined) {
- const num = value.match(/^[0-9.]$/);
- this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
- }
- const target = getTitledDoc(docTitle);
- const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
- return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
- }),
+ if (value?.includes(',')) {
+ const values = value.split(',');
+ const strs = values.some(v => !v.match(/^[-]?[0-9.]$/));
+ this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v)));
+ } else if (value !== '' && value !== undefined) {
+ const num = value.match(/^[0-9.]$/);
+ this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value;
+ }
+ const target = getTitledDoc(docTitle);
+ const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false });
+ return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true);
+ },
+ { inCode: true }
+ ),
// create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
// wiki:title
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index a342285b0..a141ef041 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -1,6 +1,7 @@
import * as React from 'react';
import { DOMOutputSpec, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from 'prosemirror-model';
import { Doc } from '../../../../fields/Doc';
+import { Utils } from '../../../../Utils';
const emDOM: DOMOutputSpec = ['em', 0];
const strongDOM: DOMOutputSpec = ['strong', 0];
@@ -44,7 +45,7 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM(node: any) {
const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), '');
const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), '');
- return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
+ return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0];
},
},
noAutoLinkAnchor: {
@@ -104,7 +105,7 @@ export const marks: { [index: string]: MarkSpec } = {
node.attrs.title,
],
]
- : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
+ : ['a', { id: '' + Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0];
},
},
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index 31f001b11..4706a97fa 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -179,6 +179,7 @@ export const nodes: { [index: string]: NodeSpec } = {
dashComment: {
attrs: {
docId: { default: '' },
+ reflow: { default: true },
},
inline: true,
group: 'inline',
@@ -275,6 +276,17 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
+ paintButton: {
+ inline: true,
+ attrs: {},
+ group: 'inline',
+ draggable: false,
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` };
+ return ['div', { ...node.attrs, ...attrs }];
+ },
+ },
+
video: {
inline: true,
attrs: {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 1b2c45e72..b8f6575dd 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
-import { Animation, DocData } from '../../../../fields/DocSymbols';
+import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
import { Copy, Id } from '../../../../fields/FieldSymbols';
import { InkField } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
@@ -414,7 +414,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
bestTarget.rotation = NumCast(activeItem.config_rotation, NumCast(bestTarget.rotation));
bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
- setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
+ bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]);
+ bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10);
changed = true;
}
}
@@ -441,7 +442,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
const bestTargetData = bestTarget[DocData];
const current = bestTargetData[fkey];
const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined;
- if (hash) bestTargetData[fkey + '_' +hash] = current instanceof ObjectField ? current[Copy]() : current;
+ if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
}
bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 67f09f37b..56d50846a 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -16,7 +16,7 @@ import { DateField } from './DateField';
import {
AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
- Initializing, Self, SelfProxy, UpdatingFromServer, Width
+ Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
} from './DocSymbols'; // prettier-ignore
import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
import { InkField, InkTool } from './InkField';
@@ -60,14 +60,17 @@ export namespace Field {
case 'number':
case 'boolean':rawjava = String(field);
break;
- default: rawjava = field?.[ToJavascriptString]?.() ?? 'null';
+ default: rawjava = field?.[ToJavascriptString]?.() ?? '';
} // prettier-ignore
var script = rawjava;
// this is a bit hacky, but we treat '^@' references to a published document
// as a kind of macro to include the content of those documents
Doc.MyPublishedDocs.forEach(doc => {
const regex = new RegExp(`^\\^${doc.title}\\s`, 'm');
- script = script.replace(regex, Cast(doc.text, RichTextField, null)?.Text ?? '');
+ const sections = (Cast(doc.text, RichTextField, null)?.Text ?? '').split('--DOCDATA--');
+ if (script.match(regex)) {
+ script = script.replace(regex, sections[0]) + (sections.length > 1 ? sections[1] : '');
+ }
});
return script;
}
@@ -306,6 +309,7 @@ export class Doc extends RefField {
public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
public [Width] = () => NumCast(this[SelfProxy]._width);
public [Height] = () => NumCast(this[SelfProxy]._height);
+ public [TransitionTimer]: any = undefined;
public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here?
public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`;
public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`;
@@ -387,7 +391,7 @@ export class Doc extends RefField {
export namespace Doc {
export function SetContainer(doc: Doc, container: Doc) {
- doc.embedContainer = container;
+ container !== Doc.MyRecentlyClosed && (doc.embedContainer = container);
}
export function RunCachedUpdate(doc: Doc, field: string) {
const update = doc[CachedUpdates][field];
@@ -572,6 +576,16 @@ export namespace Doc {
return false;
}
+ export function RemoveEmbedding(doc: Doc, embedding: Doc) {
+ Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', embedding);
+ }
+ export function AddEmbedding(doc: Doc, embedding: Doc) {
+ Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding, undefined, undefined, undefined, undefined, undefined, true);
+ }
+ export function GetEmbeddings(doc: Doc) {
+ return DocListCast(Doc.Get(doc[DocData], 'proto_embeddings', true));
+ }
+
export function MakeEmbedding(doc: Doc, id?: string) {
const embedding = (!GetT(doc, 'isDataDoc', 'boolean', true) && doc.proto) || doc.type === DocumentType.CONFIG ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id);
const layout = Doc.LayoutField(embedding);
@@ -579,20 +593,18 @@ export namespace Doc {
Doc.SetLayout(embedding, Doc.MakeEmbedding(layout));
}
embedding.createdFrom = doc;
- embedding.proto_embeddingId = doc[DocData].proto_embeddingId = DocListCast(doc[DocData].proto_embeddings).length - 1;
+ embedding.proto_embeddingId = doc[DocData].proto_embeddingId = Doc.GetEmbeddings(doc).length - 1;
!Doc.GetT(embedding, 'title', 'string', true) && (embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`));
embedding.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(doc[DocData], 'proto_embeddings', embedding);
-
return embedding;
}
export function BestEmbedding(doc: Doc) {
const dataDoc = doc[DocData];
- const availableEmbeddings = DocListCast(dataDoc.proto_embeddings);
+ 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);
+ bestEmbedding && Doc.AddDocToList(dataDoc, 'protoEmbeddings', doc, undefined, undefined, undefined, undefined, undefined, true);
return bestEmbedding ?? Doc.MakeEmbedding(doc);
}
@@ -866,7 +878,7 @@ export namespace Doc {
export function FindReferences(infield: Doc | List<any>, references: Set<Doc>, system: boolean | undefined) {
if (infield instanceof Promise) return;
if (!(infield instanceof Doc)) {
- infield.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
+ infield?.forEach(val => (val instanceof Doc || val instanceof List) && FindReferences(val, references, system));
return;
}
const doc = infield as Doc;
@@ -951,7 +963,7 @@ export namespace Doc {
Doc.GetProto(copy).embedContainer = undefined;
Doc.GetProto(copy).proto_embeddings = new List<Doc>([copy]);
} else {
- Doc.AddDocToList(copy[DocData], 'proto_embeddings', copy);
+ Doc.AddEmbedding(copy, copy);
}
copy.embedContainer = undefined;
if (retitle) {
@@ -972,9 +984,10 @@ export namespace Doc {
Object.keys(doc)
.filter(key => key.startsWith('acl'))
.forEach(key => (delegate[key] = doc[key]));
- if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DocData], 'proto_embeddings', delegate);
+ if (!Doc.IsSystem(doc)) Doc.AddEmbedding(doc, delegate);
title && (delegate.title = title);
delegate[Initializing] = false;
+ Doc.AddEmbedding(doc, delegate);
return delegate;
}
return undefined;
@@ -995,7 +1008,7 @@ export namespace Doc {
delegate[Initializing] = true;
delegate.proto = delegateProto;
delegate.author = Doc.CurrentUserEmail;
- Doc.AddDocToList(delegateProto[DocData], 'proto_embeddings', delegate);
+ Doc.AddEmbedding(delegateProto, delegate);
delegate[Initializing] = false;
delegateProto[Initializing] = false;
return delegate;
@@ -1032,13 +1045,13 @@ export namespace Doc {
// This function converts a generic field layout display into a field layout that displays a specific
// metadata field indicated by the title of the template field (not the default field that it was rendering)
//
- export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>): boolean {
+ export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt<Doc>, keepFieldKey = false): boolean {
// find the metadata field key that this template field doc will display (indicated by its title)
- const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
+ const metadataFieldKey = keepFieldKey ? Doc.LayoutFieldKey(templateField) : StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField);
// update the original template to mark it as a template
templateField.isTemplateForField = metadataFieldKey;
- templateField.title = metadataFieldKey;
+ !keepFieldKey && (templateField.title = metadataFieldKey);
const templateFieldValue = templateField[metadataFieldKey] || templateField[Doc.LayoutFieldKey(templateField)];
const templateCaptionValue = templateField.caption;
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 9c563abbf..f8a57acd5 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -15,6 +15,7 @@ export const DocLayout = Symbol('DocLayout');
export const DocFields = Symbol('DocFields');
export const DocCss = Symbol('DocCss');
export const DocAcl = Symbol('DocAcl');
+export const TransitionTimer = Symbol('DocTransitionTimer');
export const DirectLinks = Symbol('DocDirectLinks');
export const AclPrivate = Symbol('DocAclOwnerOnly');
export const AclReadonly = Symbol('DocAclReadOnly');
diff --git a/src/fields/List.ts b/src/fields/List.ts
index 9458a9611..ec31f7dae 100644
--- a/src/fields/List.ts
+++ b/src/fields/List.ts
@@ -317,7 +317,7 @@ class ListImpl<T extends Field> extends ObjectField {
return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`;
}
[ToString]() {
- return `List(${(this as any).length})`;
+ return `[${(this as any).map((field: any) => Field.toString(field))}]`;
}
}
export type List<T extends Field> = ListImpl<T> & (T | (T extends RefField ? Promise<T> : never))[];
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index b1a7a9c5e..307aec6fc 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -14,7 +14,7 @@ import * as path from 'path';
import { basename } from 'path';
import * as parse from 'pdf-parse';
import * as request from 'request-promise';
-import { Duplex } from 'stream';
+import { Duplex, Stream } from 'stream';
import { filesDirectory, publicDirectory } from '.';
import { Utils } from '../Utils';
import { Opt } from '../fields/Doc';
@@ -349,10 +349,8 @@ export namespace DashUploadUtils {
if (metadata instanceof Error) {
return { name: metadata.name, message: metadata.message };
}
- const outputFile = filename || metadata.filename;
- if (!outputFile) {
- return { name: source, message: 'output file not found' };
- }
+ const outputFile = filename || metadata.filename || '';
+
return UploadInspectedImage(metadata, outputFile, prefix);
};
@@ -552,7 +550,22 @@ export namespace DashUploadUtils {
writtenFiles = {};
}
} else {
- writtenFiles = await outputResizedImages(metadata.source, resolved, pathToDirectory(Directory.images));
+ try {
+ writtenFiles = await outputResizedImages(metadata.source, resolved, pathToDirectory(Directory.images));
+ } catch (e) {
+ // input is a blob or other, try reading it to create a metadata source file.
+ const reqSource = request(metadata.source);
+ let readStream: Stream = reqSource instanceof Promise ? await reqSource : reqSource;
+ const readSource = `${prefix}upload_${Utils.GenerateGuid()}.${metadata.contentType.split('/')[1].toLowerCase()}`;
+ await new Promise<void>((res, rej) =>
+ readStream
+ .pipe(createWriteStream(readSource))
+ .on('close', () => res())
+ .on('error', () => rej())
+ );
+ writtenFiles = await outputResizedImages(readSource, resolved, pathToDirectory(Directory.images));
+ fs.unlink(readSource, err => console.log("Couldn't unlink temporary image file:" + readSource));
+ }
}
for (const suffix of Object.keys(writtenFiles)) {
information.accessPaths[suffix] = getAccessPaths(images, writtenFiles[suffix]);