aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-04-27 22:56:56 -0400
committerbobzel <zzzman@gmail.com>2023-04-27 22:56:56 -0400
commitc27fcf300d72248e82d722e1b7ded9e0ca07f657 (patch)
treee6c99b49324c60713b59c65c8963b018f59dc1ec /src
parentd3dc9938b38e89b2215d13fbc5bc92d33502e818 (diff)
parent66e5fe4d8c4c6fae768305e31b45735f563b7500 (diff)
merged with master
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts7
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/util/CurrentUserUtils.ts29
-rw-r--r--src/client/util/DocumentManager.ts15
-rw-r--r--src/client/util/DragManager.ts18
-rw-r--r--src/client/util/RTFMarkup.tsx137
-rw-r--r--src/client/util/ServerStats.tsx54
-rw-r--r--src/client/util/SharingManager.tsx9
-rw-r--r--src/client/views/DocComponent.tsx104
-rw-r--r--src/client/views/DocumentButtonBar.tsx4
-rw-r--r--src/client/views/DocumentDecorations.tsx112
-rw-r--r--src/client/views/FilterPanel.scss12
-rw-r--r--src/client/views/FilterPanel.tsx41
-rw-r--r--src/client/views/LightboxView.tsx6
-rw-r--r--src/client/views/MainView.tsx26
-rw-r--r--src/client/views/MainViewModal.tsx37
-rw-r--r--src/client/views/PropertiesButtons.tsx7
-rw-r--r--src/client/views/StyleProvider.tsx5
-rw-r--r--src/client/views/collections/CollectionPileView.tsx39
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx15
-rw-r--r--src/client/views/collections/CollectionTimeView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx1
-rw-r--r--src/client/views/collections/TabDocView.tsx1
-rw-r--r--src/client/views/collections/TreeView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx24
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx113
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx13
-rw-r--r--src/client/views/nodes/DocumentView.tsx77
-rw-r--r--src/client/views/nodes/FilterBox.scss189
-rw-r--r--src/client/views/nodes/FilterBox.tsx0
-rw-r--r--src/client/views/nodes/ImageBox.scss4
-rw-r--r--src/client/views/nodes/ImageBox.tsx3
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.scss1
-rw-r--r--src/client/views/nodes/button/FontIconBox.tsx6
-rw-r--r--src/client/views/nodes/formattedText/DashDocView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FootnoteView.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss66
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx55
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts78
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts61
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts2
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx1
-rw-r--r--src/client/views/pdf/PDFViewer.tsx3
-rw-r--r--src/client/views/search/SearchBox.tsx2
-rw-r--r--src/client/views/topbar/TopBar.tsx14
-rw-r--r--src/fields/Doc.ts2
-rw-r--r--src/fields/util.ts3
48 files changed, 832 insertions, 576 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 8f79ebb03..ba64f993c 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -29,6 +29,13 @@ import { GestureOverlay } from './views/GestureOverlay';
export namespace DocServer {
let _cache: { [id: string]: RefField | Promise<Opt<RefField>> } = {};
+ export function QUERY_SERVER_CACHE(title: string) {
+ const foundDocId = Array.from(Object.keys(_cache))
+ .filter(key => _cache[key] instanceof Doc)
+ .find(key => (_cache[key] as Doc).title === title);
+
+ return foundDocId ? (_cache[foundDocId] as Doc) : undefined;
+ }
export function UPDATE_SERVER_CACHE(print: boolean = false) {
if (print) {
const strings: string[] = [];
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c5b6546d7..b81ca6b2b 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1077,7 +1077,7 @@ export namespace Docs {
}
export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', enableDragWhenActive: true, _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
}
export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index abf7313a4..ca23e8f53 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -49,6 +49,7 @@ interface Button {
ignoreClick?: boolean;
buttonText?: string;
backgroundColor?: string;
+ waitForDoubleClickToClick?: boolean;
// fields that do not correspond to DocumentOption fields
scripts?: { script?: string; onClick?: string; onDoubleClick?: string }
@@ -613,35 +614,36 @@ export class CurrentUserUtils {
static freeTools(): Button[] {
return [
- { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform
- { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform
+ { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform
+ { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform
+ { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag (double click to set for all)",waitForDoubleClickToClick:true, btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(false, _readOnly_)', onDoubleClick:`{ return toggleRaiseOnDrag(true, _readOnly_)`}}, // Only when floating document is selected in freeform
]
}
static viewTools(): Button[] {
return [
- { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Snap\xA0Lines",icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "View\xA0All", icon: "object-group",toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Arrange", icon: "window", toolTip: "Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform
+ { title: "Snap\xA0Lines", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snap lines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "View\xA0All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Arrange",icon: "arrow-down-short-wide",toolTip: "Toggle Auto Arrange",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Reset", icon: "check", toolTip: "Reset View", btnType: ButtonType.ClickButton, expertMode: false, backgroundColor:"transparent", scripts: { onClick: 'resetView()'}}, // Only when floating document is selected in freeform
]
}
static textTools():Button[] {
return [
{ title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},
btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
- { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
- { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
+ { title: "Size", toolTip: "Font size (%size)", btnType: ButtonType.NumberButton, width: 75, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions },
+ { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}},
{ title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
{ title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
- { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
- { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }},
+ { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
+ { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} },
{ title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}},
{ title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}},
// { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}},
@@ -689,7 +691,6 @@ export class CurrentUserUtils {
{ title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 20, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
{ title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}},
{ title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
- { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag",btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(_readOnly_)'}}, // Only when floating document is selected in freeform
{ title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
{ title: "Num", icon:"",toolTip: "Frame Number (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
{ title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index e01457b4f..4542c1c05 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -8,8 +8,7 @@ import { CollectionViewType } from '../documents/DocumentTypes';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { TabDocView } from '../views/collections/TabDocView';
import { LightboxView } from '../views/LightboxView';
-import { MainView } from '../views/MainView';
-import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
import { PresBox } from '../views/nodes/trails';
@@ -227,7 +226,7 @@ export class DocumentManager {
let rootContextView = docViewPath.shift();
await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined })));
if (options.toggleTarget && (!options.didMove || targetDocView.rootDoc.hidden)) targetDocView.rootDoc.hidden = !targetDocView.rootDoc.hidden;
- else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) MainView.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
+ else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.rootDoc, options.openLocation);
};
// shows a document by first:
@@ -247,9 +246,17 @@ export class DocumentManager {
const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
if (viewIndex !== -1) return res(this.getDocumentView(docContextPath[viewIndex])!);
options.didMove = true;
- docContextPath.some(doc => TabDocView.Activate(doc)) || MainView.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ docContextPath.some(doc => TabDocView.Activate(doc)) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
});
+ if (options.openLocation === OpenWhere.lightbox) {
+ // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox)
+ const target = DocCast(targetDoc.annotationOn, targetDoc);
+ const contextView = this.getDocumentView(DocCast(target.context));
+ if (contextView?.docView?._componentView?.addDocTab?.(target, OpenWhere.lightbox)) {
+ await new Promise<void>(waitres => setTimeout(() => waitres()));
+ }
+ }
docContextPath.shift();
const childViewIterator = async (docView: DocumentView) => {
const innerDoc = docContextPath.shift();
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 404c85eb2..b6de5604d 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -11,6 +11,7 @@ import * as globalCssVariables from '../views/global/globalCssVariables.scss';
import { Colors } from '../views/global/globalEnums';
import { DocumentView } from '../views/nodes/DocumentView';
import { ScriptingGlobals } from './ScriptingGlobals';
+import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
@@ -597,7 +598,18 @@ export namespace DragManager {
}
}
-ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
- if (readOnly) return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE : 'transparent';
- DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged());
+ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?: boolean) {
+ if (readOnly) {
+ if (SelectionManager.Views().length)
+ return SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged)
+ ? Colors.MEDIUM_BLUE
+ : SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged === false)
+ ? 'transparent'
+ : DragManager.GetRaiseWhenDragged()
+ ? Colors.MEDIUM_BLUE_ALT
+ : Colors.PINK;
+ return DragManager.GetRaiseWhenDragged() ? Colors.PINK : 'transparent';
+ }
+ if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false)));
+ else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged());
});
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
new file mode 100644
index 000000000..69f62fc3f
--- /dev/null
+++ b/src/client/util/RTFMarkup.tsx
@@ -0,0 +1,137 @@
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { MainViewModal } from '../views/MainViewModal';
+
+@observer
+export class RTFMarkup extends React.Component<{}> {
+ static Instance: RTFMarkup;
+ @observable private isOpen = false; // whether the SharingManager modal is open or not
+
+ // private get linkVisible() {
+ // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+ // }
+
+ @action
+ public open = () => (this.isOpen = true);
+
+ @action
+ public close = () => (this.isOpen = false);
+
+ constructor(props: {}) {
+ super(props);
+ RTFMarkup.Instance = this;
+ }
+
+ @observable _stats: { [key: string]: any } | undefined;
+
+ /**
+ * @returns the main interface of the SharingManager.
+ */
+ @computed get cheatSheet() {
+ return (
+ <div style={{ textAlign: 'initial', height: '100%' }}>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b>
+ {` display wikipedia page for entered text (terminate with carriage return)`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`#tag `}</b>
+ {` add hashtag metadata to document. e.g, #idea`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`#, ## ... ###### `}</b>
+ {` set heading style based on number of '#'s between 1 and 6`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`#tag `}</b>
+ {` add hashtag metadata to document. e.g, #idea`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`>> `}</b>
+ {` add a sidebar text document inline`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`\`\` `}</b>
+ {` create a code snippet block`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`cmd-f `}</b>
+ {` collapse to an inline footnote)`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`cmd-e `}</b>
+ {` collapse to elided text`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`cmd-[ `}</b>
+ {` left justify text`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`cmd-\\ `}</b>
+ {` center text`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`cmd-] `}</b>
+ {` right justify text`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%% `}</b>
+ {` restore default styling`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%color `}</b>
+ {` changes text color styling. e.g., %green.`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%num `}</b>
+ {` set font size. e.g., %10 for 10pt font`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%eq `}</b>
+ {` creates an equation block for typeset math`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%alt `}</b>
+ {` switch between primary and alternate text (see bottom right Button for hover options).`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%> `}</b>
+ {` create a bockquote section. Terminate with 2 carriage returns`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%q `}</b>
+ {` start a quoted block of text that’s indented on the left and right. Terminate with %q`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%d `}</b>
+ {` start a block text where the first line is indented`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`%h `}</b>
+ {` start a block of text that begins with a hanging indent`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`[:doctitle]] `}</b>
+ {` hyperlink to document specified by it’s title`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`[[fieldname]] `}</b>
+ {` display value of fieldname`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`[[fieldname=value]] `}</b>
+ {` assign value to fieldname of document and display it`}
+ </p>
+ <p>
+ <b style={{ fontSize: 'larger' }}>{`[[fieldname:doctitle]] `}</b>
+ {` show value of fieldname from doc specified by it’s title`}
+ </p>
+ </div>
+ );
+ }
+
+ render() {
+ return <MainViewModal contents={this.cheatSheet} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+ }
+}
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
new file mode 100644
index 000000000..f84ad8598
--- /dev/null
+++ b/src/client/util/ServerStats.tsx
@@ -0,0 +1,54 @@
+import { action, computed, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { MainViewModal } from '../views/MainViewModal';
+import './SharingManager.scss';
+
+@observer
+export class ServerStats extends React.Component<{}> {
+ public static Instance: ServerStats;
+ @observable private isOpen = false; // whether the SharingManager modal is open or not
+
+ // private get linkVisible() {
+ // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+ // }
+
+ @action
+ public open = async () => {
+ /**
+ * Populates the list of users.
+ */
+ fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats)))));
+
+ this.isOpen = true;
+ };
+
+ public close = action(() => {
+ this.isOpen = false;
+ });
+
+ constructor(props: {}) {
+ super(props);
+ ServerStats.Instance = this;
+ }
+
+ @observable _stats: { [key: string]: any } | undefined;
+
+ /**
+ * @returns the main interface of the SharingManager.
+ */
+ @computed get sharingInterface() {
+ return (
+ <div>
+ <span>Active users:{this._stats?.socketMap.length}</span>
+ {this._stats?.socketMap.map((user: any) => (
+ <p>{user.username}</p>
+ ))}
+ </div>
+ );
+ }
+
+ render() {
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+ }
+}
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index a73eda04c..4937866f8 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -5,14 +5,13 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
-import { AclAdmin, AclPrivate, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, HierarchyMapping, Opt } from '../../fields/Doc';
+import { AclAdmin, AclPrivate, AclSym, AclUnset, DataSym, Doc, DocListCast, DocListCastAsync, HierarchyMapping } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
-import { Cast, NumCast, PromiseValue, StrCast } from '../../fields/Types';
+import { NumCast, StrCast } from '../../fields/Types';
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
import { Utils } from '../../Utils';
import { DocServer } from '../DocServer';
-import { CollectionView } from '../views/collections/CollectionView';
import { DictationOverlay } from '../views/DictationOverlay';
import { MainViewModal } from '../views/MainViewModal';
import { DocumentView } from '../views/nodes/DocumentView';
@@ -21,7 +20,6 @@ import { SearchBox } from '../views/search/SearchBox';
import { DocumentManager } from './DocumentManager';
import { GroupManager, UserOptions } from './GroupManager';
import { GroupMemberView } from './GroupMemberView';
-import { LinkManager } from './LinkManager';
import { SelectionManager } from './SelectionManager';
import './SharingManager.scss';
@@ -581,9 +579,8 @@ export class SharingManager extends React.Component<{}> {
</div>
);
});
-
return (
- <div className={'sharing-interface'}>
+ <div className="sharing-interface">
{GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
<div className="sharing-contents">
<p className={'share-title'}>
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index a59189fd2..d60ad68c6 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -141,29 +141,25 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
const effectiveAcl = GetEffectiveAcl(this.dataDoc);
const indocs = doc instanceof Doc ? [doc] : doc;
const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
- if (docs.length) {
- docs.map(doc => {
- Doc.SetInPlace(doc, 'followLinkToggle', undefined, true);
- doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true);
+
+ docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
+ const targetDataDoc = this.dataDoc;
+ const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
+ const toRemove = value.filter(v => docs.includes(v));
+
+ if (toRemove.length !== 0) {
+ const recent = Doc.MyRecentlyClosed;
+ toRemove.forEach(doc => {
+ leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
+ Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+ doc.context = undefined;
+ if (recent) {
+ Doc.RemoveDocFromList(recent, 'data', doc);
+ doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
+ }
});
- const targetDataDoc = this.dataDoc;
- const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
- const toRemove = value.filter(v => docs.includes(v));
-
- if (toRemove.length !== 0) {
- const recent = Doc.MyRecentlyClosed;
- toRemove.forEach(doc => {
- leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey);
- Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
- doc.context = undefined;
- if (recent) {
- Doc.RemoveDocFromList(recent, 'data', doc);
- doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true);
- }
- });
- this.isAnyChildContentActive() && this.props.select(false);
- return true;
- }
+ this.isAnyChildContentActive() && this.props.select(false);
+ return true;
}
return false;
@@ -190,46 +186,44 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
return false;
}
const targetDataDoc = this.props.Document[DataSym];
- const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
- const added = docs.filter(d => !docList.includes(d));
const effectiveAcl = GetEffectiveAcl(targetDataDoc);
+ if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
+ return false;
+ }
+ const added = docs;
if (added.length) {
- if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) {
- return false;
- } else {
- if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
- added.forEach(d => {
- for (const [key, value] of Object.entries(this.props.Document[AclSym])) {
- if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d);
- }
- });
- }
+ if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) {
+ added.forEach(d => {
+ for (const key of Object.keys(this.props.Document[AclSym])) {
+ if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d);
+ }
+ });
+ }
- if (effectiveAcl === AclAugment) {
- added.map(doc => {
- if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
+ if (effectiveAcl === AclAugment) {
+ added.map(doc => {
+ if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc);
+ doc.context = this.props.Document;
+ if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
+ Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+ });
+ } else {
+ added
+ .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)))
+ .map(doc => {
+ // only make a pushpin if we have acl's to edit the document
+ //DocUtils.LeavePushpin(doc);
+ doc._stayInCollection = undefined;
doc.context = this.props.Document;
if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
- Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
+
+ Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
});
- } else {
- added
- .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)))
- .map(doc => {
- // only make a pushpin if we have acl's to edit the document
- //DocUtils.LeavePushpin(doc);
- doc._stayInCollection = undefined;
- doc.context = this.props.Document;
- if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document;
-
- Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc);
- });
- const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
- if (annoDocs instanceof List) annoDocs.push(...added);
- else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
- targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now()));
- }
+ const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>;
+ if (annoDocs instanceof List) annoDocs.push(...added);
+ else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added);
+ targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now()));
}
}
return true;
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 8c2ced55a..30e41b06c 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -303,10 +303,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
onPointerEnter={action(e => (this.subEndLink = (pinLayout ? 'Layout' : '') + (pinLayout && pinContent ? ' &' : '') + (pinContent ? ' Content' : '')))}
onPointerLeave={action(e => (this.subEndLink = ''))}
onClick={e => {
- const docs = this.props
- .views()
- .filter(v => v)
- .map(dv => dv!.rootDoc);
this.view0 &&
DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.view0.props.Document, true, this.view0, {
pinDocLayout: pinLayout,
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 042e8f6d0..2811c96eb 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -12,7 +12,7 @@ import { InkField } from '../../fields/InkField';
import { ScriptField } from '../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../fields/Types';
import { GetEffectiveAcl } from '../../fields/util';
-import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
+import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils';
import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from '../util/DragManager';
@@ -34,6 +34,7 @@ import React = require('react');
import { RichTextField } from '../../fields/RichTextField';
import { LinkFollower } from '../util/LinkFollower';
import _ = require('lodash');
+import { DocumentManager } from '../util/DocumentManager';
@observer
export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> {
@@ -509,73 +510,86 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
dragRight = false,
dragBotRight = false,
dragTop = false;
- let dX = 0,
- dY = 0,
- dW = 0,
- dH = 0;
+ let dXin = 0,
+ dYin = 0,
+ dWin = 0,
+ dHin = 0;
switch (this._resizeHdlId.split(' ')[0]) {
case '':
break;
case 'documentDecorations-topLeftResizer':
- dX = -1;
- dY = -1;
- dW = -move[0];
- dH = -move[1];
+ dXin = -1;
+ dYin = -1;
+ dWin = -move[0];
+ dHin = -move[1];
break;
case 'documentDecorations-topRightResizer':
- dW = move[0];
- dY = -1;
- dH = -move[1];
+ dWin = move[0];
+ dYin = -1;
+ dHin = -move[1];
break;
case 'documentDecorations-topResizer':
- dY = -1;
- dH = -move[1];
+ dYin = -1;
+ dHin = -move[1];
dragTop = true;
break;
case 'documentDecorations-bottomLeftResizer':
- dX = -1;
- dW = -move[0];
- dH = move[1];
+ dXin = -1;
+ dWin = -move[0];
+ dHin = move[1];
break;
case 'documentDecorations-bottomRightResizer':
- dW = move[0];
- dH = move[1];
+ dWin = move[0];
+ dHin = move[1];
dragBotRight = true;
break;
case 'documentDecorations-bottomResizer':
- dH = move[1];
+ dHin = move[1];
dragBottom = true;
break;
case 'documentDecorations-leftResizer':
- dX = -1;
- dW = -move[0];
+ dXin = -1;
+ dWin = -move[0];
break;
case 'documentDecorations-rightResizer':
- dW = move[0];
+ dWin = move[0];
dragRight = true;
break;
}
- SelectionManager.Views().forEach(
+ const isGroup = first.rootDoc._isGroup ? first.rootDoc : undefined;
+ const scaleViews = isGroup ? DocListCast(isGroup.data).map(doc => DocumentManager.Instance.getFirstDocumentView(doc)!) : SelectionManager.Views();
+ const aggBounds = aggregateBounds(scaleViews.map(view => view.rootDoc) as any, 0, 0);
+ const refWidth = aggBounds.r - aggBounds.x;
+ const refHeight = aggBounds.b - aggBounds.y;
+ const scaleRefPt = first.props
+ .ScreenToLocalTransform()
+ .inverse()
+ .transformPoint(
+ NumCast(isGroup?._xPadding) + (dXin ? refWidth : 0), //
+ NumCast(isGroup?._yPadding) + (dYin ? refHeight : 0)
+ );
+ scaleViews.forEach(
action((docView: DocumentView) => {
if (e.ctrlKey && !Doc.NativeHeight(docView.props.Document)) docView.toggleNativeDimensions();
- if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) {
- const doc = Document(docView.rootDoc);
+ if (dXin !== 0 || dYin !== 0 || dWin !== 0 || dHin !== 0) {
+ const doc = docView.rootDoc;
+ const refCent = docView.props.ScreenToLocalTransform().transformPoint(scaleRefPt[0], scaleRefPt[1]);
+
if (doc.nativeHeightUnfrozen && !NumCast(doc.nativeHeight)) {
doc._nativeHeight = (NumCast(doc._height) / NumCast(doc._width, 1)) * docView.nativeWidth;
}
const nwidth = docView.nativeWidth;
const nheight = docView.nativeHeight;
- let docheight = doc._height || 0;
- let docwidth = doc._width || 0;
- const width = docwidth;
- let height = docheight || (nheight / nwidth) * width;
- height = !height || isNaN(height) ? 20 : height;
+ const docwidth = NumCast(doc._width);
+ let docheight = (hgt => (!hgt || isNaN(hgt) ? 20 : hgt))(NumCast(doc._height) || (nheight / nwidth) * docwidth);
+ let dW = docwidth * (dWin / refWidth);
+ let dH = docheight * (dHin / refHeight);
const scale = docView.props.ScreenToLocalTransform().Scale;
const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen);
if (nwidth && nheight) {
- if (nwidth / nheight !== width / height && !dragBottom && !dragTop) {
- height = (nheight / nwidth) * width;
+ if (nwidth / nheight !== docwidth / docheight && !dragBottom && !dragTop) {
+ docheight = (nheight / nwidth) * docwidth;
}
if (modifyNativeDim && !dragBottom && !dragTop) {
// ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction
@@ -583,21 +597,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
else dW = (dH * nwidth) / nheight;
}
}
- let actualdW = Math.max(width + dW * scale, 20);
- let actualdH = Math.max(height + dH * scale, 20);
+ let actualdW = Math.max(docwidth + dW * scale, 20);
+ let actualdH = Math.max(docheight + dH * scale, 20);
+ let dX = !dWin ? 0 : scale * refCent[0] * (1 - (1 + dWin / refWidth));
+ let dY = !dHin ? 0 : scale * refCent[1] * (1 - (1 + dHin / refHeight));
const preserveNativeDim = doc._nativeHeightUnfrozen === false && doc._nativeDimModifiable === false;
const fixedAspect = nwidth && nheight && (!doc._fitWidth || preserveNativeDim || e.ctrlKey || doc.nativeHeightUnfrozen || doc.nativeDimModifiable);
if (fixedAspect) {
if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
if (dragRight && modifyNativeDim) {
if (Doc.NativeWidth(doc)) {
- doc._nativeWidth = (actualdW / (doc._width || 1)) * Doc.NativeWidth(doc);
+ doc._nativeWidth = (actualdW / (docwidth || 1)) * Doc.NativeWidth(doc);
}
} else {
if (!doc._fitWidth || preserveNativeDim) {
actualdH = (nheight / nwidth) * actualdW;
doc._height = actualdH;
- } else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
+ } else if (!modifyNativeDim || dragBotRight) {
+ doc._height = actualdH;
+ }
}
doc._width = actualdW;
} else {
@@ -605,21 +623,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
// frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight
// to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match
// a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used)
- doc._nativeHeight = (actualdH / (doc._height || 1)) * Doc.NativeHeight(doc);
+ doc._nativeHeight = (actualdH / (docheight || 1)) * Doc.NativeHeight(doc);
doc._autoHeight = false;
} else {
if (!doc._fitWidth || preserveNativeDim) {
actualdW = (nwidth / nheight) * actualdH;
doc._width = actualdW;
- } else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
+ } else if (!modifyNativeDim || dragBotRight) {
+ doc._width = actualdW;
+ }
}
if (!modifyNativeDim) {
- actualdH = Math.min((nheight / nwidth) * NumCast(doc._width), actualdH);
- doc._height = actualdH;
- } else doc._height = actualdH;
+ actualdH = Math.min((nheight / nwidth) * docwidth, actualdH);
+ }
+ doc._height = actualdH;
}
} else {
- const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2];
+ const rotCtr = [docwidth / 2, docheight / 2];
const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._rotation) / 180) * Math.PI);
const maxHeight = doc.nativeHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling();
@@ -632,8 +652,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc
doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]);
}
- doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
- doc.y = (doc.y || 0) + (dragBottom ? 0 : dY * (actualdH - docheight));
+ doc.x = NumCast(doc.x) + dX;
+ doc.y = NumCast(doc.y) + dY;
doc._lastModified = new DateField();
}
const val = this._dragHeights.get(docView.layoutDoc);
@@ -726,7 +746,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
}
// hide the decorations if the parent chooses to hide it or if the document itself hides it
const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations;
- const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating;
+ const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || this._isRounding || this._isRotating;
const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating;
const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating;
// if multiple documents have been opened at the same time, then don't show open button
diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss
index 7f907c8d4..c903f29ee 100644
--- a/src/client/views/FilterPanel.scss
+++ b/src/client/views/FilterPanel.scss
@@ -33,9 +33,10 @@
// }
.filterBox-select {
- // width: 90%;
+ display: flex;
+ width: 100%;
margin-top: 5px;
- // margin-bottom: 15px;
+ background: white;
}
.filterBox-saveBookmark {
@@ -150,8 +151,8 @@
.filterBox-treeView {
display: flex;
flex-direction: column;
- width: 200px;
- position: absolute;
+ width: 100%;
+ position: relative;
right: 0;
top: 0;
z-index: 1;
@@ -184,6 +185,7 @@
display: inline-block;
width: 100%;
margin-bottom: 10px;
- //height: calc(100% - 30px);
+ margin-left: 5px;
+ overflow: auto;
}
}
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index d35494f26..a237249c1 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -51,10 +51,12 @@ export class FilterPanel extends React.Component<filterProps> {
const keys = new Set<string>(noviceFields);
this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key)));
- return Array.from(keys.keys())
+ const sortedKeys = Array.from(keys.keys())
.filter(key => key[0])
.filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || noviceFields.includes(key) || !Doc.noviceMode)
.sort();
+ noviceFields.forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1));
+ return [...noviceFields, ...sortedKeys];
}
/**
@@ -129,7 +131,7 @@ export class FilterPanel extends React.Component<filterProps> {
maxVal = Math.max(num, maxVal);
}
});
- if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.rtFields > 20)) {
+ if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) {
this._chosenFacets.set(facetHeader, 'text');
} else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) {
} else {
@@ -140,7 +142,7 @@ export class FilterPanel extends React.Component<filterProps> {
facetValues = (facetHeader: string) => {
const allCollectionDocs = new Set<Doc>();
SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
- const set = new Set<string>();
+ const set = new Set<string>([String.fromCharCode(127) + '--undefined--']);
if (facetHeader === 'tags')
allCollectionDocs.forEach(child =>
Field.toString(child[facetHeader] as Field)
@@ -158,32 +160,29 @@ export class FilterPanel extends React.Component<filterProps> {
let nonNumbers = 0;
facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++);
- const facetValueDocSet = (nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => {
- return facetValue;
- });
- return facetValueDocSet;
+ return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
};
render() {
const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
return (
- <div className="filterBox-treeView" style={{ position: 'relative', width: '100%' }}>
- <div className="filterBox-select-bool">
- <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._filterBoolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.filterBoolean)}>
- {['AND', 'OR'].map(bool => (
- <option value={bool} key={bool}>
- {bool}
- </option>
- ))}
- </select>
- <div className="filterBox-select-text">filters together</div>
- </div>
-
+ <div className="filterBox-treeView">
<div className="filterBox-select">
- <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
+ <div style={{ width: '100%' }}>
+ <Select placeholder="Add a filter..." options={options} isMulti={false} onChange={val => this.facetClick((val as UserOptions).value)} onKeyDown={e => e.stopPropagation()} value={null} closeMenuOnSelect={true} />
+ </div>
+ <div className="filterBox-select-bool">
+ <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._filterBoolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.filterBoolean)}>
+ {['AND', 'OR'].map(bool => (
+ <option value={bool} key={bool}>
+ {bool}
+ </option>
+ ))}
+ </select>
+ </div>{' '}
</div>
- <div className="filterBox-tree" key="tree" style={{ overflow: 'auto' }}>
+ <div className="filterBox-tree" key="tree">
{Array.from(this.activeFacets.keys()).map(facetHeader => (
<div>
{facetHeader}
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index 69eec8456..c18a89481 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -63,10 +63,8 @@ export class LightboxView extends React.Component<LightboxViewProps> {
Doc.ActiveTool = InkTool.None;
MainView.Instance._exploreMode = false;
} else {
- if (doc) {
- const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
- l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen');
- }
+ const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement();
+ l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen');
CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.());
//TabDocView.PinDoc(doc, { hidePresBox: true });
this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4cbf8a811..5af9a9f9a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -14,7 +14,7 @@ import { StrCast } from '../../fields/Types';
import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../Utils';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
import { DocServer } from '../DocServer';
-import { Docs, DocUtils } from '../documents/Documents';
+import { Docs } from '../documents/Documents';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { CaptureManager } from '../util/CaptureManager';
import { DocumentManager } from '../util/DocumentManager';
@@ -22,8 +22,10 @@ import { GroupManager } from '../util/GroupManager';
import { HistoryUtil } from '../util/History';
import { Hypothesis } from '../util/HypothesisUtils';
import { ReportManager } from '../util/ReportManager';
+import { RTFMarkup } from '../util/RTFMarkup';
import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SelectionManager } from '../util/SelectionManager';
+import { ServerStats } from '../util/ServerStats';
import { ColorScheme, SettingsManager } from '../util/SettingsManager';
import { SharingManager } from '../util/SharingManager';
import { SnappingManager } from '../util/SnappingManager';
@@ -40,7 +42,7 @@ import { DashboardView } from './DashboardView';
import { DictationOverlay } from './DictationOverlay';
import { DocumentDecorations } from './DocumentDecorations';
import { GestureOverlay } from './GestureOverlay';
-import { TOPBAR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss';
+import { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } from './global/globalCssVariables.scss';
import { Colors } from './global/globalEnums';
import { KeyManager } from './GlobalKeyHandler';
import { InkTranscription } from './InkTranscription';
@@ -49,7 +51,7 @@ import { LinkMenu } from './linking/LinkMenu';
import './MainView.scss';
import { AudioBox } from './nodes/AudioBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
-import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
+import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './nodes/DocumentView';
import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
@@ -222,6 +224,7 @@ export class MainView extends React.Component {
constructor(props: Readonly<{}>) {
super(props);
+ DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl;
MainView.Instance = this;
DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any);
@@ -245,6 +248,7 @@ export class MainView extends React.Component {
...[
fa.faExclamationCircle,
fa.faEdit,
+ fa.faArrowDownShortWide,
fa.faTrash,
fa.faTrashAlt,
fa.faShare,
@@ -584,7 +588,7 @@ export class MainView extends React.Component {
Document={this.headerBarDoc}
DataDoc={undefined}
addDocument={undefined}
- addDocTab={MainView.addDocTabFunc}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={DefaultStyleProvider}
@@ -619,7 +623,7 @@ export class MainView extends React.Component {
Document={this.mainContainer!}
DataDoc={undefined}
addDocument={undefined}
- addDocTab={MainView.addDocTabFunc}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={this._hideUI ? DefaultStyleProvider : undefined}
@@ -688,7 +692,7 @@ export class MainView extends React.Component {
sidebarScreenToLocal = () => new Transform(0, -this.topOfSidebarDoc, 1);
mainContainerXf = () => this.sidebarScreenToLocal().translate(-this.leftScreenOffsetOfMainDocView, 0);
- static addDocTabFunc = (doc: Doc, location: OpenWhere): boolean => {
+ static addDocTabFunc_impl = (doc: Doc, location: OpenWhere): boolean => {
const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
const keyValue = whereFields[1]?.includes('KeyValue');
const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
@@ -716,7 +720,7 @@ export class MainView extends React.Component {
Document={this._sidebarContent.proto || this._sidebarContent}
DataDoc={undefined}
addDocument={undefined}
- addDocTab={MainView.addDocTabFunc}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
docViewPath={returnEmptyDoclist}
styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider}
@@ -748,7 +752,7 @@ export class MainView extends React.Component {
Document={Doc.MyLeftSidebarMenu}
DataDoc={undefined}
addDocument={undefined}
- addDocTab={MainView.addDocTabFunc}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
rootSelected={returnTrue}
removeDocument={returnFalse}
@@ -810,7 +814,7 @@ export class MainView extends React.Component {
</div>
)}
<div className="properties-container" style={{ width: this.propertiesWidth() }}>
- {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={MainView.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
+ {this.propertiesWidth() < 10 ? null : <PropertiesView styleProvider={DefaultStyleProvider} addDocTab={DocumentViewInternal.addDocTabFunc} width={this.propertiesWidth()} height={this.propertiesHeight()} />}
</div>
</div>
</div>
@@ -889,7 +893,7 @@ export class MainView extends React.Component {
docViewPath={returnEmptyDoclist}
moveDocument={this.moveButtonDoc}
addDocument={this.addButtonDoc}
- addDocTab={MainView.addDocTabFunc}
+ addDocTab={DocumentViewInternal.addDocTabFunc}
pinToPres={emptyFunction}
removeDocument={this.remButtonDoc}
ScreenToLocalTransform={this.buttonBarXf}
@@ -967,6 +971,8 @@ export class MainView extends React.Component {
{this.inkResources}
<DictationOverlay />
<SharingManager />
+ <ServerStats />
+ <RTFMarkup />
<SettingsManager />
<ReportManager />
<CaptureManager />
diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx
index 55dee005d..32997a944 100644
--- a/src/client/views/MainViewModal.tsx
+++ b/src/client/views/MainViewModal.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import "./MainViewModal.scss";
+import './MainViewModal.scss';
import { observer } from 'mobx-react';
export interface MainViewOverlayProps {
@@ -15,31 +15,38 @@ export interface MainViewOverlayProps {
@observer
export class MainViewModal extends React.Component<MainViewOverlayProps> {
-
render() {
const p = this.props;
const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1;
const overlayOpacity = p.overlayDisplayedOpacity || 0.4;
- return !p.isDisplayed ? (null) : (
- <div className="mainViewModal-cont" style={{
- pointerEvents: p.isDisplayed && p.interactive ? "all" : "none"
- }}>
- <div className="dialogue-box" style={{
- borderColor: "black",
- ...(p.dialogueBoxStyle || {}),
- opacity: p.isDisplayed ? dialogueOpacity : 0
- }} >
+ return !p.isDisplayed ? null : (
+ <div
+ className="mainViewModal-cont"
+ style={{
+ pointerEvents: p.isDisplayed && p.interactive ? 'all' : 'none',
+ }}>
+ <div
+ className="dialogue-box"
+ style={{
+ borderColor: 'black',
+ height: 'max-content',
+ overflow: 'auto',
+ maxHeight: '80%',
+ ...(p.dialogueBoxStyle || {}),
+ opacity: p.isDisplayed ? dialogueOpacity : 0,
+ }}>
{p.contents}
</div>
- <div className="overlay"
+ <div
+ className="overlay"
onClick={this.props?.closeOnExternalClick}
style={{
- backgroundColor: "black",
+ backgroundColor: 'black',
...(p.overlayStyle || {}),
- opacity: p.isDisplayed ? overlayOpacity : 0
+ opacity: p.isDisplayed ? overlayOpacity : 0,
}}
/>
</div>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 98dcf4f21..cf808f801 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -136,10 +136,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
// containerDoc.noShadow =
// containerDoc.disableDocBrushing =
// containerDoc._forceActive =
- containerDoc._fitContentsToBox = containerDoc._isLightbox = !containerDoc._isLightbox;
- containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
+ //containerDoc._fitContentsToBox =
+ containerDoc._isLightbox = !containerDoc._isLightbox;
+ //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined;
const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]);
- dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
+ //dv.rootDoc.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' });
containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.linkDisplay = false)));
});
}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index c810cb155..d13052f71 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -31,7 +31,6 @@ export enum StyleProp {
TreeViewSortings = 'treeViewSortings', // options for how to sort tree view items
DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
Opacity = 'opacity', // opacity of the document view
- Hidden = 'hidden', // whether the document view should not be isplayed
BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
BorderRounding = 'borderRounding', // border radius of the document view
Color = 'color', // foreground color of Document view items
@@ -173,8 +172,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
const backColor = backgroundCol() || docView?.props.styleProvider?.(docView.props.treeViewDoc, docView.props, StyleProp.BackgroundColor);
if (!backColor) return undefined;
return lightOrDark(backColor);
- case StyleProp.Hidden:
- return props?.LayoutTemplateString?.includes(KeyValueBox.name) || props?.LayoutTemplateString?.includes(SchemaRowBox.name) ? false : BoolCast(doc?.hidden);
case StyleProp.BorderRounding:
return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.borderRounding, doc?._viewType === CollectionViewType.Pile ? '50%' : ''));
case StyleProp.TitleHeight:
@@ -258,7 +255,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break;
docColor =
docColor ||
- (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc)
+ (Doc.IsSystem(doc)
? darkScheme()
? Colors.DARK_GRAY
: Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index fd9b0c0ce..5b96a8682 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -1,6 +1,6 @@
import { action, computed, IReactionDisposer, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc';
import { NumCast, StrCast } from '../../../fields/Types';
import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
@@ -26,12 +26,6 @@ export class CollectionPileView extends CollectionSubView() {
}
this._originalChrome = this.layoutDoc._chromeHidden;
this.layoutDoc._chromeHidden = true;
-
- // pileups are designed to go away when they are empty.
- this._disposers.selected = reaction(
- () => this.childDocs.length,
- num => !num && this.props.CollectionFreeFormDocumentView?.().props.removeDocument?.(this.props.Document)
- );
}
componentWillUnmount() {
this.layoutDoc._chromeHidden = this._originalChrome;
@@ -48,13 +42,15 @@ export class CollectionPileView extends CollectionSubView() {
@undoBatch
removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
- (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d)));
- return this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+ (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
+ const ret = this.props.moveDocument?.(doc, targetCollection, addDoc) || false;
+ if (ret && !DocListCast(this.rootDoc[this.fieldKey ?? 'data']).length) this.props.DocumentView?.().props.removeDocument?.(this.rootDoc);
+ return ret;
};
- toggleIcon = () => {
+ @computed get toggleIcon() {
return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
- };
+ }
// returns the contents of the pileup in a CollectionFreeFormView
@computed get contents() {
@@ -63,11 +59,11 @@ export class CollectionPileView extends CollectionSubView() {
<div className="collectionPileView-innards" style={{ pointerEvents: isStarburst || SnappingManager.GetIsDragging() ? undefined : 'none' }}>
<CollectionFreeFormView
{...this.props}
+ childContentsActive={returnFalse}
layoutEngine={this.layoutEngine}
- childDocumentsActive={isStarburst ? returnTrue : undefined}
addDocument={this.addPileDoc}
childCanEmbedOnDrag={true}
- childClickScript={this.toggleIcon()}
+ childClickScript={this.toggleIcon}
moveDocument={this.removePileDoc}
/>
</div>
@@ -77,6 +73,9 @@ export class CollectionPileView extends CollectionSubView() {
// toggles the pileup between starburst to compact
toggleStarburst = action(() => {
if (this.layoutEngine() === computeStarburstLayout.name) {
+ if (this.rootDoc[WidthSym]() !== NumCast(this.rootDoc._starburstDiameter, 500)) {
+ this.rootDoc._starburstDiameter = this.rootDoc[WidthSym]();
+ }
const defaultSize = 110;
this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2;
this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2;
@@ -87,15 +86,11 @@ export class CollectionPileView extends CollectionSubView() {
this.layoutDoc._panY = -10;
this.props.Document._pileLayoutEngine = computePassLayout.name;
} else {
- const defaultSize = 25;
- !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250);
- !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5);
- if (this.layoutEngine() === computePassLayout.name) {
- this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
- this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
- this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
- this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
- }
+ const defaultSize = NumCast(this.rootDoc._starburstDiameter, 500);
+ this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2;
+ this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2;
+ this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym]();
+ this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym]();
this.layoutDoc._panX = this.layoutDoc._panY = 0;
this.layoutDoc._width = this.layoutDoc._height = defaultSize;
this.props.Document._pileLayoutEngine = computeStarburstLayout.name;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bdad325d5..eedf639aa 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -10,7 +10,7 @@ import { listSpec } from '../../../fields/Schema';
import { SchemaHeaderField } from '../../../fields/SchemaHeaderField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
+import { emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils';
import { Docs, DocUtils } from '../../documents/Documents';
import { CollectionViewType } from '../../documents/DocumentTypes';
import { DragManager, dropActionType } from '../../util/DragManager';
@@ -238,9 +238,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
};
- @computed get onChildClickHandler() {
- return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
- }
+ onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
@computed get onChildDoubleClickHandler() {
return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
}
@@ -300,7 +298,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
@observable _renderCount = 5;
isChildContentActive = () =>
- this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined;
+ this.props.isContentActive?.() === false
+ ? false
+ : this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive))
+ ? true
+ : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false
+ ? false
+ : undefined;
isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined);
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
@@ -320,6 +324,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
renderDepth={this.props.renderDepth + 1}
PanelWidth={width}
PanelHeight={height}
+ pointerEvents={this.props.DocumentView?.().props.onClick?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
styleProvider={this.styleProvider}
docViewPath={this.props.docViewPath}
fitWidth={this.props.childFitWidth}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 4d5978548..03c010703 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -277,7 +277,7 @@ export class CollectionTimeView extends CollectionSubView() {
}
ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
- const pivotField = StrCast(pivotDoc._pivotField) || 'author';
+ const pivotField = StrCast(pivotDoc._pivotField, 'author');
let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField));
pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField);
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 790aa765d..b76033a0c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -46,6 +46,7 @@ interface CollectionViewProps_ extends FieldViewProps {
// property overrides for child documents
childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
childDocumentsActive?: () => boolean | undefined; // whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
+ childContentsActive?: () => boolean | undefined;
childFitWidth?: (child: Doc) => boolean;
childShowTitle?: () => string;
childOpacity?: () => number;
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 4bbc3bb44..45604c1bf 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -266,6 +266,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header
const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], null);
+ if (pinProps.pinViewport) PresBox.pinDocView(pinDoc, pinProps, anchorDoc ?? doc);
if (!pinProps?.audioRange && duration !== undefined) {
pinDoc.mediaStart = 'manual';
pinDoc.mediaStop = 'manual';
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 8fb610b87..4adf86683 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -801,7 +801,6 @@ export class TreeView extends React.Component<TreeViewProps> {
case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1;
case StyleProp.BackgroundColor: return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor));
case StyleProp.Highlighting: if (this.props.treeView.outlineMode) return undefined;
- case StyleProp.Hidden: return false;
case StyleProp.BoxShadow: return undefined;
case StyleProp.DocContents:
const highlightIndex = this.props.treeView.outlineMode ? Doc.DocBrushStatus.unbrushed : Doc.isBrushedHighlightedDegree(doc);
@@ -827,7 +826,6 @@ export class TreeView extends React.Component<TreeViewProps> {
};
embeddedStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => {
if (property.startsWith(StyleProp.Decorations)) return null;
- if (property.startsWith(StyleProp.Hidden)) return false;
return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView
};
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 81b0c4d8a..c1f3c5aa6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -93,6 +93,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
width: layout[WidthSym](),
height: layout[HeightSym](),
pair: { layout, data },
+ transition: 'all .3s',
replica: '',
});
});
@@ -100,28 +101,28 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
}
export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
- const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
const docMap = new Map<string, PoolData>();
- const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75
- const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
- const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
+ const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
+ const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
childPairs.forEach(({ layout, data }, i) => {
- const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75
+ const aspect = layout[HeightSym]() / layout[WidthSym]();
+ const docSize = Math.min(Math.min(400, layout[WidthSym]()), Math.min(400, layout[WidthSym]()) / aspect) * burstScale;
const deg = (i / childPairs.length) * Math.PI * 2;
docMap.set(layout[Id], {
- x: Math.cos(deg) * burstRadius[0] - docSize / 2,
- y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2,
- width: docSize, //layout[WidthSym](),
- height: (docSize * layout[HeightSym]()) / layout[WidthSym](),
+ x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
+ y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)),
+ width: docSize,
+ height: docSize * aspect,
zIndex: NumCast(layout.zIndex),
pair: { layout, data },
replica: '',
color: 'white',
backgroundColor: 'white',
+ transition: 'all 0.3s',
});
});
- const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
- return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
+ const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined };
+ return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
}
export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
@@ -424,6 +425,7 @@ function normalizeResults(
color: newPosRaw.color,
pair: ele[1].pair,
};
+ if (newPosRaw.transition) newPos.transition = newPosRaw.transition;
poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
}
});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ff0d01f29..719a39e8d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -141,7 +141,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@computed get fitToContentVals() {
return {
bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
- scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
+ scale:
+ !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x)
+ ? 1
+ : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
};
}
@computed get fitContentsToBox() {
@@ -308,6 +311,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
focus = (anchor: Doc, options: DocFocusOptions) => {
+ if (this._lightboxDoc) return;
const xfToCollection = options?.docTransform ?? Transform.Identity();
const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
@@ -327,7 +331,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getView = async (doc: Doc): Promise<Opt<DocumentView>> => {
return new Promise<Opt<DocumentView>>(res => {
- doc.hidden && (doc.hidden = false);
+ if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false;
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -778,7 +782,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._batch?.end();
};
+ @action
onClick = (e: React.MouseEvent) => {
+ if (this._lightboxDoc) this._lightboxDoc = undefined;
if (this.onBrowseClickHandler()) {
if (this.props.DocumentView?.()) {
this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY });
@@ -1274,7 +1280,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={emptyFunction}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
addDocTab={this.addDocTab}
addDocument={this.props.addDocument}
@@ -1320,13 +1326,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case undefined:
case OpenWhere.lightbox:
if (this.layoutDoc._isLightbox) {
- // _isLightbox docs have a script that will unset this overlay onClick
- this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc);
+ this._lightboxDoc = doc;
+ return true;
+ } else if (this.childDocList?.includes(doc)) {
+ if (doc.hidden) doc.hidden = false;
return true;
}
}
return this.props.addDocTab(doc, where);
});
+ @observable _lightboxDoc: Opt<Doc>;
getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
const childDoc = params.pair.layout;
@@ -1936,7 +1945,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
1000
);
};
-
+ lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
+ lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
+ lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
render() {
TraceMobx();
return (
@@ -1961,40 +1972,70 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
? 'all'
: (this.props.pointerEvents?.() as any),
+ textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
transform: `scale(${this.nativeDimScaling || 1})`,
width: `${100 / (this.nativeDimScaling || 1)}%`,
height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
- {this._firstRender ? this.placeholder : this.marqueeView}
- {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
-
- {/* // uncomment to show snap lines */}
- <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
- <svg style={{ width: '100%', height: '100%' }}>
- {this._hLines?.map(l => (
- <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
- ))}
- {this._vLines?.map(l => (
- <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
- ))}
- </svg>
- </div>
+ {this._lightboxDoc ? (
+ <div style={{ padding: 15, width: '100%', height: '100%' }}>
+ <DocumentView
+ {...this.props}
+ Document={this._lightboxDoc}
+ DataDoc={undefined}
+ PanelWidth={this.lightboxPanelWidth}
+ PanelHeight={this.lightboxPanelHeight}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ onClick={this.onChildClickHandler}
+ onKey={this.onKeyDown}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ onBrowseClick={this.onBrowseClickHandler}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
+ addDocTab={this.addDocTab}
+ ScreenToLocalTransform={this.lightboxScreenToLocal}
+ fitContentsToBox={undefined}
+ focus={this.focus}
+ />
+ </div>
+ ) : (
+ <>
+ {this._firstRender ? this.placeholder : this.marqueeView}
+ {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+
+ {/* // uncomment to show snap lines */}
+ <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
+ <svg style={{ width: '100%', height: '100%' }}>
+ {this._hLines?.map(l => (
+ <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
+ ))}
+ {this._vLines?.map(l => (
+ <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+ ))}
+ </svg>
+ </div>
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
- <div
- className="collectionFreeForm-groupDropper"
- ref={this.createGroupEventsTarget}
- style={{
- width: this.ChildDrag ? '10000' : '100%',
- height: this.ChildDrag ? '10000' : '100%',
- left: this.ChildDrag ? '-5000' : 0,
- top: this.ChildDrag ? '-5000' : 0,
- position: 'absolute',
- background: '#0009930',
- pointerEvents: 'all',
- }}
- />
- ) : null}
+ {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
+ <div
+ className="collectionFreeForm-groupDropper"
+ ref={this.createGroupEventsTarget}
+ style={{
+ width: this.ChildDrag ? '10000' : '100%',
+ height: this.ChildDrag ? '10000' : '100%',
+ left: this.ChildDrag ? '-5000' : 0,
+ top: this.ChildDrag ? '-5000' : 0,
+ position: 'absolute',
+ background: '#0009930',
+ pointerEvents: 'all',
+ }}
+ />
+ ) : null}
+ </>
+ )}
</div>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index d443df0f3..e5f47823c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -371,10 +371,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@undoBatch
@action
- delete = () => {
+ delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
- selected.forEach(doc => this.props.removeDocument?.(doc));
+ selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc)));
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -425,9 +425,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
*/
@undoBatch
@action
- pinWithView = async () => {
- const doc = this.props.Document;
- TabDocView.PinDoc(doc, { pinViewport: this.Bounds });
+ pinWithView = () => {
+ TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds });
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
};
@@ -550,11 +549,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (this._commandExecuted || (e as any).propagationIsStopped) {
return;
}
- if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd') {
+ if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd' || e.key === 'h') {
this._commandExecuted = true;
e.stopPropagation();
(e as any).propagationIsStopped = true;
- this.delete();
+ this.delete(e, e.key === 'h');
e.stopPropagation();
}
if ('cbtsSpg'.indexOf(e.key) !== -1) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dc508d95f..a25e5c42d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -262,7 +262,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@observable _animateScalingTo = 0;
public get animateScaleTime() {
- return this._animateScaleTime ?? 300;
+ return this._animateScaleTime ?? 100;
}
public get displayName() {
return 'DocumentView(' + this.props.Document.title + ')';
@@ -295,8 +295,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Decorations + (this.props.isSelected() ? ':selected' : ''));
}
@computed get backgroundBoxColor() {
- const thumb = ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb));
- return thumb ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
+ return this.thumbShown() ? undefined : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box');
}
@computed get docContents() {
return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.DocContents);
@@ -413,6 +412,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
setTimeout(() => this._titleRef.current?.setIsFocused(true)); // use timeout in case title wasn't shown to allow re-render so that titleref will be defined
};
+ public static addDocTabFunc: (doc: Doc, location: OpenWhere) => boolean = returnFalse;
+
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
if (!this.Document.ignoreClick && this.pointerEvents !== 'none' && this.props.renderDepth >= 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) {
let stopPropagate = true;
@@ -446,24 +447,33 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
let clickFunc: undefined | (() => any);
if (!this.disableClickScriptFunc && this.onClickHandler?.script) {
const { clientX, clientY, shiftKey, altKey, metaKey } = e;
- const func = () =>
- this.onClickHandler?.script.run(
- {
- this: this.layoutDoc,
- self: this.rootDoc,
- _readOnly_: false,
- scriptContext: this.props.scriptContext,
- documentView: this.props.DocumentView(),
- clientX,
- clientY,
- shiftKey,
- altKey,
- metaKey,
- },
- console.log
- ).result?.select === true
- ? this.props.select(false)
- : '';
+ const func = () => {
+ // replace default add doc func with this view's add doc func.
+ // to allow override behaviors for how to display links to undisplayed documents.
+ // e.g., if this document is part of a labeled 'lightbox' container, then documents will be shown in place
+ // instead of in the global lightbox
+ const oldFunc = DocumentViewInternal.addDocTabFunc;
+ DocumentViewInternal.addDocTabFunc = this.props.addDocTab;
+ const res =
+ this.onClickHandler?.script.run(
+ {
+ this: this.layoutDoc,
+ self: this.rootDoc,
+ _readOnly_: false,
+ scriptContext: this.props.scriptContext,
+ documentView: this.props.DocumentView(),
+ clientX,
+ clientY,
+ shiftKey,
+ altKey,
+ metaKey,
+ },
+ console.log
+ ).result?.select === true
+ ? this.props.select(false)
+ : '';
+ DocumentViewInternal.addDocTabFunc = oldFunc;
+ };
clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click'));
} else {
// onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
@@ -707,7 +717,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
event: undoBatch(action(() => (this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined))),
icon: 'hand-point-up',
});
- !zorders && cm.addItem({ description: 'ZOrder...', noexpand: true, subitems: zorderItems, icon: 'compass' });
+ !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'compass' });
}
onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
@@ -720,11 +730,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' });
onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' });
- !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
} else if (LinkManager.Links(this.Document).length) {
onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' });
onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' });
- !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, subitems: onClicks, icon: 'mouse-pointer' });
+ !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' });
}
}
@@ -755,7 +765,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
!more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' });
}
const constantItems: ContextMenuProps[] = [];
-
+ constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
if (!Doc.IsSystem(this.rootDoc)) {
constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() });
@@ -764,11 +774,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
// need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions)
constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
}
- cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
}
+ cm.addItem({ description: 'General...', noexpand: !Doc.IsSystem(this.rootDoc), subitems: constantItems, icon: 'question' });
+
const help = cm.findByDescription('Help...');
const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
- helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
!Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
!Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
!Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' });
@@ -809,13 +819,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (documentationDescription && documentationLink) {
helpItems.push({
description: documentationDescription,
- event: () => {
- window.open(documentationLink, '_blank');
- },
+ event: () => window.open(documentationLink, '_blank'),
icon: 'book',
});
}
- if (!help) cm.addItem({ description: 'Help...', noexpand: true, subitems: helpItems, icon: 'question' });
+ if (!help) cm.addItem({ description: 'Help...', noexpand: !Doc.noviceMode, subitems: helpItems, icon: 'question' });
else cm.moveAfter(help);
e?.stopPropagation(); // DocumentViews should stop propagation of this event
@@ -858,6 +866,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
? true
: false;
};
+ docFilters = () => [...this.props.docFilters(), ...StrListCast(this.layoutDoc.docFilters)];
contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents);
@computed get contents() {
TraceMobx();
@@ -888,6 +897,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
docViewPath={this.props.viewPath}
thumbShown={this.thumbShown}
setContentView={this.setContentView}
+ docFilters={this.docFilters}
NativeDimScaling={this.props.NativeDimScaling}
PanelHeight={this.panelHeight}
setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined}
@@ -1318,9 +1328,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton;
return hideCount ? null : <DocumentLinksButton View={this} scaling={this.scaleToScreenSpace} OnHover={true} Bottom={this.topMost} ShowCount={true} />;
}
- @computed get hidden() {
- return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden);
- }
@computed get docViewPath(): DocumentView[] {
return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this];
}
@@ -1497,7 +1504,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
const xshift = Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
const yshift = Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
- return this.hidden ? null : (
+ return (
<div className="contentFittingDocumentView" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))}>
{!this.props.Document || !this.props.PanelWidth() ? null : (
<div
diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss
deleted file mode 100644
index 7f907c8d4..000000000
--- a/src/client/views/nodes/FilterBox.scss
+++ /dev/null
@@ -1,189 +0,0 @@
-.filterBox-flyout {
- display: block;
- text-align: left;
- font-weight: 100;
-
- .filterBox-flyout-facet {
- background-color: white;
- text-align: left;
- display: inline-block;
- position: relative;
- width: 100%;
-
- .filterBox-flyout-facet-check {
- margin-right: 6px;
- }
- }
-}
-
-.filter-bookmark {
- //display: flex;
-
- .filter-bookmark-icon {
- float: right;
- margin-right: 10px;
- margin-top: 7px;
- }
-}
-
-// .filterBox-bottom {
-// // position: fixed;
-// // bottom: 0;
-// // width: 100%;
-// }
-
-.filterBox-select {
- // width: 90%;
- margin-top: 5px;
- // margin-bottom: 15px;
-}
-
-.filterBox-saveBookmark {
- background-color: #e9e9e9;
- border-radius: 11px;
- padding-left: 8px;
- padding-right: 8px;
- padding-top: 5px;
- padding-bottom: 5px;
- margin: 8px;
- display: flex;
- font-size: 11px;
- cursor: pointer;
-
- &:hover {
- background-color: white;
- }
-
- .filterBox-saveBookmark-icon {
- margin-right: 6px;
- margin-top: 4px;
- margin-left: 2px;
- }
-}
-
-.filterBox-select-scope,
-.filterBox-select-bool,
-.filterBox-addWrapper,
-.filterBox-select-matched,
-.filterBox-saveWrapper {
- font-size: 10px;
- justify-content: center;
- justify-items: center;
- padding-bottom: 10px;
- display: flex;
-}
-
-.filterBox-addWrapper {
- font-size: 11px;
- width: 100%;
-}
-
-.filterBox-saveWrapper {
- width: 100%;
-}
-
-// .filterBox-top {
-// padding-bottom: 20px;
-// border-bottom: 2px solid black;
-// position: fixed;
-// top: 0;
-// width: 100%;
-// }
-
-.filterBox-select-scope {
- padding-bottom: 20px;
- border-bottom: 2px solid black;
-}
-
-.filterBox-title {
- font-size: 15;
- // border: 2px solid black;
- width: 100%;
- align-self: center;
- text-align: center;
- background-color: #d3d3d3;
-}
-
-.filterBox-select-bool {
- margin-top: 6px;
-}
-
-.filterBox-select-text {
- margin-right: 8px;
- margin-left: 8px;
- margin-top: 3px;
-}
-
-.filterBox-select-box {
- margin-right: 2px;
- font-size: 30px;
- border: 0;
- background: transparent;
-}
-
-.filterBox-selection {
- border-radius: 6px;
- border: none;
- background-color: #e9e9e9;
- padding: 2px;
-
- &:hover {
- background-color: white;
- }
-}
-
-.filterBox-addFilter {
- width: 120px;
- background-color: #e9e9e9;
- border-radius: 12px;
- padding: 5px;
- margin: 5px;
- display: flex;
- text-align: center;
- justify-content: center;
-
- &:hover {
- background-color: white;
- }
-}
-
-.filterBox-treeView {
- display: flex;
- flex-direction: column;
- width: 200px;
- position: absolute;
- right: 0;
- top: 0;
- z-index: 1;
- background-color: #9f9f9f;
-
- .filterBox-tree {
- z-index: 0;
- }
-
- .filterBox-addfacet {
- display: inline-block;
- width: 200px;
- height: 30px;
- text-align: left;
-
- .filterBox-addFacetButton {
- display: flex;
- margin: auto;
- cursor: pointer;
- }
-
- > div,
- > div > div {
- width: 100%;
- height: 100%;
- }
- }
-
- .filterBox-tree {
- display: inline-block;
- width: 100%;
- margin-bottom: 10px;
- //height: calc(100% - 30px);
- }
-}
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
deleted file mode 100644
index e69de29bb..000000000
--- a/src/client/views/nodes/FilterBox.tsx
+++ /dev/null
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 22dbc1e80..29943e156 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -2,7 +2,9 @@
border-radius: inherit;
width: 100%;
height: 100%;
- position: relative;
+ position: absolute;
+ top: 0;
+ left: 0;
transform-origin: top left;
.imageBox-annotationLayer {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 98df777cb..c9be10d3a 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -407,7 +407,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
.filter(url => url)
.map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
- return paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')];
+ return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
}
@observable _isHovering = false; // flag to switch between primary and alternate images on hover
@@ -505,6 +505,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
}
})}
style={{
+ display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
width: this.props.PanelWidth() ? undefined : `100%`,
height: this.props.PanelWidth() ? undefined : `100%`,
pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 11220c300..b54364332 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -73,7 +73,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
value = eq ? value.substring(1) : value;
const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false;
value = dubEq ? value.substring(2) : value;
- const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: false };
+ const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: true };
if (dubEq) options.typecheck = false;
const script = CompileScript(value, options);
return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq };
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 6f578a9fc..75847c100 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -183,6 +183,7 @@
height: 100%;
position: absolute;
top: 0;
+ left: 0;
body {
::selection {
color: white;
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 1a75a7e76..4f570b5fc 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -610,16 +610,16 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
});
-ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snap lines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snapline' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
const selected = SelectionManager.Docs().lastElement();
// prettier-ignore
- const map: Map<'grid' | 'snap lines' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+ const map: Map<'grid' | 'snapline' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
['grid', {
undo: false,
checkResult: (doc:Doc) => doc._backgroundGridShow,
setDoc: (doc:Doc) => doc._backgroundGridShow = !doc._backgroundGridShow,
}],
- ['snap lines', {
+ ['snapline', {
undo: false,
checkResult: (doc:Doc) => doc.showSnapLines,
setDoc: (doc:Doc) => doc._showSnapLines = !doc._showSnapLines,
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index b31fc01ff..648c579d0 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -181,6 +181,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
height: this._height,
position: 'absolute',
display: 'inline-block',
+ left: 0,
+ top: 0,
}}
onPointerLeave={this.onPointerLeave}
onPointerEnter={this.onPointerEnter}
diff --git a/src/client/views/nodes/formattedText/FootnoteView.tsx b/src/client/views/nodes/formattedText/FootnoteView.tsx
index 531a60297..cf48e1250 100644
--- a/src/client/views/nodes/formattedText/FootnoteView.tsx
+++ b/src/client/views/nodes/formattedText/FootnoteView.tsx
@@ -83,13 +83,11 @@ export class FootnoteView {
};
toggle = () => {
- console.log('TOGGLE');
if (this.innerView) this.close();
else this.open();
};
close() {
- console.log('CLOSE');
this.innerView?.destroy();
this.innerView = null;
this.dom.textContent = '';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index fd7fbb333..b5a3c5d84 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -24,6 +24,27 @@ audiotag:hover {
transform: scale(2);
transform-origin: bottom center;
}
+.formattedTextBox {
+ touch-action: none;
+ background: inherit;
+ padding: 0;
+ border-width: 0px;
+ border-radius: inherit;
+ border-color: $medium-gray;
+ box-sizing: border-box;
+ background-color: inherit;
+ border-style: solid;
+ overflow-y: auto;
+ overflow-x: hidden;
+ color: inherit;
+ display: flex;
+ flex-direction: row;
+ transition: opacity 1s;
+ width: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
.formattedTextBox-cont {
touch-action: none;
@@ -51,6 +72,17 @@ audiotag:hover {
position: absolute;
}
}
+.formattedTextBox-alternateButton {
+ align-items: center;
+ flex-direction: column;
+ position: absolute;
+ color: white;
+ background: black;
+ right: 0;
+ bottom: 0;
+ width: 11;
+ height: 11;
+}
.formattedTextBox-outer-selected,
.formattedTextBox-outer {
@@ -193,16 +225,15 @@ audiotag:hover {
}
footnote {
- display: inline-block;
+ display: inline-flex;
+ top: -0.5em;
position: relative;
cursor: pointer;
-
- div {
- padding: 0 !important;
- }
+ height: 1em;
+ width: 0.5em;
}
-footnote::after {
+footnote::before {
content: counter(prosemirror-footnote);
vertical-align: super;
font-size: 75%;
@@ -216,15 +247,14 @@ footnote::after {
.footnote-tooltip {
cursor: auto;
font-size: 75%;
- position: absolute;
- left: -30px;
- top: calc(100% + 10px);
+ position: relative;
background: silver;
- padding: 3px;
border-radius: 2px;
- max-width: 100px;
- min-width: 50px;
- width: max-content;
+ min-width: 100px;
+ top: 2em;
+ height: max-content;
+ left: -1em;
+ padding: 3px;
}
.prosemirror-attribution {
@@ -239,8 +269,7 @@ footnote::after {
border-left-color: transparent;
border-right-color: transparent;
position: absolute;
- top: -5px;
- left: 27px;
+ top: -0.5em;
content: ' ';
height: 0;
width: 0;
@@ -734,8 +763,8 @@ footnote::after {
cursor: auto;
font-size: 75%;
position: absolute;
- left: -30px;
- top: calc(100% + 10px);
+ // left: -30px;
+ // top: calc(100% + 10px);
background: silver;
padding: 3px;
border-radius: 2px;
@@ -756,8 +785,7 @@ footnote::after {
border-left-color: transparent;
border-right-color: transparent;
position: absolute;
- top: -5px;
- left: 27px;
+ top: -0.5em;
content: ' ';
height: 0;
width: 0;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index bbe38cf99..0610d1e45 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1,9 +1,9 @@
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
import { isEqual } from 'lodash';
import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
-import { Configuration, OpenAIApi } from 'openai';
import { baseKeymap, selectAll } from 'prosemirror-commands';
import { history } from 'prosemirror-history';
import { inputRules } from 'prosemirror-inputrules';
@@ -68,6 +68,7 @@ import { schema } from './schema_rts';
import { SummaryView } from './SummaryView';
import applyDevTools = require('prosemirror-dev-tools');
import React = require('react');
+import { RTFMarkup } from '../../../util/RTFMarkup';
const translateGoogleApi = require('translate-google-api');
export const GoogleRef = 'googleDocId';
type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -852,8 +853,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const optionItems = options && 'subitems' in options ? options.subitems : [];
optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' });
optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' });
- optionItems.push({ description: !this.Document._singleLine ? 'Make Single Line' : 'Make Multi Line', event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine), icon: !this.Document._singleLine ? 'grip-lines' : 'bars' });
+ optionItems.push({
+ description: !this.Document._singleLine ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns',
+ event: () => (this.layoutDoc._singleLine = !this.layoutDoc._singleLine),
+ icon: !this.Document._singleLine ? 'grip-lines' : 'bars',
+ });
optionItems.push({ description: `${this.Document._autoHeight ? 'Lock' : 'Auto'} Height`, event: () => (this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight), icon: this.Document._autoHeight ? 'lock' : 'unlock' });
+ optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: 'text' });
!options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
this._downX = this._downY = Number.NaN;
};
@@ -1928,6 +1934,45 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
</div>
);
}
+ @computed get overlayAlternateIcon() {
+ const usePath = this.rootDoc[`${this.props.fieldKey}-usePath`];
+ return (
+ <Tooltip
+ title={
+ <div className="dash-tooltip">
+ toggle between
+ <span style={{ color: usePath === undefined ? 'black' : undefined }}>
+ <em> primary, </em>
+ </span>
+ <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
+ <em>alternate, </em>
+ </span>
+ and show
+ <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
+ <em> alternate on hover</em>
+ </span>
+ </div>
+ }>
+ <div
+ className="formattedTextBox-alternateButton"
+ onPointerDown={e =>
+ setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.props.fieldKey}-usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))
+ }
+ style={{
+ display: this.props.isContentActive() && !SnappingManager.GetIsDragging() ? 'flex' : 'none',
+ background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
+ color: usePath === undefined ? 'black' : 'white',
+ }}>
+ <FontAwesomeIcon icon="turn-up" size="sm" />
+ </div>
+ </Tooltip>
+ );
+ }
+ @computed get fieldKey() {
+ const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}-usePath`]);
+ return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `-${usePath.replace(':hover', '')}` : '');
+ }
+ @observable _isHovering = false;
render() {
TraceMobx();
const active = this.props.isContentActive() || this.props.isSelected();
@@ -1944,7 +1989,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' >
return styleFromLayoutString?.height === '0px' ? null : (
<div
- className="formattedTextBox-cont"
+ className="formattedTextBox"
+ onPointerEnter={action(() => (this._isHovering = true))}
+ onPointerLeave={action(() => (this._isHovering = false))}
ref={r =>
r?.addEventListener(
'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
@@ -1966,6 +2013,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
width: `${100 / scale}%`,
height: `${100 / scale}%`,
}),
+ display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined,
transition: 'inherit',
// overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined,
color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color),
@@ -2017,6 +2065,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
{this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
{this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
{this.audioHandle}
+ {this.overlayAlternateIcon}
</div>
</div>
);
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 68b0488a2..4dfe07b24 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -8,6 +8,7 @@ import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc';
import { GetEffectiveAcl } from '../../../../fields/util';
import { Utils } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
+import { RTFMarkup } from '../../../util/RTFMarkup';
import { SelectionManager } from '../../../util/SelectionManager';
import { OpenWhere } from '../DocumentView';
import { liftListItem, sinkListItem } from './prosemirrorPatches.js';
@@ -178,6 +179,83 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
return true;
});
+ bind('Cmd-?', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ RTFMarkup.Instance.open();
+ return true;
+ });
+ bind('Cmd-e', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ if (!state.selection.empty) {
+ const mark = state.schema.marks.summarizeInclusive.create();
+ const tr = state.tr.addMark(state.selection.$from.pos, state.selection.$to.pos, mark);
+ const content = tr.selection.content();
+ tr.selection.replaceWith(tr, schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }));
+ dispatch(tr);
+ }
+ return true;
+ });
+ bind('Cmd-]', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const resolved = state.doc.resolve(state.selection.from) as any;
+ const tr = state.tr;
+ if (resolved?.parent.type.name === 'paragraph') {
+ tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ if (node) {
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ }
+ }
+ dispatch(tr);
+ return true;
+ });
+ bind('Cmd-\\', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const resolved = state.doc.resolve(state.selection.from) as any;
+ const tr = state.tr;
+ if (resolved?.parent.type.name === 'paragraph') {
+ tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ if (node) {
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ }
+ }
+ dispatch(tr);
+ return true;
+ });
+ bind('Cmd-[', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const resolved = state.doc.resolve(state.selection.from) as any;
+ const tr = state.tr;
+ if (resolved?.parent.type.name === 'paragraph') {
+ tr.setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks);
+ } else {
+ const node = resolved.nodeAfter;
+ const sm = state.storedMarks || undefined;
+ if (node) {
+ tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]);
+ }
+ }
+ dispatch(tr);
+ return true;
+ });
+
+ bind('Cmd-f', (state: EditorState, dispatch: (tx: Transaction) => void) => {
+ const content = state.tr.selection.empty ? undefined : state.tr.selection.content().content.textBetween(0, state.tr.selection.content().size + 1);
+ const newNode = schema.nodes.footnote.create({}, content ? state.schema.text(content) : undefined);
+ const tr = state.tr;
+ tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
+ dispatch(
+ tr.setSelection(
+ new NodeSelection( // select the footnote node to open its display
+ tr.doc.resolve(
+ // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+ tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)
+ )
+ )
+ )
+ );
+ return true;
+ });
bind('Ctrl-a', (state: EditorState, dispatch: (tx: Transaction) => void) => {
dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1))));
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index e691869cc..cc19d12bd 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -5,7 +5,7 @@ import { Id } from '../../../../fields/FieldSymbols';
import { ComputedField } from '../../../../fields/ScriptField';
import { NumCast, StrCast } from '../../../../fields/Types';
import { normalizeEmail } from '../../../../fields/util';
-import { returnFalse, Utils } from '../../../../Utils';
+import { Utils } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs, DocUtils } from '../../../documents/Documents';
import { FormattedTextBox } from './FormattedTextBox';
@@ -28,7 +28,7 @@ export class RichTextRules {
emDash,
// > blockquote
- wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
+ wrappingInputRule(/%>$/, schema.nodes.blockquote),
// 1. create numerical ordered list
wrappingInputRule(
@@ -190,21 +190,6 @@ export class RichTextRules {
}
}),
- // %f create footnote
- new InputRule(new RegExp(/%f$/), (state, match, start, end) => {
- const newNode = schema.nodes.footnote.create({});
- const tr = state.tr;
- tr.deleteRange(start, end).replaceSelectionWith(newNode); // replace insertion with a footnote.
- return tr.setSelection(
- new NodeSelection( // select the footnote node to open its display
- tr.doc.resolve(
- // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
- tr.selection.anchor - (tr.selection.$anchor.nodeBefore?.nodeSize || 0)
- )
- )
- );
- }),
-
// activate a style by name using prefix '%<color name>'
new InputRule(new RegExp(/%[a-z]+$/), (state, match, start, end) => {
const color = match[0].substring(1, match[0].length);
@@ -229,6 +214,12 @@ export class RichTextRules {
}),
// stop using active style
+ new InputRule(new RegExp(/%alt$/), (state, match, start, end) => {
+ setTimeout(() => (this.Document[this.TextBox.props.fieldKey + '-usePath'] = this.Document[this.TextBox.props.fieldKey + '-usePath'] ? undefined : 'alternate'));
+ return state.tr.deleteRange(start, end);
+ }),
+
+ // stop using active style
new InputRule(new RegExp(/%%$/), (state, match, start, end) => {
const tr = state.tr.deleteRange(start, end);
const marks = state.tr.selection.$anchor.nodeBefore?.marks;
@@ -250,22 +241,26 @@ export class RichTextRules {
const fieldKey = match[1];
const docId = 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, { linkRelationship: '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))));
+ }
+ };
if (!fieldKey) {
if (docId) {
- DocServer.GetRefField(docId).then(docx => {
- const rstate = this.TextBox.EditorView?.state;
- const selection = rstate?.selection.$from.pos;
- if (rstate) {
- this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3))));
- }
- const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500 }, docId);
- DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { linkRelationship: '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 target = DocServer.QUERY_SERVER_CACHE(docId);
+ if (target) setTimeout(() => linkToDoc(target));
+ else DocServer.GetRefField(docId).then(docx => linkToDoc((docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId + '(auto)', _width: 500, _height: 500 }, docId)));
+
return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3);
}
return state.tr;
@@ -296,7 +291,7 @@ export class RichTextRules {
// create an inline equation node
// eq:<equation>>
- new InputRule(new RegExp(/:eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => {
+ new InputRule(new RegExp(/%eq([a-zA-Z-0-9\(\)]*)$/), (state, match, start, end) => {
const fieldKey = 'math' + Utils.GenerateGuid();
this.TextBox.dataDoc[fieldKey] = match[1];
const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey }));
@@ -374,7 +369,7 @@ export class RichTextRules {
const content = selected.selection.content();
const replaced = node ? selected.replaceRangeWith(start, end, schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })) : state.tr;
- return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))).setStoredMarks([...node.marks, ...sm]);
+ return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]);
}),
new InputRule(new RegExp(/%\)/), (state, match, start, end) => {
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 3898490d3..5b47e8a70 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -349,7 +349,7 @@ export const marks: { [index: string]: MarkSpec } = {
group: 'inline',
toDOM(node: any) {
const uid = node.attrs.userid.replace('.', '').replace('@', '');
- const min = Math.round(node.attrs.modified / 12);
+ const min = Math.round(node.attrs.modified / 60);
const hr = Math.round(min / 60);
const day = Math.round(hr / 60 / 24);
const remote = node.attrs.userid !== Doc.CurrentUserEmail ? ' UM-remote' : '';
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f8c47aafe..0b780f589 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -567,6 +567,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
bestTarget._panY = viewport.panY;
const dv = DocumentManager.Instance.getDocumentView(bestTarget);
if (dv) {
+ changed = true;
const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height);
activeItem.presMovement === PresMovement.Zoom && (bestTarget._viewScale = computedScale);
dv.ComponentView?.brushView?.(viewport);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 68241e61f..20803bba8 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -536,7 +536,8 @@ export class PDFViewer extends React.Component<IViewerProps> {
NativeWidth={returnZero}
NativeHeight={returnZero}
setContentView={emptyFunction} // override setContentView to do nothing
- pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone}
+ pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it.
+ childPointerEvents={'all'} // but freeform children need to get events to allow text editing, etc
renderDepth={this.props.renderDepth + 1}
isAnnotationOverlay={true}
fieldKey={this.props.fieldKey + '-annotations'}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 8f93f1150..32b661bb6 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -211,7 +211,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
}
@action
static staticSearchCollection(rootDoc: Opt<Doc>, query: string) {
- const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
+ const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.MARKER, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = [
'x',
'y',
diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx
index d63c25dbe..9bd2ba5ce 100644
--- a/src/client/views/topbar/TopBar.tsx
+++ b/src/client/views/topbar/TopBar.tsx
@@ -1,21 +1,22 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, FontSize, IconButton, Size } from 'browndash-components';
+import { Button, IconButton, Size } from 'browndash-components';
import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { FaBug, FaCamera } from 'react-icons/fa';
-import { AclAdmin, Doc, DocListCast } from '../../../fields/Doc';
+import { FaBug, FaCamera, FaStamp } from 'react-icons/fa';
+import { AclAdmin, Doc } from '../../../fields/Doc';
import { StrCast } from '../../../fields/Types';
import { GetEffectiveAcl } from '../../../fields/util';
import { DocumentManager } from '../../util/DocumentManager';
import { ReportManager } from '../../util/ReportManager';
+import { ServerStats } from '../../util/ServerStats';
import { SettingsManager } from '../../util/SettingsManager';
import { SharingManager } from '../../util/SharingManager';
import { UndoManager } from '../../util/UndoManager';
import { CollectionDockingView } from '../collections/CollectionDockingView';
import { ContextMenu } from '../ContextMenu';
import { DashboardView } from '../DashboardView';
-import { Borders, Colors } from '../global/globalEnums';
+import { Colors } from '../global/globalEnums';
import { MainView } from '../MainView';
import './TopBar.scss';
@@ -132,9 +133,10 @@ export class TopBar extends React.Component {
@computed get topbarRight() {
return (
<div className="topbar-right">
- <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => ReportManager.Instance.open()} icon={<FaBug />} />
+ <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ServerStats.Instance.open} icon={<FaStamp />} />
+ <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={ReportManager.Instance.open} icon={<FaBug />} />
<IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={<FontAwesomeIcon icon="question-circle" />} />
- <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={() => SettingsManager.Instance.open()} icon={<FontAwesomeIcon icon="cog" />} />
+ <IconButton size={Size.SMALL} color={Colors.LIGHT_GRAY} onClick={SettingsManager.Instance.open} icon={<FontAwesomeIcon icon="cog" />} />
{/* <Button text={'Logout'} borderRadius={5} hoverStyle={'gray'} backgroundColor={Colors.DARK_GRAY} color={this.textColor} fontSize={FontSize.SECONDARY} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> */}
</div>
);
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 22d0664ce..93c28cf08 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -1395,7 +1395,7 @@ export namespace Doc {
return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
const fieldStr = Field.toString(fieldVal as Field);
- return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
+ return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring
}
export function deiconifyView(doc: Doc) {
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 70d9ed61f..92f3a69eb 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -107,8 +107,11 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
redo: () => (receiver[prop] = value),
undo: () => {
const wasUpdate = receiver[UpdatingFromServer];
+ const wasForce = receiver[ForceServerWrite];
+ receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet
receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable.
receiver[prop] = curValue;
+ receiver[ForceServerWrite] = wasForce;
receiver[UpdatingFromServer] = wasUpdate;
},
prop: prop?.toString(),